使用这种本控件,控件内部可以根据你的需要生成不同大小的Unit矩阵,并赋予每个矩阵单元一个tag,在移动到边界时将会回收再利用,tag也会根据移动时的情况更改值,你可以在tag被更新时加载内存或者外存的数据,刷新到Unit中,形成地图加载器或者Excel类软件,而且不会像之前用ImageView组成的矩阵那样,内部分辨率被固定住了。
可以看效果,视频中格子只有20*20个,但是可以通过Unit到边界之后回收复用的办法,形成图像上的无限眼延伸效果,非常适合加载地图类数据、用来做白板类APP的画布或者做Excel类应用:
控件代码:
package com.testcanvaszoom3; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.PointF; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; import cjz.project.whiteboardlab.R; /** * Created by cjz on 2019/10/28. */ public class DrawView extends View{ private Bitmap testBitmap = null; private final int MATRIX_LEN = 20; private Unit unitMatrix[][] = new Unit[MATRIX_LEN][MATRIX_LEN]; private PointF currentCenter = new PointF(); private PointF prevCurrentCenter = null; private float prevDistance = Float.MIN_VALUE; /**将触摸点的坐标平均化**/ private float avergeX = 0, avergeY = 0; private int prevPointCount = 0; /*** 触摸点点距队列**/ private Queue<Float> touchDistanceQueue = new LinkedBlockingQueue<>(); private float totalScale = 1f; private int mWidth, mHeight; public DrawView(Context context) { super(context); init(); } public DrawView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public DrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init(){ if(testBitmap == null){ testBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg); // testBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_recongize_circle); for(int i = 0; i < MATRIX_LEN; i++){ unitMatrix[i] = new Unit[MATRIX_LEN]; for(int j = 0; j < MATRIX_LEN; j++){ unitMatrix[i][j] = new Unit(160, 60, ""); unitMatrix[i][j].setTag(new int[]{i, j}); unitMatrix[i][j].translate(i * 160, j * 60); } } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); mWidth = width; mHeight = height; } /**matrix: * [MSCALE_X, MSKEW_X, MTRANS_X, * MSKEW_Y, MSCALE_Y, MTRANS_Y, * MPRESP_0, MPRESP_1, MPRESP_2]**/ private void translate(float dx, float dy){ for(int i = 0; i < MATRIX_LEN; i++){ for(int j = 0; j < MATRIX_LEN; j++){ unitMatrix[i][j].translate(dx, dy); } } float values[] = new float[9]; float valuesCompare[] = new float[9]; //x轴,y轴要分开两个循环处理,否则会引发混乱,而且以左上角为缩放中心缩放的,不像View是中心为缩放中心 for (int yPos = 0; yPos < MATRIX_LEN; yPos++) { for (int xPos = 0; xPos < MATRIX_LEN; xPos++) { //单元格溢出到了屏幕左边,移动到当前对应行最右边 Unit unit = unitMatrix[xPos][yPos]; unit.getMatrix().getValues(values); //移除去的部分添加到未显示的部分的末尾, if(values[2] + unit.getWidth() * values[0] < 0 && unit.getWidth() > 0){ if(xPos == 0){ // unitMatrix[MATRIX_LEN - 1][yPos].getMatrix().getValues(valuesCompare); values[2] = valuesCompare[2] + unitMatrix[MATRIX_LEN - 1][yPos].getWidth() * valuesCompare[0]; unit.getMatrix().setValues(values); int targetPos[] = unitMatrix[MATRIX_LEN - 1][yPos].getTag(); unit.setTag(new int[]{targetPos[0] + 1, targetPos[1]}); //重设单元格标记 for (int i = xPos; i < MATRIX_LEN - 1; i++) { unitMatrix[i][yPos] = unitMatrix[i + 1][yPos]; } unitMatrix[MATRIX_LEN - 1][yPos] = unit; } } else if(values[2] > mWidth) { if (xPos == MATRIX_LEN - 1) { //因为初始化时显示的Unit是最左上角的Unit,有可能导致非最后一列的内容被平移,这违反自动补充的逻辑,会出bug,所以要加判断 //重设位置(设置和最后一个的左上角坐标直接重合(setx用于设定左上角坐标),再减去控件宽度*缩放量使得目标控件右上角和最后一个控件左上角对齐) unitMatrix[0][yPos].getMatrix().getValues(valuesCompare); values[2] = valuesCompare[2] - unitMatrix[0][yPos].getWidth() * valuesCompare[0]; unit.getMatrix().setValues(values); int targetPos[] = unitMatrix[0][yPos].getTag(); unit.setTag(new int[]{targetPos[0] - 1, targetPos[1]}); //重设单元格标记 Unit temp = unitMatrix[MATRIX_LEN - 1][yPos]; for (int i = MATRIX_LEN - 1; i > 0; i--) { unitMatrix[i][yPos] = unitMatrix[i - 1][yPos]; } unitMatrix[0][yPos] = temp; } } } } for (int yPos = 0; yPos < MATRIX_LEN; yPos++) { for (int xPos = 0; xPos < MATRIX_LEN; xPos++) { Unit unit = unitMatrix[xPos][yPos]; unit.getMatrix().getValues(values); if(values[5] + unit.getHeight() * values[4] < 0 && unit.getHeight() > 0){ if (yPos == 0) { //重设位置 unitMatrix[xPos][MATRIX_LEN - 1].getMatrix().getValues(valuesCompare); values[5] = valuesCompare[5] + unitMatrix[xPos][MATRIX_LEN - 1].getHeight() * valuesCompare[4]; unit.getMatrix().setValues(values); int targetPos[] = unitMatrix[xPos][MATRIX_LEN - 1].getTag(); unit.setTag(new int[]{targetPos[0], targetPos[1] + 1}); //重设单元格标记 for (int i = yPos; i < MATRIX_LEN - 1; i++) { unitMatrix[xPos][i] = unitMatrix[xPos][i + 1]; } unitMatrix[xPos][MATRIX_LEN - 1] = unit; } } else if(values[5] > mHeight){ if (yPos == MATRIX_LEN - 1) { //重设位置(设置和最后一个的左上角坐标直接重合(setx用于设定左上角坐标),再减去控件宽度*缩放量使得目标控件右上角和最后一个控件左上角对齐) unitMatrix[xPos][0].getMatrix().getValues(valuesCompare); values[5] = valuesCompare[5] - unitMatrix[xPos][0].getHeight() * valuesCompare[4]; unit.getMatrix().setValues(values); int targetPos[] = unitMatrix[xPos][0].getTag(); unit.setTag(new int[]{targetPos[0], targetPos[1] - 1}); //重设单元格标记 Unit temp = unitMatrix[xPos][MATRIX_LEN - 1]; for (int i = MATRIX_LEN - 1; i > 0; i--) { unitMatrix[xPos][i] = unitMatrix[xPos][i - 1]; } unitMatrix[xPos][0] = temp; } } } } } private void scale(float scale, float sx, float sy){ for(int i = 0; i < MATRIX_LEN; i++){ for(int j = 0; j < MATRIX_LEN; j++){ unitMatrix[i][j].scale(scale, sx, sy); } } } /**移动缩放,触发第一次移动缩放时,MapView会把表面图层切图并放入各Unit中**/ private void transAndScale(MotionEvent event){ switch (event.getAction()) { case MotionEvent.ACTION_DOWN: prevDistance = 0; prevPointCount = event.getPointerCount(); //算出移动中心坐标、点间距离 for(int i = 0; i < event.getPointerCount(); i++){ avergeX += event.getX(i); avergeY += event.getY(i); if(i + 1 < event.getPointerCount()){ prevDistance += Math.sqrt(Math.pow(event.getX(i + 1) - event.getX(i), 2) + Math.pow(event.getY(i + 1) - event.getY(i), 2)); } } avergeX /= event.getPointerCount(); avergeY /= event.getPointerCount(); if(prevCurrentCenter == null){ prevCurrentCenter = new PointF(avergeX, avergeY); } else { prevCurrentCenter.set(avergeX, avergeY); } break; case MotionEvent.ACTION_MOVE: avergeX = 0; avergeY = 0; float nowDistance = 0; //算出移动中心坐标、点间距离 for(int i = 0; i < event.getPointerCount(); i++){ avergeX += event.getX(i); avergeY += event.getY(i); if(i + 1 < event.getPointerCount()){ nowDistance += Math.sqrt(Math.pow(event.getX(i + 1) - event.getX(i), 2) + Math.pow(event.getY(i + 1) - event.getY(i), 2)); } } //现在的点间距离 除以 上次点间距离 这次得到缩放比例 avergeX /= event.getPointerCount(); avergeY /= event.getPointerCount(); if((prevPointCount != event.getPointerCount()) || event.getPointerCount() <= 1 || prevPointCount <= 1){ //触摸点数突然改变 或者 触摸点不超过2,不允许缩放 prevDistance = nowDistance = 0; } //如果缩放数据有效,则进行平均平滑化并且进行缩放 if(prevDistance > 0 && nowDistance > 0){ touchDistanceQueue.add(nowDistance / prevDistance); if(touchDistanceQueue.size() >= 6) { Float point[] = new Float[touchDistanceQueue.size()]; touchDistanceQueue.toArray(point); float avergDistance = 0; for(int i = 0; i < point.length; i++){ avergDistance += point[i]; } avergDistance /= point.length; double scale = Math.sqrt(avergDistance); scale((float) scale, avergeX, avergeY); totalScale *= scale; ToastUtil.showToast(String.format("缩放量:%.2f", totalScale * 100)); while(touchDistanceQueue.size() > 6){ touchDistanceQueue.poll(); } } } prevPointCount = event.getPointerCount(); prevDistance = nowDistance; //当前坐标 - 上次坐标 = 偏移值,然后进行位置偏移 if(prevCurrentCenter == null) { prevCurrentCenter = new PointF(avergeX, avergeY); } else { translate(avergeX - prevCurrentCenter.x, avergeY - prevCurrentCenter.y); prevCurrentCenter.set(avergeX, avergeY); } break; case MotionEvent.ACTION_UP: //抬起,清理干净数据 avergeX = 0; avergeY = 0; touchDistanceQueue.clear(); break; } } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(getClass().getName(), event.toString()); transAndScale(event); invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { for(int i = 0; i < MATRIX_LEN; i++){ for(int j = 0; j < MATRIX_LEN; j++){ unitMatrix[i][j].draw(canvas); } } } }Unit代码:
package com.testcanvaszoom3; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; /** * Created by cjz on 2019/10/28. */ /**不够内存的时候要释放这些Unit里面的位图,要用的时候再从外面读写**/ public class Unit { /**图块根目录**/ private String rootPath; /**本Unit图块目录**/ private String unitDataPath; /**Debug时显示tag坐标**/ private Paint paintPen; /**是否要显示tag坐标**/ private boolean isDebug = true; /**matrix: * [MSCALE_X, MSKEW_X, MTRANS_X, * MSKEW_Y, MSCALE_Y, MTRANS_Y, * MPRESP_0, MPRESP_1, MPRESP_2]**/ /**单元位置和缩放大小的控制矩阵**/ private Matrix matrix = new Matrix(); /**matrix映射**/ private float[] matrixVal = new float[9]; /**这次的试验品可以传入位图进行绘制,而且是共享一张位图,这样平铺完之后非常节约内存**/ private Bitmap bitmap = null; private int width, height; private int[] tag = new int[2]; public Unit(int width, int height, String rootPath) { this.width = width; this.height = height; this.rootPath = rootPath; if (isDebug) { paintPen = new Paint(); paintPen.setStrokeWidth(2f); paintPen.setStyle(Paint.Style.STROKE); paintPen.setColor(Color.RED); paintPen.setTextSize(16f); paintPen.setAntiAlias(true); } } /**绘制**/ public void draw(Canvas canvas){ if(bitmap != null){ matrix.getValues(matrixVal); if(matrixVal[2] + width * matrixVal[0] > 0 && matrixVal[2] < canvas.getWidth() && matrixVal[5] + height * matrixVal[4] > 0 && matrixVal[5] < canvas.getHeight()){ //可见区域之外不用渲染 canvas.drawBitmap(bitmap, matrix, null); } } if (isDebug) { float[] matrixVal = new float[9]; matrix.getValues(matrixVal); canvas.drawText(String.format("%d, %d", tag[0], tag[1]), matrixVal[2] + 50 * matrixVal[0], matrixVal[5] + 40 * matrixVal[4], paintPen); paintPen.setStrokeWidth(2 * matrixVal[0]); paintPen.setTextSize(16 * matrixVal[0]); canvas.drawRect(matrixVal[2], matrixVal[5], matrixVal[2] + width * matrixVal[0], matrixVal[5] + height * matrixVal[4], paintPen); } } public void translate(float dx, float dy){ matrix.postTranslate(dx, dy); } public void scale(float scale, float px, float py){ matrix.postScale(scale, scale, px, py); } public Matrix getMatrix() { return matrix; } public Bitmap getBitmap() { return bitmap; } public int[] getTag() { return tag; } public void setTag(int[] tag) { this.tag = tag; } public int getWidth() { return width; } public int getHeight() { return height; } }效果视频,我没有限制缩放程度,所以缩小到一定程度之后再拖动,可以直观地感受到Unit在复用:
链接: https://pan.baidu.com/s/14oa83n3ErRzkR8Au3-q8kw 提取码: 26wh 复制这段内容后打开百度网盘手机App,操作更方便哦