之前写过一篇文章Android TextView 横竖排切换(字方向不变) 是自定义了一个LinearLayout, 实现了当然还不够, 还要对它进行操作, 平移,旋转 and 缩放, 相信很多小伙伴都知道对图片的平移等等操作最好用的就是矩阵了,因为有个方法叫做imageview.setImageMatrix(matrix), 直接构造一个矩阵对象然后设置到图片上就进行相关操作了, 那我就会想了,其他的View没有这个方法么, 有兴趣的伙伴可以试试,结果是只有getMatrix(), 那对于不是图片的情况呢?下面我们一起一步步的研究吧。
先列一下矩阵的基本方法, 后面也要用到的:
平移操作:matrix.postTranslate(translateX, translateY); 参数为平移的距离,而不是平移到哪!
旋转操作:matrix.postRotate(newRotation, midP.x, midP.y); 参数为旋转的角度以及以midP.x, midP.y 为原点进行旋转
缩放操作:matrix.postScale(scale, scale, midP.x, midP.y); 参数为缩放的倍数以及缩放原点。
先说一下对图片操作的思路:
首先一定要对一个imageview添加一个OnTouchListener监听, 在MotionEvent.ACTION_DOWN(按下), MotionEvent.ACTION_MOVE(移动),MotionEvent.ACTION_UP(抬起) 主要是这三个事件, 当然还有其他的事件要监听, 比如双指按下然后抬起一个的时候, 如果不监听这个事件的话那一个手指在屏幕的时候就会执行单击事件, 后面代码注释我会写到。
那根据矩阵的方法我会想到 在 手指按下 的时候我记录当前点击的坐标, 在手指移动的时候我计算出移动了多少, 然后matrix.postTranslate(translateX, translateY);执行平移,同理 缩放和旋转也要计算, 然后在不断移动的过程当中 不断的把生成的矩阵对象设置到图片上。我觉得描述出来就是这样了, 接下来看下我的效果图:
下面分析一下主要代码, 我写了详细的注释 方便查看
首先要添加一个view
private void addMyFrame() {
//新添加一个view的话要把其他的都设置成未选中, 只有新添加的是选中
for (int i = (addFrameHolders.size() - 1); i >= 0; i--) {
AddWordFrame addWordFrame = addFrameHolders.get(i).getAddWordFrame();
if (addWordFrame.isSelect()) {
addWordFrame.setSelect(false);
break;
}
}
addWordFrame = new AddWordFrame(this);
addWordFrame.setSelect(true);
//添加到屏幕上
frame.addView(addWordFrame);
layout = addWordFrame.getLayout();
addWordBitmap = BitmapUtils.convertViewToBitmap(layout);
addWordWidth = addWordBitmap.getWidth();
addWordHeight = addWordBitmap.getHeight();
//这里是想平移到屏幕比较好看的位置
addWordx1 = width/2 - addWordWidth /2;
addWordy1 = height/3;
//这里设置四个角的坐标是为了后续判断是不是点击到了删除或者旋转
//原图左上角
addWordFrame.leftTop.set(addWordx1, addWordy1);
// 原图右上角
addWordFrame.rightTop.set(addWordx1 + addWordWidth, addWordy1);
// 原图左下角
addWordFrame.leftBottom.set(addWordx1, addWordy1 + addWordHeight);
// 原图右下角
addWordFrame.rightBottom.set(addWordx1 + addWordWidth, addWordy1 + addWordHeight);
addWordMatrix = new Matrix();
addWordMatrix.postTranslate(addWordx1, addWordy1);
addWordFrame.setMatrix(addWordMatrix);
//这个类里面主要是存储当前view的区域
AddWordFrameState addWordFrameState = new AddWordFrameState();
addWordFrameState.setLeft(addWordx1);
addWordFrameState.setTop(addWordy1);
addWordFrameState.setRight(addWordx1 + addWordWidth);
addWordFrameState.setBottom(addWordy1 + addWordHeight);
AddFrameHolder addFrameHolder = new AddFrameHolder();
addFrameHolder.setAddWordFrame(addWordFrame);
addFrameHolder.setState(addWordFrameState);
addFrameHolders.add(addFrameHolder);
//设置一个监听
addWordFrame.setOnTouchListener(new AddWordMyOntouch());
AddWordSelectImageCount = addFrameHolders.size() - 1;
}
接下来主要看下监听的类: (这里要是做图片缩放代码差不多), 在action_move里面完成对view的平移旋转和缩放
class AddWordMyOntouch implements View.OnTouchListener {
//俩个手指间的距离
private float baseValue = 0;
//原来的角度
private float oldRotation;
//旋转和缩放的中点
private PointF midP;
//点中的要进行缩放的点与图片中点的距离
private float imgLengHalf;
//保存刚开始按下的点
private PointF startPoinF = new PointF();
private int NONE = 0; // 无
private int DRAG = 1; // 移动
private int ZOOM = 2; // 变换
@Override
public boolean onTouch(View v, MotionEvent event) {
int eventaction = event.getAction();
float event_x = (int) event.getRawX();
float event_y = (int) event.getRawY() - StatusBarHeightUtil.getStatusBarHeight(context);
//这里算是一个点击区域值 点中删除或者点中变换的100 * 100 的矩形区域 用这个区域来判断是否点中
int tempInt = 100;
int addint = 100;
switch (eventaction & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: // touch down so check if the
startPoinF.set(event_x, event_y);// 保存刚开始按下的坐标
//因为可能要添加多个这样的view 所以要按选中了哪个
selectMyFrame(event_x, event_y);
//如果有选中状态的额view
if (AddWordSelectImageCount != -1) {
addWordFrame = addFrameHolders.get(AddWordSelectImageCount).getAddWordFrame();
addWordMatrix = addFrameHolders.get(AddWordSelectImageCount).getAddWordFrame().getMatrix();
addWordSavedMatrix.set(addWordMatrix);
AddWordMode = DRAG;
//构造一个旋转按钮的矩形区域
Rect moveRect = new Rect((int) addWordFrame.rightBottom.x - tempInt,
(int) addWordFrame.rightBottom.y - tempInt, (int) addWordFrame.rightBottom.x + addint,
(int) addWordFrame.rightBottom.y + addint);
//删除按钮的矩形区域
Rect deleteRect = new Rect((int) addWordFrame.leftTop.x - tempInt,
(int) addWordFrame.leftTop.y - tempInt, (int) addWordFrame.leftTop.x + addint,
(int) addWordFrame.leftTop.y + addint);
//如果点中了变换
if(moveRect.contains((int)event_x, (int)event_y)){
LogUtils.e("点中了变换");
// 点中了变换
midP = midPoint(addWordFrame.leftTop, addWordFrame.rightBottom);
imgLengHalf = spacing(midP, addWordFrame.rightBottom);
oldRotation = rotation(midP, startPoinF);
AddWordMode = ZOOM;
}else if (deleteRect.contains((int)event_x, (int)event_y)) {
// 点中了删除
LogUtils.e("点中了删除");
deleteMyFrame();
}
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
midP = midPoint(addWordFrame.leftTop, addWordFrame.rightBottom);
imgLengHalf = spacing(midP, addWordFrame.rightBottom);
oldRotation = rotationforTwo(event);
break;
case MotionEvent.ACTION_MOVE: // touch drag with the ball
//如果是双指点中
if (event.getPointerCount() == 2) {
if (AddWordSelectImageCount != -1) {
AddWordMode = NONE;
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
float value = (float) Math.sqrt(x * x + y * y);// 计算两点的距离
//旋转的角度
float newRotation = rotationforTwo(event) - oldRotation;
if (baseValue == 0) {
baseValue = value;
} else {
//旋转到一定角度再执行 不能刚点击就执行旋转或者缩放
if (value - baseValue >= 10 || value - baseValue <= -10) {
float scale = value / baseValue;// 当前两点间的距离除以手指落下时两点间的距离就是需要缩放的比例。
addWordMatrix.set(addWordSavedMatrix);
addWordMatrix.postScale(scale, scale, midP.x, midP.y);
addWordMatrix.postRotate(newRotation, midP.x, midP.y);
}
}
}
} else if (event.getPointerCount() == 1) {
//单指点击
if (AddWordSelectImageCount != -1) {
if (AddWordMode == DRAG) {
if (event_x < MyApplication.getInstance().getScreenWidth() - 50 && event_x > 50
&& event_y > 100
&& event_y < MyApplication.getInstance().getScreenHeight() - 100) {
addWordMatrix.set(addWordSavedMatrix);
// 图片移动的距离
float translateX = event_x - startPoinF.x;
float translateY = event_y - startPoinF.y;
addWordMatrix.postTranslate(translateX, translateY);
}
} else if (AddWordMode == ZOOM) {
//点击到了缩放旋转按钮
PointF movePoin = new PointF(event_x, event_y);
float moveLeng = spacing(startPoinF, movePoin);
float newRotation = rotation(midP, movePoin) - oldRotation;
if (moveLeng > 10f) {
float moveToMidLeng = spacing(midP, movePoin);
float scale = moveToMidLeng / imgLengHalf;
addWordMatrix.set(addWordSavedMatrix);
addWordMatrix.postScale(scale, scale, midP.x, midP.y);
addWordMatrix.postRotate(newRotation, midP.x, midP.y);
}
}
}
}
if (AddWordSelectImageCount != -1) {
//最后在action_move 执行完前设置好矩阵 设置view的位置
addWordFrame = addFrameHolders.get(AddWordSelectImageCount).getAddWordFrame();
adjustLocation(addWordMatrix, addWordFrame);
}
break;
case MotionEvent.ACTION_POINTER_UP: //一只手指离开屏幕,但还有一只手指在上面会触此事件
//什么都没做
AddWordMode = NONE;
break;
case MotionEvent.ACTION_UP:
AddWordMode = NONE;
break;
}
return true;
}
}
那有了添加和相关操作, 那怎么判断选中状态呢 ,判断是否选中:
private void selectMyFrame(float x, float y) {
//选取消所有的选中 后面只有点击到的才是选中状态
for (int i = (addFrameHolders.size() - 1); i >= 0; i--) {
AddFrameHolder addFrameHolder = addFrameHolders.get(i);
if (addFrameHolder.getAddWordFrame().isSelect()) {
addFrameHolder.getAddWordFrame().setSelect(false);
break;
}
}
for (int i = (addFrameHolders.size() - 1); i >= 0; i--) {
AddFrameHolder addFrameHolder = addFrameHolders.get(i);
//创建一个矩形区域 这里的getLeft getTop等等的意思是当前view的最左边 最上边 最右边和最下边 只有点击到这个区域里面才是选中
Rect rect = new Rect((int)addFrameHolder.getState().getLeft(),
(int)addFrameHolder.getState().getTop(),
(int)addFrameHolder.getState().getRight(),
(int)addFrameHolder.getState().getBottom());
if (rect.contains((int) x, (int) y)) {
//如果选中 当前view图层提到最上面
addFrameHolder.getAddWordFrame().bringToFront();
addFrameHolder.getAddWordFrame().setSelect(true);
//记录选中了哪个
AddWordSelectImageCount = i;
LogUtils.e("选中");
break;
}
AddWordSelectImageCount = -1;
LogUtils.e("没选中");
}
}
上述的代码都非常的清晰,那么接下来还有一个难点就是上面的代码已经可以对普通的图片进行平移相关操作了, 那对其他的自定义view呢? 这里我们用到了Canvas类,大家都知道要自定义一个view里面的内容只需要在Ondraw(Canvas canvas)里面画就可以了, 那我们把当前的matrix对象设置到这里面可不可以呢 ,当然是可以的,在我的AddWordFrame类的ondraw里面,看下代码:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.setLayerType(View.LAYER_TYPE_HARDWARE, null);
canvas.concat(matrix);
this.setLayerType(View.LAYER_TYPE_NONE, null);
}
特别注意 ,首先canvas.concat方法, canvas还有canvas.setMatrix()方法, 当然这俩个是有区别的, concat会和之前的matrix进行计算操作,有一种叠加的感觉, 而setMatrix会替换掉之前的matrix。
另外更要注意这俩句:
this.setLayerType(View.LAYER_TYPE_HARDWARE, null);
和this.setLayerType(View.LAYER_TYPE_NONE, null);
如果不加这俩句的话平移的时候大部分几率会出现重影想过, 小伙伴们可以注释掉试试哈。这里我的理解是做了一部缓存, 这个setLayerType一定要慎用! 感兴趣的可以深入的研究它。
这里其实我是对当前view所在的画布(Canvas)进行了平移, 旋转和缩放的动作 , 实际上是画布在动,而不是view在动, 而图片的矩阵缩放是imageview本身在动
如果有小伙伴有类似的需求可以留言或者在我的github上提出来:
源代码github地址: