使用這種本控件,控件內部可以根據你的需要生成不同大小的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,操作更方便哦