您好,登錄后才能下訂單哦!
這篇“Android自定義PhotoView使用的方法是什么”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Android自定義PhotoView使用的方法是什么”文章吧。
自定義PhotoView
自定義 PhotoView 繼承(extends)自 View。并在最中間顯示后面操作的圖片。繪制圖片可以重寫 onDraw()方法,并在里面通過Canvas.drawBitmap()來要繪制圖片。
drawBitmap()的四個參數:
bitmap: 要在 Canvas 中繪制的位圖
letf:正在繪制的位圖左側的位置
top:正在繪制的位圖頂部的位置
paint: 畫筆
其中 (left, top) 是要繪制圖片的起始坐標。要將圖片繪制在中間,我們就需要計算 left/top 的位置。我們重寫 onSizeChanged() 函數,該函數在onDraw之前調用,且尺寸改變時也要調用。
其中:(下面代碼中是用 originalOffsetX/originalOffsetY 來代替的)
left = (getWidth() - bitmap.getWidth()) / 2;
top =(getHeight() - bitmap.getHeight()) / 2;
public class PhotoView extends View { private static final float IMAGE_WIDTH = Utils.dpToPixel(300); private Bitmap bitmap; private Paint paint; // 畫筆 private float originalOffsetX; private float originalOffsetY; public PhotoView(Context context) { this(context, null); } public PhotoView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public PhotoView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } /** * 初始化操作 */ private void init() { bitmap = Utils.getPhoto(getResources(), (int) IMAGE_WIDTH); // 獲取到圖片 paint = new Paint(); } /** * TODO 在onDraw之前調用,且尺寸改變時也要調用 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); originalOffsetX = (getWidth() - bitmap.getWidth()) / 2f; originalOffsetY = (getHeight() - bitmap.getHeight()) / 2f; } /** * 畫出圖片 * @param canvas 畫布 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint); } }
xml 布局
xml 布局中最外層是 FragmeLayout,里面只有一個自定義的 PhotoView 用來展示圖片。
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.example.photoview2.PhotoView android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
Utils 工具類
Utils 工具類里主要有兩個函數。dpToPixel() 將 dp 轉換為像素;getPhot() 加載 Drawable 下的圖片,并返回為 bitmap 類型。
public class Utils { public static float dpToPixel(float dp) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics()); } public static Bitmap getPhoto(Resources res, int width) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, R.drawable.photo, options); options.inJustDecodeBounds = false; options.inDensity = options.outWidth; options.inTargetDensity = width; return BitmapFactory.decodeResource(res, R.drawable.photo, options); } }
設置圖片的縮放比例
如下圖的三種情況,左邊的是原圖;中間是小放大(smallScale),即圖片左右兩邊貼進屏幕;右邊是大放大(bigScale),即圖片沾滿整個屏幕。
根據上面的描述,設置兩個變量即 smallScale 和 bigScale 分別代表上圖"中"和“右”的縮放比例,smallScale 是初始樣式,bigSmall 是雙擊后的樣式。將 smallScale 和 bigScale 的設置放在 onSizeChanged() 函數里設值。如下圖所示
/** * TODO 在onDraw之前調用,且尺寸改變時也要調用 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); originalOffsetX = (getWidth() - bitmap.getWidth()) / 2f; originalOffsetY = (getHeight() - bitmap.getHeight()) / 2f; // TODO 判斷 bitmap 是扁的還是長的 if ((float)bitmap.getWidth() / bitmap.getHeight() > (float) getWidth() / getHeight()) { // bitmap 的 width > height smallScale = (float) getWidth() / bitmap.getWidth(); bigScale = (float) getHeight() / bitmap.getHeight() * OVER_SCALE_FACTOR; }else { // bitmap 的 height > width smallScale = (float) getHeight() / bitmap.getHeight(); bigScale = (float) getWidth() / bitmap.getWidth() * OVER_SCALE_FACTOR; } currentScale = smallScale; }
注意 if 里的判斷條件,判斷圖片是扁平還是長的。如下圖理解,當然我們這里用的圖是扁平的。currentScale 是當前的縮放比例,smallScale <= currentScale <= bigScale 。
最后設置了 smallScale 和 bigScale 后,我們還要在 onDraw 里將 smallScale 放大的圖片繪制出來。這里用 currentScale ,因為在 onSizeChanged 函數里,我們將 smallScale 賦值給了 currentScale 。使用 Canvas.scale 函數進行縮放。
// TODO 圖片放大, // 第1,2個參數是放大比例,第3,4個參數是縮放的起始點,默認是(0,0) canvas.scale(currentScale, currentScale, getWidth() / 2f, getHeight() / 2f);
雙擊擊縮放
Android 為我們提供了一個 GestureDetector 類來實現雙擊、單擊、滑動、慣性滑動等。在 init 函數里添加如下代碼,初始化 GestureDetector。gestureDectector 是一個全局變量。
gestureDetector = new GestureDetector(context, new photoGestureListener());
GestureDetector 的第二個參數是一個 Listener ,所以我們寫了個內部類 photoGestureListener 繼承GestureDetector.SimpleOnGestureListener。SimpleOnGestureListener 是一個 interface, 所以我們重寫里面的方法,其中onDoubleTap() 就是實現寫雙擊縮放的。
注意:onDown() 方法要返回 true 才能響應到雙擊事件
/** * TODO 單擊/雙擊/慣性滑動的監聽 */ class photoGestureListener extends GestureDetector.SimpleOnGestureListener{ // up 時觸發,單擊或者雙擊的第一次會觸發 --- up時,如果不是雙擊的得二次點擊,不是長按,則觸發 @Override public boolean onSingleTapUp(MotionEvent e) { return super.onSingleTapUp(e); } // 長按 默認300ms后觸發 @Override public void onLongPress(MotionEvent e) { super.onLongPress(e); } /** * 滾動 --move * @param e1 手指按下 * @param e2 當前動作 * @param distanceX 就位置 - 新位置 * @param distanceY * @return */ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return super.onScroll(e1, e2, distanceX, distanceY); } /** * 慣性滑動 * @param velocityX X軸方向運動速度 像素/s * @param velocityY Y軸方向運動速度 像素/s * @return */ @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return super.onFling(e1, e2, velocityX, velocityY); } // 處理點擊效果 --延時 100ms 觸發 @Override public void onShowPress(MotionEvent e) { super.onShowPress(e); } // 只需要關注 onDown 的返回值,默認返回 false @Override public boolean onDown(MotionEvent e) { return true; } // 雙擊的第二次點擊 down 時觸發 雙擊 40ms -- 300ms 之間 @Override public boolean onDoubleTap(MotionEvent e) { // // TODO 第一版,這種直接放大/縮小有點深硬,不平滑 // isEnlarge = !isEnlarge; // if (isEnlarge) { // currentScale = bigScale; // 雙擊放大 // }else { // currentScale = smallScale; // 再雙擊時放小 // } // invalidate(); // 刷新 //TODO 第二版,借助屬性動畫實現 isEnlarge = !isEnlarge; if (isEnlarge) { // TODO 雙擊時計算偏移,雙擊那個位置,就放大那個位置 / (e.getX(), e.getY()) 當前點擊的位置 offsetX = (e.getX() - getWidth() / 2f) - (e.getX() - getWidth() / 2f) * bigScale / smallScale; offsetY = (e.getY() - getHeight() / 2f) - (e.getY() - getHeight() / 2f) * bigScale / smallScale; fitOffsets(); // 解決點擊圖片外時放大空白部分 getScaleAnimator().start(); }else { getScaleAnimator().reverse(); } return super.onDoubleTap(e); } // 雙擊的第二次down, move, up 都觸發 @Override public boolean onDoubleTapEvent(MotionEvent e) { return super.onDoubleTapEvent(e); } // 單擊按下時觸發,雙擊時不觸發/ down, up時都可能觸發(不會同時觸發) // 延時300ms觸發TAP事件 // 300ms 以內抬手 -- 才會觸發TAP -- onSingleTapConfirmed // 300ms 以后抬手 -- 不是雙擊或長按,則觸發 @Override public boolean onSingleTapConfirmed(MotionEvent e) { return super.onSingleTapConfirmed(e); } }
onDoubleTap() 里的第一版代碼里 currentScale 直接由 smallScale 變到 bigscale,一下子就放大了,就很生硬不平滑。為了實現平滑的效果,我們使用 屬性動畫(ObjectAnimator),使得currentScale 由 smallScale 逐步變化到 bigScale,即 currentScale
(smallScale, bigScale)
private ObjectAnimator getScaleAnimator(){ if (scaleAnimator == null) { scaleAnimator = ObjectAnimator.ofFloat(this, "currentScale", 0); } // TODO 平滑的范圍,從 smallScale --> bigScale scaleAnimator.setFloatValues(smallScale, bigScale); return scaleAnimator; } public float getCurrentScale() { return currentScale; } public void setCurrentScale(float currentScale) { this.currentScale = currentScale; // 每一次在 smallScale -- bigScale 直接變化時都刷新 invalidate(); }
注意:上面代碼里的 offsetX / offsetY 兩個變量這里沒講,是因為它們是滑動里用到的變量,所以我們放到下一小節里講,這里用它們是為了實現雙擊那個位置,就放大那個位置。如果把下面兩句代碼注釋掉,會發現雙擊的時候永遠是從中間位置放大。實現原理就是 offsetX / offsetY 是兩個偏移量,我們從中間放大后再移到 offsetX / offsetY 的位置,就實現了點擊哪里就放大哪里。
offsetX = (e.getX() - getWidth() / 2f) - (e.getX() - getWidth() / 2f) * bigScale / smallScale; offsetY = (e.getY() - getHeight() / 2f) - (e.getY() - getHeight() / 2f) * bigScale / smallScale; fitOffsets(); // 解決點擊圖片外時放大空白部分
完成上面的代碼,當我們運行程序然后雙擊屏幕時發現圖片并沒有放大,為什么?因為我們雙擊的時候觸發的是 photoView 的 onTouchEvent(),而雙擊時需要觸發 GestureDetector 的 onToucEvent()才能實現效果,所以我們再 photoView 里重寫 onTouchEvent ,并用 GestureDetector 的 onTouchEvent() 來強制接管。
/** TODO 我們點擊圖片時,觸發的是 PhotoView 里的 onTouchEvent, * TODO 并沒有觸發 GestureDetector 里的onTouchEvent, 所以才需要強制接管 */ @Override public boolean onTouchEvent(MotionEvent event) { return gestureDetector.onTouchEvent(event); //return super.onTouchEvent(event); }
當我們雙擊放大圖片后,可以通過手指滑動查看屏幕外面的內容,或者用力往某個方向滑動,實現慣性滑動的效果。
手指滑動
在上面一節提到的 SimpleOnGestureListener 接口,里面的 onScroll 函數實現滑動。offsetX offsetY 是滑動的偏移量,即滑動到了圖片的那個位置,在繪制的時候才能把滑動到的位置的圖片繪制出來。
/** * 滾動 --move * @param e1 手指按下 * @param e2 當前動作 * @param distanceX 就位置 - 新位置 * @param distanceY * @return */ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // 圖片放大時,才可以滑動,即改變 offsetX offsetY if (isEnlarge) { offsetX -= distanceX; offsetY -= distanceY; fitOffsets(); invalidate(); } return super.onScroll(e1, e2, distanceX, distanceY); }
if 里的判斷條件是確保在圖片放大的情況下才進行滑動。fitOffsets() 是一個功能函數,計算圖片滑動到邊界的情況,放大后圖片的邊界滑動到屏幕邊界時就滑不動了。
/** * 計算圖片滑動的邊界情況 * TODO 當往某個方向滑動圖片時,放大后的圖片邊界與手機屏幕邊界重合時,就不能滑動了 */ private void fitOffsets(){ offsetX = Math.min(offsetX, (bitmap.getWidth() * bigScale - getWidth()) / 2); offsetX = Math.max(offsetX, -(bitmap.getWidth() * bigScale - getWidth()) / 2); offsetY = Math.min(offsetY, (bitmap.getHeight() * bigScale - getHeight()) / 2); offsetY = Math.max(offsetY, -(bitmap.getHeight() * bigScale - getHeight()) / 2); }
對 offsetX 取值用 Math.min()和 Math.max() 的情況可以如下圖理解。offsetY 同理。
設置好了 onScroll() 函數后,我們還要將滑動的圖片繪制出來,所以我們還要在 onDraw 函數里調用 Canvas.translate(), 將滑動的偏移 offsetX / offsetY 設置進去。
// TODO 圖片滑動查看隱藏部分 canvas.translate(offsetX, offsetY);
慣性滑動
SimpleOnGestureListener 接口里的 onFling 函數實現慣性滑動。通過 OverScroll.fling() 來實現,filing 函數的最后兩個參數表示當滑動到邊界時,如果還有速度,則會將邊界外的空白部分拉出200像素,然后立馬回彈回去的那種效果。可以嘗試將這兩個參數去掉對比兩種情況的效果。
/** * 慣性滑動 * @param velocityX X軸方向運動速度 像素/s * @param velocityY Y軸方向運動速度 像素/s * @return */ @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (isEnlarge) { overScroller.fling((int) offsetX, (int) offsetY, (int) velocityX, (int) velocityY, -(int) (bitmap.getWidth() * bigScale - getWidth()) / 2, (int) (bitmap.getWidth() * bigScale - getWidth()) /2, -(int) (bitmap.getHeight() * bigScale - getHeight()) / 2, (int) (bitmap.getHeight() * bigScale - getHeight()) /2, 200, 200); // TODO 我們要不斷的刷新界面,不斷的改變 offsetX, offsetY, 參數:Runnable接口 // postOnAnimation 下一幀動畫的時候執行 postOnAnimation(new flingRunner()); } return super.onFling(e1, e2, velocityX, velocityY); }
我們在慣性滑動時要不斷的刷新界面,不斷改變 offsetX / offsetY 。我們使用 postOnAnimation(),里面傳入一個 filingRunner 接口,繼承自Runnable 。然后在filingRunner 里再調用postOnAnimation() 實現循環的效果。用 overScroller.computeScrollOffset() 函數計算當前的偏移并賦值給 offsetX/offsetY,實現不斷改變它的功能。當computeScrollOffset() 返回 false,則表明當前的慣性速度為0,慣性滑動就結束,則結束循環。
class flingRunner implements Runnable{ @Override public void run() { // TODO 用 overScroller 計算當前的偏移,并賦值給offsetX, offsetY if (overScroller.computeScrollOffset()) { // computeScrollOffset()會返回一個boolean值,為true, 說明動作還沒完成,以此來作為循環結束條件 offsetX = overScroller.getCurrX(); offsetY = overScroller.getCurrY(); invalidate(); //在上面的onFling 方法里面,postOnAnimation 只會調用一次,所以我們這里再調用,參數:自己(flingRunner) //TODO postOnAnimation 下一幀動畫的時候執行 postOnAnimation(this); } } }
注意:寫到這里,就有了一個小 bug ,就是當我們滑動了圖片后再雙擊放小,會發現圖片不會顯示在正中間了,只需在 onDraw() 函數里做如下修改:我們在 offsetX / offsetY 上乘以一個平移因子,當雙擊縮小的時候,currentScale == smallScale ,則 scaleFaction == 0 --> offsetX / offsetY ==0 ,就相當于沒有平移了,所以雙擊縮小時就能顯示在原位置。
// 解決:當位置移動后,雙擊縮小,讓圖片顯示在最初的位置 // 雙擊縮小時,currentScale = smallScale, 所以 scaleFunction = 0, 所以 translate就相當于沒有平移 float scaleFaction = (currentScale - smallScale) / (bigScale - smallScale); // TODO 圖片滑動查看隱藏部分 canvas.translate(offsetX * scaleFaction, offsetY * scaleFaction);
Android 為我們提供了一個 ScaleGestureDetector 類來實現雙指縮放功能。在 init() 函數里初始化。
scaleGestureDetector = new ScaleGestureDetector(context, new photoScaleGestureListener());
photoScaleGestureListener() 實現了ScaleGestureDetector.onScaleGestureListener 接口,實現里面的三個方法。
onScale:處理正在縮放
onScaleBegin: 開始縮放
onScaleEnd: 結束縮放
/** * TODO 雙指縮放大的監聽 */ class photoScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener{ float initScale; // 處理正在縮放 @Override public boolean onScale(ScaleGestureDetector detector) { if (currentScale >= smallScale && !isEnlarge) { isEnlarge = !isEnlarge; } // 縮放因子 縮放后 / 縮放前 // eg 放大后=10,放大前=5, 縮放因子 == 10 / 5 == 2 currentScale = initScale * detector.getScaleFactor(); invalidate(); return false; } // 開始縮放 @Override public boolean onScaleBegin(ScaleGestureDetector detector) { initScale = currentScale; return true; } //結束縮放 @Override public void onScaleEnd(ScaleGestureDetector detector) { } }
同理,ScaleGestureDetector 的觸發也需要在 photoView 里的 onTouchEvent 里強制接管,所以修改 onTouchEvnet() 里的代碼如下:
/** TODO 我們點擊圖片時,觸發的是 PhotoView 里的 onTouchEvent, * TODO 并沒有觸發 GestureDetector 里的onTouchEvent, 所以才需要強制接管 * TODO 同理,ScaleGestureDetector 也需要接管 */ @Override public boolean onTouchEvent(MotionEvent event) { // TODO 響應事件以雙指縮放優先 boolean result = scaleGestureDetector.onTouchEvent(event); if(!scaleGestureDetector.isInProgress()){ // TODO 不是雙指縮放,則用 GestureDetector 的 onTouchEvent 強制接管 result = gestureDetector.onTouchEvent(event); } return result; //return super.onTouchEvent(event); }
完整的 photoView 代碼(MainActivity里沒寫什么)
package com.example.photoview; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.widget.OverScroller; import androidx.annotation.Nullable; public class PhotoView extends View { private static final float IMAGE_WIDTH = Utils.dpToPixel(300); private Bitmap bitmap; private Paint paint; float originalOffsetX; float originalOffsetY; private float smallScale; private float bigScale; private float currentScale; //當前縮放值 private float OVER_SCALE_FACTOR = 1.5f; private boolean isEnlarge = false; //雙擊時放大/縮小的標志位 private ObjectAnimator scaleAnimator; // 雙擊放大/縮小時,通過屬性動畫做出平滑的效果 private GestureDetector gestureDetector; // android 提高的手勢探測器,TODO 判斷是單價還是雙擊 private ScaleGestureDetector scaleGestureDetector; // TODO 實現雙指縮放 private float offsetX; // 圖片放大后,手指滑動圖片查看隱藏部分 private float offsetY; private OverScroller overScroller; // TODO 實現慣性滑動 public PhotoView(Context context) { this(context, null); } public PhotoView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public PhotoView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context){ bitmap = Utils.getPhoto(getResources(), (int) IMAGE_WIDTH); paint = new Paint(Paint.ANTI_ALIAS_FLAG); gestureDetector = new GestureDetector(context, new photoGestureListener()); scaleGestureDetector = new ScaleGestureDetector(context, new photoScaleGestureListener()); // 設置長按響應,false--關閉 //gestureDetector.setIsLongpressEnabled(false); overScroller = new OverScroller(context); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 解決:當位置移動后,雙擊縮小,讓圖片顯示在最初的位置 // 雙擊縮小時,currentScale = smallScale, 所以 scaleFunction = 0, 所以 translate就相當于沒有平移 float scaleFaction = (currentScale - smallScale) / (bigScale - smallScale); // TODO 圖片滑動查看隱藏部分 canvas.translate(offsetX * scaleFaction, offsetY * scaleFaction); // TODO 圖片放大, // 第1,2個參數是放大比例,第3,4個參數是縮放的起始點,默認是(0,0) canvas.scale(currentScale, currentScale, getWidth() / 2f, getHeight() / 2f); // drawBitmap(); 第2,3個參數是畫bitmap的起始坐標點 canvas.drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint); } /** * TODO 在onDraw之前調用,且尺寸改變時也要調用 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); originalOffsetX = (getWidth() - bitmap.getWidth()) / 2f; originalOffsetY = (getHeight() - bitmap.getHeight()) / 2f; // TODO 判斷 bitmap 是扁的還是長的 if ((float)bitmap.getWidth() / bitmap.getHeight() > (float) getWidth() / getHeight()) { // bitmap 的 width > height smallScale = (float) getWidth() / bitmap.getWidth(); bigScale = (float) getHeight() / bitmap.getHeight() * OVER_SCALE_FACTOR; }else { // bitmap 的 height > width smallScale = (float) getHeight() / bitmap.getHeight(); bigScale = (float) getWidth() / bitmap.getWidth() * OVER_SCALE_FACTOR; } currentScale = smallScale; } /** TODO 我們點擊圖片時,觸發的是 PhotoView 里的 onTouchEvent, * TODO 并沒有觸發 GestureDetector 里的onTouchEvent, 所以才需要強制接管 * TODO 同理,ScaleGestureDetector 也需要接管 */ @Override public boolean onTouchEvent(MotionEvent event) { // TODO 響應事件以雙指縮放優先 boolean result = scaleGestureDetector.onTouchEvent(event); if(!scaleGestureDetector.isInProgress()){ // TODO 不是雙指縮放,則用 GestureDetector 的 onTouchEvent 強制接管 result = gestureDetector.onTouchEvent(event); } return result; //return super.onTouchEvent(event); } /** * TODO 單擊/雙擊/慣性滑動的監聽 */ class photoGestureListener extends GestureDetector.SimpleOnGestureListener{ // up 時觸發,單擊或者雙擊的第一次會觸發 --- up時,如果不是雙擊的得二次點擊,不是長按,則觸發 @Override public boolean onSingleTapUp(MotionEvent e) { return super.onSingleTapUp(e); } // 長按 默認300ms后觸發 @Override public void onLongPress(MotionEvent e) { super.onLongPress(e); } /** * 滾動 --move * @param e1 手指按下 * @param e2 當前動作 * @param distanceX 就位置 - 新位置 * @param distanceY * @return */ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // 圖片放大時,才可以滑動,即改變 offsetX offsetY if (isEnlarge) { offsetX -= distanceX; offsetY -= distanceY; fitOffsets(); invalidate(); } return super.onScroll(e1, e2, distanceX, distanceY); } /** * 慣性滑動 * @param velocityX X軸方向運動速度 像素/s * @param velocityY Y軸方向運動速度 像素/s * @return */ @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (isEnlarge) { overScroller.fling((int) offsetX, (int) offsetY, (int) velocityX, (int) velocityY, -(int) (bitmap.getWidth() * bigScale - getWidth()) / 2, (int) (bitmap.getWidth() * bigScale - getWidth()) /2, -(int) (bitmap.getHeight() * bigScale - getHeight()) / 2, (int) (bitmap.getHeight() * bigScale - getHeight()) /2, 200, 200); // TODO 我們要不斷的刷新界面,不斷的改變 offsetX, offsetY, 參數:Runnable接口 // postOnAnimation 下一幀動畫的時候執行 postOnAnimation(new flingRunner()); } return super.onFling(e1, e2, velocityX, velocityY); } // 處理點擊效果 --延時 100ms 觸發 @Override public void onShowPress(MotionEvent e) { super.onShowPress(e); } // 只需要關注 onDown 的返回值,默認返回 false @Override public boolean onDown(MotionEvent e) { return true; } // 雙擊的第二次點擊 down 時觸發 雙擊 40ms -- 300ms 之間 @Override public boolean onDoubleTap(MotionEvent e) { // // TODO 第一版,這種直接放大/縮小有點深硬,不平滑 // isEnlarge = !isEnlarge; // if (isEnlarge) { // currentScale = bigScale; // 雙擊放大 // }else { // currentScale = smallScale; // 再雙擊時放小 // } // invalidate(); // 刷新 //TODO 第二版,借助屬性動畫實現 isEnlarge = !isEnlarge; if (isEnlarge) { // TODO 雙擊時計算偏移,雙擊那個位置,就放大那個位置 / (e.getX(), e.getY()) 當前點擊的位置 offsetX = (e.getX() - getWidth() / 2f) - (e.getX() - getWidth() / 2f) * bigScale / smallScale; offsetY = (e.getY() - getHeight() / 2f) - (e.getY() - getHeight() / 2f) * bigScale / smallScale; fitOffsets(); // 解決點擊圖片外時放大空白部分 getScaleAnimator().start(); }else { getScaleAnimator().reverse(); } return super.onDoubleTap(e); } // 雙擊的第二次down, move, up 都觸發 @Override public boolean onDoubleTapEvent(MotionEvent e) { return super.onDoubleTapEvent(e); } // 單擊按下時觸發,雙擊時不觸發/ down, up時都可能觸發(不會同時觸發) // 延時300ms觸發TAP事件 // 300ms 以內抬手 -- 才會觸發TAP -- onSingleTapConfirmed // 300ms 以后抬手 -- 不是雙擊或長按,則觸發 @Override public boolean onSingleTapConfirmed(MotionEvent e) { return super.onSingleTapConfirmed(e); } } /** * TODO 雙指縮放大的監聽 */ class photoScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener{ float initScale; // 處理正在縮放 @Override public boolean onScale(ScaleGestureDetector detector) { if (currentScale >= smallScale && !isEnlarge) { isEnlarge = !isEnlarge; } // 縮放因子 縮放后 / 縮放前 // eg 放大后=10,放大前=5, 縮放因子 == 10 / 5 == 2 currentScale = initScale * detector.getScaleFactor(); invalidate(); return false; } // 開始縮放 @Override public boolean onScaleBegin(ScaleGestureDetector detector) { initScale = currentScale; return true; } //結束縮放 @Override public void onScaleEnd(ScaleGestureDetector detector) { } } class flingRunner implements Runnable{ @Override public void run() { // TODO 用 overScroller 計算當前的偏移,并賦值給offsetX, offsetY if (overScroller.computeScrollOffset()) { // computeScrollOffset()會返回一個boolean值,為true, 說明動作還沒完成,以此來作為循環結束條件 offsetX = overScroller.getCurrX(); offsetY = overScroller.getCurrY(); invalidate(); //在上面的onFling 方法里面,postOnAnimation 只會調用一次,所以我們這里再調用,參數:自己(flingRunner) //TODO postOnAnimation 下一幀動畫的時候執行 postOnAnimation(this); } } } /** * 計算圖片滑動的邊界情況 * TODO 當往某個方向滑動圖片時,放大后的圖片邊界與手機屏幕邊界重合時,就不能滑動了 */ private void fitOffsets(){ offsetX = Math.min(offsetX, (bitmap.getWidth() * bigScale - getWidth()) / 2); offsetX = Math.max(offsetX, -(bitmap.getWidth() * bigScale - getWidth()) / 2); offsetY = Math.min(offsetY, (bitmap.getHeight() * bigScale - getHeight()) / 2); offsetY = Math.max(offsetY, -(bitmap.getHeight() * bigScale - getHeight()) / 2); } private ObjectAnimator getScaleAnimator(){ if (scaleAnimator == null) { scaleAnimator = ObjectAnimator.ofFloat(this, "currentScale", 0); } // TODO 平滑的范圍,從 smallScale --> bigScale scaleAnimator.setFloatValues(smallScale, bigScale); return scaleAnimator; } public float getCurrentScale() { return currentScale; } public void setCurrentScale(float currentScale) { this.currentScale = currentScale; // 每一次在 smallScale -- bigScale 直接變化時都刷新 invalidate(); } }
以上就是關于“Android自定義PhotoView使用的方法是什么”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。