自定义PhotoView实现突破预览

在各种APP中,我们会经常看中都会涉及到一个图片预览的功能。研究了android的手势和滑动处理,自定义实现一个PhotoView。支持图片双击放大,拖动,放大边界控制,双指放大缩小等功能 。话不多说,先看效果图。


效果图1

上图演示了图片的双击放大和缩小。项目我已经放大我的GitHub上,地址如下:
https://github.com/hugoca/PhotoView

本博文主要分析具体实现流程。

第一步 创建自定义View,初始化画笔和图片。
public class MyPhotoView extends View {
    private static final float IMAGE_WIDTH=Utils.dpToPixel(300);
    private static final float SCALE_FACTOR=1.5f;
    private Bitmap mBitmap;
    private Paint mPaint;
    
    public MyPhotoView(Context context) {
        super(context);
        init(context);
    }

    public MyPhotoView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MyPhotoView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context){
        mBitmap= Utils.getPhoto(getResources(), (int) IMAGE_WIDTH);
        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
    }

}

工具类Util代码

工具类中提供两个方法, 一个是处理dp转换的,一个是获取图片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 resources,int width){
        BitmapFactory.Options options=new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeResource(resources,R.drawable.photo,options);
        options.inJustDecodeBounds=false;
        options.inDensity=options.outWidth;
        options.inTargetDensity=width;
        return BitmapFactory.decodeResource(resources,R.drawable.photo,options);
    }
}
第三步重写onSizeChanged方法
@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        originalOffsetX=(getWidth()-mBitmap.getWidth())/2f;
        originalOffsetY=(getHeight()-mBitmap.getHeight())/2f;

        if((float)mBitmap.getWidth()/mBitmap.getHeight()>(float) getWidth()/getHeight()){
            smallScale=(float) getWidth()/mBitmap.getWidth();
            bigScale=(float)getHeight()/mBitmap.getHeight()*SCALE_FACTOR;
        }else {
            smallScale=(float)getHeight()/mBitmap.getHeight();
            bigScale=(float) getWidth()/mBitmap.getWidth()*SCALE_FACTOR;

        }
        curScale=smallScale;
    }
第四步 重写onDraw方法

重写onDraw方法进行图片的绘制

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        scaleFactor=(curScale-smallScale)/(bigScale-smallScale);
        //当前放大比例为small时,scaleFactor=0 不偏移
        //通过设置所放量来决定bitmap显示的偏移量
        canvas.translate(offsetX*scaleFactor,offsetY*scaleFactor);
        canvas.scale(curScale,curScale,getWidth()/2f,getHeight()/2f);
        canvas.drawBitmap(mBitmap,originalOffsetX,originalOffsetY,mPaint);
    }

    private float getMaxWidth(){
        return (mBitmap.getWidth()*bigScale-getWidth())/2;
    }

    private float getMaxHeight(){
        return (mBitmap.getHeight()*bigScale-getHeight())/2;
    }
第五步 添加手势控制

手势类中要处理的一个方法进行事件
1.将onDown事件返回值设置成true
2.处理双击事件,进行图片缩放
3.处理滑动事件
4.处理惯性滑动

class photoGestureDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            isEnLarge=!isEnLarge;
            if(isEnLarge){
                offsetX=0;
                offsetY=0;
                offsetX=(e.getX()-getWidth()/2f)-(e.getX()-getWidth()/2f)*bigScale/smallScale;
                offsetY=(e.getY()-getHeight()/2f)-(e.getY()-getHeight()/2f)*bigScale/smallScale;
                getScaleAnimation().start();
            }else {
                getScaleAnimation().reverse();
            }
            return super.onDoubleTap(e);
        }

        /**
         *
         * @param e1        手指按下
         * @param e2        当前的
         * @param distanceX 旧位置 - 新位置
         * @param distanceY
         * @return
         */
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if(isEnLarge){
                if(distanceX>0){
                    offsetX=Math.max(offsetX-distanceX,-getMaxWidth());
                }else {
                    offsetX=Math.min(offsetX-distanceX,getMaxWidth());
                }
                if(distanceY>0){
                    offsetY=Math.max(offsetY-distanceY,-getMaxHeight());
                }else {
                    offsetY=Math.min(offsetY-distanceY,getMaxHeight());
                }
                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)getMaxWidth(),(int)getMaxWidth()
                        ,-(int)getMaxHeight(),(int)getMaxHeight(),300,300);
                postOnAnimation(new FingRunnable());
            }
            return super.onFling(e1, e2, velocityX, velocityY);
        }
    }

惯性滑动用到的runnable

class FingRunnable implements Runnable{

        @Override
        public void run() {
            if (overScroller.computeScrollOffset()) {
                offsetX=overScroller.getCurrX();
                offsetY=overScroller.getCurrY();
                invalidate();
                postOnAnimation(this);
            }
        }
    }
第六步 添加双指操作处理
photoview2.gif
class PhotoScakeGestrueListener implements ScaleGestureDetector.OnScaleGestureListener{

        private float beginScale; //操作前的缩放比例
        @Override
        public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
            if((curScale>smallScale&&!isEnLarge)||(curScale==smallScale&&!isEnLarge)){
                isEnLarge= true;
            }
            curScale=beginScale*scaleGestureDetector.getScaleFactor(); //缩放因子
            invalidate();
            return false;
        }

        @Override
        public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
            beginScale=curScale;
            return true;
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {

        }
    }

到这里基本上自定义View已经实现了。为了方便实现,涉及到除了工具类都做的内部类实现,方便处理逻辑。最后添加相关类的手势的初始化

public class MyPhotoView extends View {
    private static final float IMAGE_WIDTH=Utils.dpToPixel(300);
    private static final float SCALE_FACTOR=1.5f;
    private Bitmap mBitmap;
    private Paint mPaint;

    //图片起始位置偏移坐标
    private float originalOffsetX;
    private float originalOffsetY;

     private float smallScale; //横向缩放填充
     private float bigScale;  //纵向缩放填充

    private float curScale; //当前缩放
    private boolean isEnLarge; //是否已经放大

    private GestureDetector gestureDetector; //手势操作
    private ObjectAnimator scaleAnimator; //处理缩放的动画
    private OverScroller overScroller; //处理惯性滑动
    private ScaleGestureDetector scaleGestureDetector;

  private void init(Context context){
        mBitmap=Utils.getPhoto(getResources(), (int) IMAGE_WIDTH);
        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        gestureDetector=new GestureDetector(context, new photoGestureDetector());
        // 关闭长按响应
//        gestureDetector.setIsLongpressEnabled(false);
        overScroller=new OverScroller(context);
        scaleGestureDetector=new ScaleGestureDetector(context,new PhotoScakeGestrueListener());
    }

布局文件中添加




   




到这里自定义View便实现了,完整项目在github(MyPhotoView 地址)上,在项目中我还放了一个处理多指滑动的view。希望对你有所帮助,还望不吝点赞!

你可能感兴趣的:(自定义PhotoView实现突破预览)