日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

图片的多点触控缩放与移动

發(fā)布時間:2023/12/29 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 图片的多点触控缩放与移动 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

整理自 鴻洋大神的慕課網(wǎng)視頻

加了很多自己理解的注注釋





package MyView; import android.content.Context; import android.graphics.Matrix; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; import android.view.ViewTreeObserver; import android.widget.ImageView; /** * 圖片預(yù)覽與多點觸控ImageView * <p> * <p> * Created by wjc on 2017/3/8. */ public class ZoomImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener, ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener {/** * 因為縮放只需在加載圖片完成后進行一次,所以需要設(shè)置一個標記 */ private boolean mOnce; /** * 初始化時縮放的值,也就是根據(jù)圖片于控件寬高計算得到的縮放比例 */ private float mInitScale; /** * 雙擊放大的的scale比例 */ private float mMidScale; /** * 允許放大的最大縮放比例 */ private float mMaxScale; /** * 縮放矩陣 */ private Matrix mScaleMatrix; /** * 可以獲取到當前用戶手勢的 縮放比例 */ private ScaleGestureDetector mScaleGestureDetector; //------------------自由移動所需變量 /** * 記錄上一次多點觸控的點的數(shù)量,因為4個手指變?yōu)?個手指的時候, * 觸控的中心可能就瞬間發(fā)生很大的跳躍變化,如果只是根據(jù)觸控中心位置實時移動圖片,用戶體驗差 */ private int mLastPointerCount; private float mLastX; //記錄上次多指的中心點 private float mlastY; private int mTouchSlop; private boolean isCanDrag; private RectF matrixRectF; private boolean isCheckLeftAndRight; private boolean isCheckTopAndBottom; //-----------------雙擊放大與縮小 private GestureDetector mGestureDetector; private boolean isAutoScale; //當前是否處在雙擊后的縮放過程當中 public ZoomImageView(Context context) {this(context, null); }public ZoomImageView(Context context, AttributeSet attrs) {this(context, attrs, 0); }public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr); mScaleMatrix = new Matrix(); setScaleType(ScaleType.MATRIX);//覆蓋xml的scaleType 因為這里的縮放是建立在ScaleType.MATRIX的 mScaleGestureDetector = new ScaleGestureDetector(context, this); setOnTouchListener(this); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {/** * * 雙擊的圖片放大縮小,注意邊界處理 * 如果雙擊直接放大到目標大小,中間是沒有變化過程的,會非常突兀,可以使用runnable,分成多次,延時post * */ @Override public boolean onDoubleTap(MotionEvent e) {/** * 當前仍在縮放 就直接return; */ if (isAutoScale) {return true; }float x = e.getX(); float y = e.getY(); float currentScale = getScale(); /** * 兩個特殊情況: * 當前為 mMidScale 會縮小為mInitScale * 當前為 mInitScale 會放大為mMidScale * 也就是說 不會出現(xiàn)雙擊后無變化的情況。mTargetScale不會等于getScale; */ if (currentScale < mMidScale) { // mScaleMatrix.postScale(mMidScale / currentScale, mMidScale / currentScale, x, y); //這是直接縮放,不可取 postDelayed(new AutoScaleRunnable(mMidScale, x, y), 16); isAutoScale = true; } else {// mScaleMatrix.postScale(mInitScale / currentScale, mInitScale / currentScale, x, y); // postDelayed(new AutoScaleRunnable(mInitScale, x, y), 16); isAutoScale = true; }return true; }}); }/** * 自動緩慢放大與縮小 */ private class AutoScaleRunnable implements Runnable {/** * 縮放的目標值以及縮放的中心點位置(雙擊位置) */ private float mTargetScale; private float x; private float y; private final float BIGGER = 1.07f; private final float SMALLER = 0.93f; private float tmpScale; public AutoScaleRunnable(float mTargetScale, float x, float y) {this.mTargetScale = mTargetScale; this.x = x; this.y = y; //確定當前雙擊 每次的scale系數(shù),放大還是縮小 if (mTargetScale > getScale()) {tmpScale = BIGGER; } else {tmpScale = SMALLER; }}@Override public void run() {/** * 進行縮放 */ mScaleMatrix.postScale(tmpScale, tmpScale, x, y); checkBorderAndCenterWhenScale(); setImageMatrix(mScaleMatrix); float currentScale = getScale(); /** * 判斷本次縮放之后如果仍未達到目標值,接著進行下一循環(huán)的縮放,判斷 */ if (tmpScale > 1.0f && currentScale < mTargetScale || tmpScale < 1.0f && currentScale > mTargetScale) {postDelayed(this, 16); } else {/** * 臨界判定 * * 如果本次縮放之后已經(jīng)達到或者超過了目標值 * 那么直接縮放到目標值 * */ float scale = mTargetScale / currentScale; mScaleMatrix.postScale(scale, scale, x, y); checkBorderAndCenterWhenScale(); setImageMatrix(mScaleMatrix); isAutoScale = false; }}}/** * 注冊與取消注冊注冊 OnGlobalLayoutListener */ @Override protected void onAttachedToWindow() {super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(this); }@Override protected void onDetachedFromWindow() {super.onDetachedFromWindow(); getViewTreeObserver().removeGlobalOnLayoutListener(this); }/** * 全局布局完成后 調(diào)用以下OnGlobalLayoutListener的實現(xiàn)方法 * 在這里獲取控件的寬高是比較合適的 * 獲取ImageView加載完成的圖片,并且比較圖片大小與屏幕大小 根據(jù)結(jié)果進行縮放 * 縮放之后,能居中顯示完整的圖片,并且寬或者高頂?shù)絀mageView的邊 */ @Override public void onGlobalLayout() {if (!mOnce) {int width = getWidth(); //獲取當前控件的寬和高 int height = getHeight(); Drawable d = getDrawable(); if (d == null) {return; }int dw = d.getIntrinsicWidth(); //獲得加載完成的圖片寬高屬性 int dh = d.getIntrinsicHeight(); float scale = 1.0f; //四種情況 /** * 縮放策略 */ if (dw > width && dh < height) {scale = width * 1.0f / dw; } else if (dw < width && dh > height) {scale = height * 1.0f / dh; } else {scale = Math.min(width * 1.0f / dw, height * 1.0f / dh); }mInitScale = scale; mMidScale = mInitScale * 2; mMaxScale = mInitScale * 4; /** * 將圖片移動到控件的中心 */ int translateX = (width - dw) / 2; int translateY = (height - dh) / 2; mScaleMatrix.postTranslate(translateX, translateY); mScaleMatrix.postScale(mInitScale, mInitScale, width / 2, height / 2); setImageMatrix(mScaleMatrix); mOnce = true; }}/** * 獲取當前的總體縮放比例(在縮放矩陣中可以查到) */ public float getScale() {float[] values = new float[9]; mScaleMatrix.getValues(values); return values[Matrix.MSCALE_X]; }/** * ScaleGestureDetector的三個要實現(xiàn)的方法 * 監(jiān)聽 由nTouch傳遞過來的多點觸控的縮放手勢 * <p> * 本次onScale完成后的最終總體縮放結(jié)果,就是 * 原來的總體縮放結(jié)果getScale的值乘以本次的縮放因子scaleFactor */ @Override public boolean onScale(ScaleGestureDetector detector) {float scale = getScale(); float scaleFactor = detector.getScaleFactor();//獲得當前手勢的縮放比例,可能大于1 也可能小于1 if (getDrawable() == null) {return true; }//注意判斷在每一次調(diào)用onScare前后,總的縮放比例都應(yīng)該在 mInitScale和mMaxScale 之間 if (scale < mMaxScale && scaleFactor > 1.0f || scale > mInitScale && scaleFactor < 1.0f) {//在滿足前面的條件下,如果在這次縮放之后 比例超出了范圍 就要調(diào)整scaleFactor if (scale * scaleFactor < mInitScale) {/** * 修正scaleFactor使最終的縮放結(jié)果為mInitScale,不能再小了 * 理論上 使scaleFactor直接等于1也可以,也就是在本次縮放會觸及邊界的情況下,取消本次縮放 * 但是如果是修正縮放結(jié)果為剛好達到邊界,效果自然是最好的 */ scaleFactor = mInitScale / scale; // 使最終的縮放結(jié)果為mInitScale,不能再小了 }if (scale * scaleFactor > mMaxScale) {scaleFactor = mMaxScale / scale; // 使最終的縮放結(jié)果為mMaxScale,不能再大了 }/** * 后兩個參數(shù)是縮放的中心點,將其設(shè)為當前縮放手勢的中心點 */ mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); /** * * 如果直接設(shè)置縮放中心點為手勢點的話,幾次縮放之后不僅圖片中心會偏移,屏幕上可能還會出現(xiàn)留白 * 所以需要在縮放之前 即時檢測調(diào)整,并且將調(diào)整后的偏移一起post到mScaleMatrix中 */ checkBorderAndCenterWhenScale(); setImageMatrix(mScaleMatrix); }return true; }/** * 在縮放的時候進行邊界控制 以及位置控制 * 總體思路是 圖片較大的時候 處理留白 , 圖片較小的時候 處理居中 */ private void checkBorderAndCenterWhenScale() {RectF rectF = getMatrixRectF(); //為了修正需要偏移的值 float deltaX = 0; float deltaY = 0; int width = getWidth(); int height = getHeight(); /** * 進行邊界判斷 * 當圖片寬度大于控件寬度,而控件左右有留白 * 當圖片高度大于控件高度,而控件上下有留白 * 都是需要進行偏移處理覆蓋留白的 */ if (rectF.width() >= width) {if (rectF.left > 0) {deltaX = -rectF.left; }if (rectF.right < width) {deltaX = width - rectF.right; }}if (rectF.height() >= height) {if (rectF.top > 0) {deltaY = -rectF.top; }if (rectF.bottom < height) {deltaY = height - rectF.bottom; }}/** * 如果圖片的寬度或者高度小于控件的 則進行居中 * 計算的偏移量的時候要注意此時的圖片的邊不一定與控件的左邊或者上邊重合(基本不會重合) */ if (rectF.width() < width) {deltaX = width / 2f - rectF.left - rectF.width() / 2f; }if (rectF.height() < height) {deltaY = height / 2f - rectF.top - rectF.height() / 2f; }mScaleMatrix.postTranslate(deltaX, deltaY); }/** * RectF:保存一個矩形的left;top;right;bottom;四個參數(shù),且都是float * <p> * 獲取當前圖片的矩形坐標 */ private RectF getMatrixRectF() {Matrix matrix = mScaleMatrix; RectF rectF = new RectF(); Drawable d = getDrawable(); if (d != null) {//設(shè)定圖片在經(jīng)過Matrix變換之前的Rect坐標 rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); //將rectF坐標進行矩陣變換之后的坐標重新賦值給rectF matrix.mapRect(rectF); }return rectF; }@Override public boolean onScaleBegin(ScaleGestureDetector detector) {return true; }@Override public void onScaleEnd(ScaleGestureDetector detector) {}/** * 實現(xiàn)OnTouchListener 并且監(jiān)聽onTouchListener,然后在實現(xiàn)方法onTouch中 將觸摸時事件交給ScaleGestureDetector來處理 * 畢竟是系統(tǒng)的多點觸控處理API,很專業(yè) 哈哈 */ @Override public boolean onTouch(View v, MotionEvent event) {//優(yōu)先處理雙擊,以免在雙擊的時候產(chǎn)生縮放或者移動 if (mGestureDetector.onTouchEvent(event)) {return true; }//將多點的scale移交給mScaleGestureDetector mScaleGestureDetector.onTouchEvent(event); /** * 以下是平移操作 */ float x = 0;//保存多點觸控的中心點 float y = 0; //獲取當前的觸控點數(shù) int pointerCount = event.getPointerCount(); for (int i = 0; i < pointerCount; i++) {x += event.getX(i); y += event.getY(i); }x /= pointerCount;//算出當前的中心點位置 y /= pointerCount; /** * isCanDrag為false有兩種情況,一種是觸控點數(shù)發(fā)生了變化,我們只是記錄位置 * 另一種是位移太小 * 這兩種情況 我們都不會postTranslate * */ if (mLastPointerCount != pointerCount) {isCanDrag = false; mLastX = x; mlastY = y; }mLastPointerCount = pointerCount; RectF rect= getMatrixRectF(); switch (event.getAction()) {/** * 在 down和move的時候進行判斷 如果當前圖片已經(jīng)經(jīng)過人為放大了,那么就請求父控件(Viewpager) * 不要攔截touch事件,讓圖片可以移動查看 而不會滑動到viewpager的下一張 * * 這里加 0.01 是因為避免浮點數(shù)計算丟失精確度 確保在mInitScale的情況下可以翻到下一張 */ case MotionEvent.ACTION_DOWN:if(rect.width()>getWidth()+0.01||rect.height()>getHeight()+0.01){if(getParent()instanceof ViewPager)getParent().requestDisallowInterceptTouchEvent(true); }break; case MotionEvent.ACTION_MOVE:if(rect.width()>getWidth()+0.01||rect.height()>getHeight()+0.01){if(getParent()instanceof ViewPager)getParent().requestDisallowInterceptTouchEvent(true); }float dx = x - mLastX; float dy = y - mlastY; if (!isCanDrag) {isCanDrag = isMoveAction(dx, dy); }if (isCanDrag) {RectF rectF = getMatrixRectF(); if (getDrawable() != null) {isCheckLeftAndRight = isCheckTopAndBottom = true; /** * 圖片寬度小于控件寬度,不允許橫向移動 * 既然不能移動,也就不需要移動時邊界控制 */ if (rectF.width() < getWidth()) {isCheckLeftAndRight = false; dx = 0; }//圖片高度小于控件高度,不允許縱向移動 if (rectF.height() < getHeight()) {isCheckTopAndBottom = false; dy = 0; }mScaleMatrix.postTranslate(dx, dy); /** * 平移的時候也是需要邊界控制的 */ checkBorderWhenTranslate(); setImageMatrix(mScaleMatrix); }}mLastX = x;//移動過程中不斷記錄上一次的xy mlastY = y; break; case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:mLastPointerCount = 0; break; default:break; }return true; }/** * 移動時 進行邊界控制 */ private void checkBorderWhenTranslate() {RectF rectF = getMatrixRectF(); float deltaX = 0; float deltaY = 0; int width = getWidth(); int height = getHeight(); /** * 左側(cè)有留白,并且此時圖片寬度是大于控件寬度的 */ if (rectF.left > 0 && isCheckLeftAndRight) {deltaX = -rectF.left; }if (rectF.right < width && isCheckLeftAndRight) {deltaX = width - rectF.right; }if (rectF.top > 0 && isCheckTopAndBottom) {deltaY = -rectF.top; }if (rectF.bottom < height && isCheckTopAndBottom) {deltaY = height - rectF.bottom; }mScaleMatrix.postTranslate(deltaX, deltaY); }/** * 判斷偏移量是否足以觸發(fā)move */ private boolean isMoveAction(float dx, float dy) {return Math.sqrt(dx * dx + dy * dy) > mTouchSlop; } }

總結(jié)

以上是生活随笔為你收集整理的图片的多点触控缩放与移动的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。