入坑的路上,你能从本文中接触到以下知识点,共勉:
1、自定义Component实现步骤
2、属性动画
3、矩阵变化
4、Touch事件处理
实现思路:使用matrix变化,应用在canvas上进行pixmap绘制。
具体实现如下:
了解矩阵含义
先看看matrix矩阵是什么样子的:
这里通过他们的名字可以看出,scale是缩放,skew是错切,trans是平移,persp代表透视。
实现代码中,只需改变scale,以及trans参数即可。
矩阵更多详情请参考CSDN博客
一、继承Component
构造方法初始化:
实现接口:
Component.DoubleClickedListener:双击事件
Component.DrawTask:绘制流程
Component.TouchEventListener:触摸事件
public ZoomImage(Context context, AttrSet attrSet) {
super(context, attrSet);
init(context);
}
private void init(Context ct) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPrePoint = new Point();
mPreCtrlPoint = new Point();
mEndPoint = new Point();
mid = new Point();
// 增加touch,双击,绘制事件监听
setTouchEventListener(this);
setDoubleClickedListener(this);
addDrawTask(this);
}
二、双击缩放实现
注意:使用handler更新scale,否则动画监听回调值会超出预期(我理解是:绘制时间包含在动画执行时间内,假如动画执行时间为500ms,绘制一次耗时500ms,则onUpdate()只回调一次或者不回调)。
private synchronized void scaleExecute() {
if (defScale > maxScaleLimit) { //小图默认缩放已经超过3倍,无需继续放大
return;
}
float defS = scale;
if (scale < defScale || scale >= maxScaleLimit) {
scale = defScale;
} else {
if (maxScaleLimit - defScale > 1) {
scale += new BigDecimal((maxScaleLimit - defScale) / 2f).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
} else {
scale += defScale;
}
}
mCurrentMatrix.setMatrix(mBaseMatrix);
final long st = System.currentTimeMillis();
if (av == null) {
av = new AnimatorValue();
av.setDuration(500);
av.setLoopedCount(0);
av.setCurveType(Animator.CurveType.LINEAR);
} else {
if (av.isRunning()) {
av.stop();
}
}
av.setValueUpdateListener((animatorValue, v) -> {
curScl = defS + (scale - defS) * v;
// System.out.println(curScl+ "插值器:" + v + "==" + (System.currentTimeMillis() - st));
// mDrawMatrix.setScale(curScl, curScl);
// updateImageSize(curScl);
// float[] lt = getLeftTopPoint();
// mDrawMatrix.postTranslate(-lt[0], -lt[1]);
// matrix.setMatrix(mDrawMatrix);
// invalidate();
/** 直接invalidate会阻塞ui,导致回调值v异常,或不回调**/
InnerEvent in = InnerEvent.get();
in.object = curScl;
handler.sendEvent(in);
});
av.start();
}
private EventHandler handler = new EventHandler(EventRunner.getMainEventRunner()) {
@Override
protected void processEvent(InnerEvent event) {
curScl = (float) event.object;
mCurrentMatrix.setScale(curScl, curScl);
updateImageSize(curScl);
float[] lt = getLeftTopPoint();
mCurrentMatrix.postTranslate(-lt[0], -lt[1]);
setImageMatrix(mCurrentMatrix);
}
};
// 根据缩放比例计算图片尺寸
private void updateImageSize(float scl) {
imageW = Float.valueOf(imageDefW * scl).intValue();
imageH = Float.valueOf(imageDefH * scl).intValue();
}
// 更新图片矩阵,触发onDraw方法
public void setImageMatrix(Matrix mMatrix) {
if (mMatrix != null && mMatrix.isIdentity()) {
mMatrix = null;
}
if (mMatrix == null && !matrix.isIdentity() ||
mMatrix != null && !mMatrix.equals(matrix)) {
this.matrix.setMatrix(mMatrix);
invalidate();
}
}
三、绘制
@Override
public void onDraw(Component component, Canvas canvas) {
canvas.save();
updateBaseMatrix();
canvas.setMatrix(matrix);
PixelMapHolder holder = new PixelMapHolder(drawable);
canvas.drawPixelMapHolder(holder, 0, 0, mPaint);
holder = null;
canvas.restore();
}
/**
*计算默认参数
*
*/
private void updateBaseMatrix() {
if (drawable == null) {
return;
}
if (!displayRect.isEmpty()) {
// System.out.println("已初始化");
return;
}
initDefaultSize();
int viewWidth = getWidth();
int viewHeight = getHeight();
while (viewWidth == 0 || viewHeight == 0) {
viewWidth = getWidth();
viewHeight = getHeight();
}
final int drawableWidth = imageDefW - getPaddingLeft() - getPaddingRight();
final int drawableHeight = imageDefH - getPaddingTop() - getPaddingBottom();
displayRect.clear();
displayRect.fuse(0, 0, viewWidth, viewHeight);
mBaseMatrix.reset();
final float widthScale = new BigDecimal(1f * viewWidth / (drawableWidth)).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
final float heightScale = new BigDecimal(1f * viewHeight / (drawableHeight)).setScale(3, BigDecimal.ROUND_HALF_UP).floatValue();
defScale = Math.min(widthScale, heightScale);
maxScaleLimit = Math.max(widthScale, heightScale);
scale = defScale;
updateImageSize(defScale);
mBaseMatrix.postScale(defScale, defScale);
mBaseMatrix.postTranslate((viewWidth - drawableWidth * defScale) / 2.0F,
(viewHeight - drawableHeight * defScale) / 2.0F);
setImageMatrix(mBaseMatrix);
}
private void initDefaultSize() {
imageDefW = drawable.getImageInfo().size.width;
imageDefH = drawable.getImageInfo().size.height;
imageW = imageDefW;
imageH = imageDefH;
}
对外暴露方法:
设置图片资源。
public void setPixelMap(PixelMap pixelMap) {
this.drawable = pixelMap;
invalidate();
}
四、Touch事件处理
1、默认展示时,未缩放,不支持拖拽;
2、down:判断触摸位置是否在图片上,否则mode不处理;
坐标系中,利用向量判断是否超过边界:
理解上图,请移步,复习高中向量
【无真机,多指触摸缩放未完善,有需要请自行实现】
@Override
public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
switch (touchEvent.getAction()) {
case TouchEvent.PRIMARY_POINT_DOWN: {
//获取点信息
MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
mPrePoint.modify(point.getX(), point.getY());
if (isInMatrix() != null) {
mode = DRAG;
//PRIMARY_POINT_DOWN 一定要返回true
System.out.println("down:::" + mPrePoint.toString() + "===" + mPreCtrlPoint.toString());
} else {
System.out.println("触摸位置超出图片边界");
}
return true;
}
case TouchEvent.OTHER_POINT_DOWN:
mode = ZOOM;
oldDist = spacing(touchEvent);
oldRotation = rotation(touchEvent);
midPoint(mid, touchEvent);
break;
case TouchEvent.PRIMARY_POINT_UP:
MmiPoint point_up = touchEvent.getPointerPosition(touchEvent.getIndex());
mEndPoint.modify(point_up.getX(), point_up.getY());
mode = NONE;
return false;
case TouchEvent.POINT_MOVE: {
if (mode == ZOOM) {
float rotation = rotation(touchEvent) - oldRotation;
float newDist = spacing(touchEvent);
float scale = newDist / oldDist;
mCurrentMatrix.postScale(scale, scale, mid.getPointX(), mid.getPointY());// 縮放
mCurrentMatrix.postRotate(rotation, mid.getPointX(), mid.getPointY());// 旋轉
setImageMatrix(mCurrentMatrix);
} else if (mode == DRAG) {
if (scale <= defScale) {
System.out.println("未缩放无法拖动");
return false;
}
mCurrentMatrix.setMatrix(matrix);
MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
mPreCtrlPoint.position[0] = (point.getX() - mPrePoint.position[0]);
mPreCtrlPoint.position[1] = (point.getY() - mPrePoint.position[1]);
if (Math.sqrt(mPreCtrlPoint.getPointX() * mPreCtrlPoint.getPointX() + mPreCtrlPoint.getPointY() * mPreCtrlPoint.getPointY()) < 8) {
return false;
}
handleDrag();
}
break;
}
case TouchEvent.OTHER_POINT_UP:
case TouchEvent.CANCEL:
mode = NONE;
break;
default:
break;
}
return true;
}
// 判断点击位置,是否超出图片边界
private float[] isInMatrix() {
float[] f = matrix.getData();
int mw = imageDefW;
int mh = imageDefH;
// 图片4个顶点的坐标:左上,右上,左下,右下
float x1 = f[0] * 0 + f[1] * 0 + f[2];
float y1 = f[3] * 0 + f[4] * 0 + f[5];
float x2 = f[0] * mw + f[1] * 0 + f[2];
float y2 = f[3] * mw + f[4] * 0 + f[5];
float x3 = f[0] * 0 + f[1] * mh + f[2];
float y3 = f[3] * 0 + f[4] * mh + f[5];
float x4 = f[0] * mw + f[1] * mh + f[2];
float y4 = f[3] * mw + f[4] * mh + f[5];
float x = mPrePoint.getPointX();
float y = mPrePoint.getPointY();
float[] v1 = new float[]{x1 - x, y1 - y};
float[] v2 = new float[]{x2 - x, y2 - y};
float[] v3 = new float[]{x3 - x, y3 - y};
float[] v4 = new float[]{x4 - x, y4 - y};
if ((v1[0] * v2[1] - v2[0] * v1[1]) > 0
&& (v2[0] * v4[1] - v4[0] * v2[1]) > 0
&& (v4[0] * v3[1] - v3[0] * v4[1]) > 0
&& (v3[0] * v1[1] - v1[0] * v3[1]) > 0) {
return new float[]{x1, y1, x2, y2, x3, y3, x4, y4};
}
return null;
}
// 处理拖动边界
private synchronized void handleDrag(DragInfo dragInfo) {
float x = matrix.getTranslateX() + mPreCtrlPoint.getPointX();
float y = matrix.getTranslateY() + mPreCtrlPoint.getPointY();
float trans_x, trans_y;
// 计算x方向拖动距离
if (imageW <= getWidth()) {
trans_x = 0;
} else if (x > 0) {
trans_x = -matrix.getTranslateX();
} else if (x < -(imageW - getWidth())) {
trans_x = -(imageW - getWidth()) - matrix.getTranslateX();
} else {
trans_x = mPreCtrlPoint.getPointX();
}
// 计算y方向拖动距离
if (imageH <= getHeight()) {
trans_y = 0;
} else if (y > 0) {
trans_y = -matrix.getTranslateY();
} else if (y < -(imageH - getHeight())) {
trans_y = -(imageH - getHeight()) - matrix.getTranslateY();
} else {
trans_y = mPreCtrlPoint.getPointY();
}
mCurrentMatrix.postTranslate(trans_x, trans_y);
setImageMatrix(mCurrentMatrix);
}
五、使用方式
xml中:
不知道如何自定义属性,暂时只能在java中调用setPixmap设置图片。
源码:刚入坑,建议自行编码尝试,有问题请留言一起讨论。加油,干就完事!
参考代码:
链接: https://pan.baidu.com/s/1CQYi3Bqa0NGMgK0mTRm6bQ
提取码: yxjb
效果图:
链接: https://pan.baidu.com/s/1Sm6vRvlW2mluwzB2F5Kw3g
提取码: kpg7