由于用ImageView开销太大了,所以我准备用单个View继承出一个子类,直接重写onDraw来显示图块,这样还可以共享同一块位图进行平铺操作。打算未来用于白板上作为移动缩放的辅助类,提高移动缩放的效率,或者直接就取代普通的位图Canvas+SurfaceView的方式做白板程序。在每放大百分之10之后就加载分辨率更高的栅格化图片,更好的模仿地图的作用。
代码如下:
地图控件View:DrawView:
package com.testcanvaszoom;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.Toast;
import java.io.File;
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();
unitMatrix[i][j].setTag(new int[]{i, j});
unitMatrix[i][j].setBitmap(testBitmap);
unitMatrix[i][j].translate(i * testBitmap.getWidth(), j * testBitmap.getHeight());
}
}
}
}
@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.getBitmap().getWidth() * values[0] < 0 && unit.getBitmap().getWidth() > 0){
if(xPos == 0){
//
unitMatrix[MATRIX_LEN - 1][yPos].getMatrix().getValues(valuesCompare);
values[2] = valuesCompare[2] + unitMatrix[MATRIX_LEN - 1][yPos].getBitmap().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].getBitmap().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.getBitmap().getHeight() * values[4] < 0 && unit.getBitmap().getHeight() > 0){
if (yPos == 0) {
//重设位置
unitMatrix[xPos][MATRIX_LEN - 1].getMatrix().getValues(valuesCompare);
values[5] = valuesCompare[5] + unitMatrix[xPos][MATRIX_LEN - 1].getBitmap().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].getBitmap().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);
}
}
}
}
图块(未完成):
package com.testcanvaszoom;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
/**
* Created by cjz on 2019/10/28.
*/
public class Unit {
/**Debug时显示tag坐标**/
private Paint paintPen;
/**是否要显示tag坐标**/
private boolean isDebug = true;
/**不够内存的时候要释放这些Unit里面的位图,要用的时候再从外面读写**/
/**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];
/**为未来留下的一个功能,每次调用Draw的时候才读入图块(如果位置在屏幕外面则直接不加载),然后绘制好之后立即释放内存,从而使得巨图加载的时候非常节约内存**/
private String unitPath;
/**这次的试验品可以传入位图进行绘制,而且是共享一张位图,这样平铺完之后非常节约内存**/
private Bitmap bitmap = null;
private int width, height;
private int[] tag = new int[2];
public Unit() {
if (isDebug) {
paintPen = new Paint();
paintPen.setStrokeWidth(4f);
paintPen.setStyle(Paint.Style.FILL);
paintPen.setColor(Color.RED);
paintPen.setTextSize(16f);
paintPen.setAntiAlias(true);
}
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
width = bitmap.getWidth();
height = bitmap.getHeight();
}
/**绘制**/
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], matrixVal[5], 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;
}
}
运作效果: