中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android自定義PhotoView使用的方法是什么

發布時間:2023-05-08 17:33:26 來源:億速云 閱讀:150 作者:iii 欄目:開發技術

這篇“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;

Android自定義PhotoView使用的方法是什么

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);
    }
}

1、雙擊放大和縮小

Android自定義PhotoView使用的方法是什么

  • 設置圖片的縮放比例

如下圖的三種情況,左邊的是原圖;中間是小放大(smallScale),即圖片左右兩邊貼進屏幕;右邊是大放大(bigScale),即圖片沾滿整個屏幕。

Android自定義PhotoView使用的方法是什么

根據上面的描述,設置兩個變量即 smallScale 和 bigScale 分別代表上圖"中"和“右”的縮放比例,smallScale 是初始樣式,bigSmall 是雙擊后的樣式。將 smallScale 和 bigScale 的設置放在 onSizeChanged() 函數里設值。如下圖所示

Android自定義PhotoView使用的方法是什么

/**
     * 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 。

Android自定義PhotoView使用的方法是什么

最后設置了 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

Android自定義PhotoView使用的方法是什么(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);
    }

2、滑動和慣性滑動

Android自定義PhotoView使用的方法是什么

當我們雙擊放大圖片后,可以通過手指滑動查看屏幕外面的內容,或者用力往某個方向滑動,實現慣性滑動的效果。

  • 手指滑動

在上面一節提到的 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 同理。

Android自定義PhotoView使用的方法是什么

設置好了 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);

3、雙指放大和縮小

Android自定義PhotoView使用的方法是什么

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);
    }

4、完整DEMO

完整的 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使用的方法是什么”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

玛曲县| 庄河市| 确山县| 盐源县| 乌苏市| 水城县| 永新县| 九龙城区| 小金县| 北安市| 陵川县| 曲松县| 南乐县| 来宾市| 义马市| 团风县| 南澳县| 五原县| 吉林市| 赣州市| 政和县| 文登市| 拉孜县| 凤庆县| 杨浦区| 抚顺县| 左权县| 大荔县| 麻栗坡县| 龙陵县| 蒙阴县| 郎溪县| 乌拉特中旗| 祁东县| 临猗县| 城口县| 繁昌县| 隆尧县| 通海县| 襄垣县| 汝南县|