編輯:關於Android編程
先來看看要實現的效果圖
在講解中,需要大家提前了解一些關於圖片繪制的原理的相關知識。
關於實現的流程
1、自定義View
2、獲得操作圖片的Bitmap
3、復寫View
的onTouchEvent()
方法中的ACTION_DOWN
,ACTION_POINTER_DOWN
,ACTION_MOVE
,ACTION_POINTER_UP
以及ACTION_UP
事件。
4、定義相應圖片變化的Matrix
矩陣,通過手勢操作的變化來設置相應的Matrix
。
5、完成最終的Matrix
設置時,通過invalidate()
方法重新繪制頁面。
那麼接下來我們根據以上流程一步一步實現代碼。
代碼演示
/** * 作者:ZhouYou * 日期:2016/8/23. */ public class TouchImageView extends View { // 繪制圖片的邊框 private Paint paintEdge; // 繪制圖片的矩陣 private Matrix matrix = new Matrix(); // 手指按下時圖片的矩陣 private Matrix downMatrix = new Matrix(); // 手指移動時圖片的矩陣 private Matrix moveMatrix = new Matrix(); // 資源圖片的位圖 private Bitmap srcImage; // 多點觸屏時的中心點 private PointF midPoint = new PointF(); // 觸控模式 private int mode; private static final int NONE = 0; // 無模式 private static final int TRANS = 1; // 拖拽模式 private static final int ZOOM = 2; // 縮放模式 // 是否超過邊界 private boolean withinBorder; public TouchImageView(Context context) { this(context, null); } public TouchImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TouchImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paintEdge = new Paint(); paintEdge.setColor(Color.BLACK); paintEdge.setAlpha(170); paintEdge.setAntiAlias(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); srcImage = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_avatar_1); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float[] points = getBitmapPoints(srcImage, matrix); float x1 = points[0]; float y1 = points[1]; float x2 = points[2]; float y2 = points[3]; float x3 = points[4]; float y3 = points[5]; float x4 = points[6]; float y4 = points[7]; // 畫邊框 canvas.drawLine(x1, y1, x2, y2, paintEdge); canvas.drawLine(x2, y2, x4, y4, paintEdge); canvas.drawLine(x4, y4, x3, y3, paintEdge); canvas.drawLine(x3, y3, x1, y1, paintEdge); // 畫圖片 canvas.drawBitmap(srcImage, matrix, null); } // 手指按下屏幕的X坐標 private float downX; // 手指按下屏幕的Y坐標 private float downY; // 手指之間的初始距離 private float oldDistance; // 手指之間的初始角度 private float oldRotation; @Override public boolean onTouchEvent(MotionEvent event) { int action = MotionEventCompat.getActionMasked(event); switch (action) { case MotionEvent.ACTION_DOWN: mode = TRANS; downX = event.getX(); downY = event.getY(); downMatrix.set(matrix); break; case MotionEvent.ACTION_POINTER_DOWN: // 多點觸控 mode = ZOOM; oldDistance = getSpaceDistance(event); oldRotation = getSpaceRotation(event); downMatrix.set(matrix); midPoint = getMidPoint(event); break; case MotionEvent.ACTION_MOVE: // 縮放 if (mode == ZOOM) { moveMatrix.set(downMatrix); float deltaRotation = getSpaceRotation(event) - oldRotation; float scale = getSpaceDistance(event) / oldDistance; moveMatrix.postScale(scale, scale, midPoint.x, midPoint.y); moveMatrix.postRotate(deltaRotation, midPoint.x, midPoint.y); withinBorder = getMatrixBorderCheck(srcImage, event.getX(), event.getY()); if (withinBorder) { matrix.set(moveMatrix); invalidate(); } } // 平移 else if (mode == TRANS) { moveMatrix.set(downMatrix); moveMatrix.postTranslate(event.getX() - downX, event.getY() - downY); withinBorder = getMatrixBorderCheck(srcImage, event.getX(), event.getY()); if (withinBorder) { matrix.set(moveMatrix); invalidate(); } } break; case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: mode = NONE; break; default: break; } return true; } /** * 獲取手指的旋轉角度 * * @param event * @return */ private float getSpaceRotation(MotionEvent event) { double deltaX = event.getX(0) - event.getX(1); double deltaY = event.getY(0) - event.getY(1); double radians = Math.atan2(deltaY, deltaX); return (float) Math.toDegrees(radians); } /** * 獲取手指間的距離 * * @param event * @return */ private float getSpaceDistance(MotionEvent event) { float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); return (float) Math.sqrt(x * x + y * y); } /** * 獲取手勢中心點 * * @param event */ private PointF getMidPoint(MotionEvent event) { PointF point = new PointF(); float x = event.getX(0) + event.getX(1); float y = event.getY(0) + event.getY(1); point.set(x / 2, y / 2); return point; } /** * 將matrix的點映射成坐標點 * * @return */ protected float[] getBitmapPoints(Bitmap bitmap, Matrix matrix) { float[] dst = new float[8]; float[] src = new float[]{ 0, 0, bitmap.getWidth(), 0, 0, bitmap.getHeight(), bitmap.getWidth(), bitmap.getHeight() }; matrix.mapPoints(dst, src); return dst; } /** * 檢查邊界 * * @param x * @param y * @return true - 在邊界內 | false - 超出邊界 */ private boolean getMatrixBorderCheck(Bitmap bitmap, float x, float y) { if (bitmap == null) return false; float[] points = getBitmapPoints(bitmap, moveMatrix); float x1 = points[0]; float y1 = points[1]; float x2 = points[2]; float y2 = points[3]; float x3 = points[4]; float y3 = points[5]; float x4 = points[6]; float y4 = points[7]; float edge = (float) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); if ((2 + Math.sqrt(2)) * edge >= Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2)) + Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y - y2, 2)) + Math.sqrt(Math.pow(x - x3, 2) + Math.pow(y - y3, 2)) + Math.sqrt(Math.pow(x - x4, 2) + Math.pow(y - y4, 2))) { return true; } return false; } }
我已經在代碼中針對可能遇到的問題做了詳細的注釋。
1. Matrix
// 繪制圖片的矩陣 private Matrix matrix = new Matrix(); // 手指按下時圖片的矩陣 private Matrix downMatrix = new Matrix(); // 手指移動時圖片的矩陣 private Matrix moveMatrix = new Matrix();
首先我定義了三個Matrix
變量,目的在於通過不同手勢的操控圖片的Matrix
最終由繪制圖片的Matrix
所接收,因此需要在不同的操作中使用不同的Matrix
進行圖形變換的數據傳遞,從而在渲染頁面的時候將最終的Matrix
再傳遞回繪圖的Matrix
。
2. PointF
// 多點觸屏時的中心點 private PointF midPoint = new PointF();
因為如果是針對圖片的旋轉和放大操作,需要通過兩個手指進行控制,因此我們需要知道在多個手指觸摸屏幕時的中心點坐標。
3. 觸控模式
// 觸控模式 private int mode; private static final int NONE = 0; // 無模式 private static final int TRANS = 1; // 拖拽模式 private static final int ZOOM = 2; // 縮放模式
在onTouchEvent()
事件中,會根據不同的事件變換觸控的模式,從而進行不同圖片變換的操作。
4. onTouchEvent()
首先,我們是自定義的View
,因此如果要對該事件進行消費的話,需要將返回值設置為true
。
(1)ACTION_DOWN - 該事件是單點觸屏的事件,也就是說如果一個手指按下屏幕的時候就會回調這個事件。那麼我們在該事件中就將觸控模式設置為拖拽模式(TRANS),記錄下按下屏幕的xy坐標,並在這個事件中將繪圖的Matrix復制給按下屏幕的Matrix。
case MotionEvent.ACTION_DOWN: mode = TRANS; downX = event.getX(); downY = event.getY(); downMatrix.set(matrix); break;
(2)ACTION_POINTER_DOWN - 這個事件發生在超過一個手指觸摸屏幕的時候。我們在這個事件中即可針對多點觸屏的操作進行初始化設置。在該事件中,我們將觸控模式重新設置為(ZOOM),初始化兩指之間觸摸屏幕的距離以及兩指之間的旋轉角度,初始化兩指之間的中心點坐標。最後把繪圖的Matrix復制給按下屏幕的Matrix。
case MotionEvent.ACTION_POINTER_DOWN: // 多點觸控 mode = ZOOM; oldDistance = getSpaceDistance(event); oldRotation = getSpaceRotation(event); midPoint = getMidPoint(event); downMatrix.set(matrix); break;
(3)ACTION_MOVE - 到了移動的事件中,根據之前的觸控模式進行判斷。首先,將按下事件的Matrix復制給移動事件的Matrix。如果是(ZOOM)模式,我們將會根據事件獲得手指旋轉角度的差值,以及手指之間距離的差值。根據這兩個差值,以及在ACTION_DOWN事件中獲得的中點坐標,我們即可設置MOVE事件的縮放和旋轉。(TRANS)模式也是如此。最後通過獲取圖片變換的邊界值來判斷是否進行繪圖渲染。
case MotionEvent.ACTION_MOVE: // 縮放 if (mode == ZOOM) { moveMatrix.set(downMatrix); float deltaRotation = getSpaceRotation(event) - oldRotation; float scale = getSpaceDistance(event) / oldDistance; moveMatrix.postScale(scale, scale, midPoint.x, midPoint.y); moveMatrix.postRotate(deltaRotation, midPoint.x, midPoint.y); withinBorder = getMatrixBorderCheck(srcImage, event.getX(), event.getY()); if (withinBorder) { matrix.set(moveMatrix); invalidate(); } } // 平移 else if (mode == TRANS) { moveMatrix.set(downMatrix); moveMatrix.postTranslate(event.getX() - downX, event.getY() - downY); withinBorder = getMatrixBorderCheck(srcImage, event.getX(), event.getY()); if (withinBorder) { matrix.set(moveMatrix); invalidate(); } } break;
(4)ACTION_POINTER_UP和ACTION_UP - 在這兩個事件中,重新將觸屏的模式設置會NONE。
5. 邊界判斷
以下即為邊界判斷的邏輯是針對正方形的圖片來說的。首先通過原圖片相對自己四個坐標映射成為Matrix對應屏幕的點坐標。通過得到4個點的坐標,我們即可根據手指觸摸圖片時的坐標與圖片的4個點坐標進行關聯。
邊界判斷的邏輯是手指觸摸圖片的點到4個頂點的距離之和如果小於(2+根號2倍)的斜邊長度,即視為不超過邊界。
/** * 將matrix的點映射成坐標點 * * @return */ protected float[] getBitmapPoints(Bitmap bitmap, Matrix matrix) { float[] dst = new float[8]; float[] src = new float[]{ 0, 0, bitmap.getWidth(), 0, 0, bitmap.getHeight(), bitmap.getWidth(), bitmap.getHeight() }; matrix.mapPoints(dst, src); return dst; } /** * 檢查邊界 * * @param x * @param y * @return true - 在邊界內 | false - 超出邊界 */ private boolean getMatrixBorderCheck(Bitmap bitmap, float x, float y) { if (bitmap == null) return false; float[] points = getBitmapPoints(bitmap, moveMatrix); float x1 = points[0]; float y1 = points[1]; float x2 = points[2]; float y2 = points[3]; float x3 = points[4]; float y3 = points[5]; float x4 = points[6]; float y4 = points[7]; float edge = (float) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); if ((2 + Math.sqrt(2)) * edge >= Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2)) + Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y - y2, 2)) + Math.sqrt(Math.pow(x - x3, 2) + Math.pow(y - y3, 2)) + Math.sqrt(Math.pow(x - x4, 2) + Math.pow(y - y4, 2))) { return true; } return false; }
總結
好了,本文的內容到這就結束了,完成了以上的步驟,即可完成針對圖片在屏幕上的放大、平移和旋轉的操作。是不是還是很簡單的。有興趣的可以自己動手操作起來,希望這篇文章對大家的學習和工作能有所幫助,如果有疑問可以留言交流,謝謝大家對本站的支持。
問題:如果圖片很大,全部載入內存,而顯示屏又不大,那麼再大的圖片也不會提高視覺效果的,而且會消耗無謂的內存。 解決辦法就是根據實際需要多大的圖片,然後動態計算應該載入多大
今天被同時問到java/android 使用swig編譯c/c++ 代碼類型轉換。想起找個中文版swig看一下,雖然找到了,但也是基本是英文。中文版首頁的:http://
由於gif圖太大的原因,我將圖放在了github,如果博客中顯示不出來圖,傳送門 由於我是事先寫在md上的,導致代碼的可讀性差,大家將就著看吧。 1. 前言 在
如圖所示,有時候為了布局美觀,在搜索時沒有搜索按鈕,而是調用軟件盤上的按鈕。調用的實現只需要在XML在輸入框中加入android:imeOptions=actionSe