文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-07-05 22:09

关注

这篇“Android自定义PhotoView使用的方法是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Android自定义PhotoView使用的方法是什么”文章吧。

准备工作

自定义PhotoView

自定义 PhotoView 继承(extends)自 View。并在最中间显示后面操作的图片。绘制图片可以重写 onDraw()方法,并在里面通过Canvas.drawBitmap()来要绘制图片。

drawBitmap()的四个参数:

其中 (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();    }        @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;    }        @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        canvas.drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint);    }}

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 工具类里主要有两个函数。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使用的方法是什么

    @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 才能响应到双击事件

    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);        }                @Override        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {            return super.onScroll(e1, e2, distanceX, distanceY);        }                @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() 来强制接管。

    @Override    public boolean onTouchEvent(MotionEvent event) {        return gestureDetector.onTouchEvent(event);        //return super.onTouchEvent(event);    }

2、滑动和惯性滑动

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

当我们双击放大图片后,可以通过手指滑动查看屏幕外面的内容,或者用力往某个方向滑动,实现惯性滑动的效果。

在上面一节提到的 SimpleOnGestureListener 接口,里面的 onScroll 函数实现滑动。offsetX offsetY 是滑动的偏移量,即滑动到了图片的那个位置,在绘制的时候才能把滑动到的位置的图片绘制出来。

                @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() 是一个功能函数,计算图片滑动到边界的情况,放大后图片的边界滑动到屏幕边界时就滑不动了。

        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像素,然后立马回弹回去的那种效果。可以尝试将这两个参数去掉对比两种情况的效果。

                @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 接口,实现里面的三个方法。

    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() 里的代码如下:

        @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);    }        @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;    }        @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);    }        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);        }                @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);        }                @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);        }    }        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);            }        }    }        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使用的方法是什么”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯