自定义PhotoView实现双指旋转缩放功能

Utils.java

 

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.vertical, options);
        options.inJustDecodeBounds = false;
        options.inDensity = options.outWidth;
        options.inTargetDensity = width;
        return BitmapFactory.decodeResource(res, R.drawable.vertical, options);
    }

}

package com.hfengxiang.example.myphotoview;

import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.MatchResult;


public class MultiEventView extends View {
    private Bitmap bitmap;
    private Paint paint;
    private float photo_width = getResources().getDisplayMetrics().widthPixels;
    private Matrix matrix;
    private float originOffsetX;
    private float originOffsetY;
    private float offsetX;
    private float offsetY;
    private String TAG = "MultiEventView";
    private int initailDegrees;
    private int currentDegrees;
    private int deltaDegree;
    private ObjectAnimator rotateAnimator;
    private ObjectAnimator zoomAnimator;
    private int targetDegree;
    private float primaryScale;
    private float largeScale;

    public int getDeltaDegree() {
        return deltaDegree;
    }

    public MultiEventView setDeltaDegree(int deltaDegree) {
        this.deltaDegree = deltaDegree;
        invalidate();
        return this;
    }

    private int pivotX;
    private int pivotY;
    private float initialDistance;
    private float currentDistance;
    private float deltaDistance;

    public MultiEventView setZoomScale(float zoomScale) {
        this.zoomScale = zoomScale;
        return this;
    }

    private float zoomScale;
    private float targetScale;
    private float lastZoomScale;
    AtomicBoolean isFirst = new AtomicBoolean(true);

    public MultiEventView(Context context) {
        this(context, null);
    }

    public MultiEventView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, -1);
    }

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

    private void init(Context context) {
        bitmap = Utils.getPhoto(getResources(), (int) photo_width);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        matrix = new Matrix();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
//        Log.i(TAG,"onTouchEvent:"+event.getPointerCount());
        return onRotateIntercept(event);
    }

    private boolean onRotateIntercept(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_DOWN:
                if (ev.getPointerCount() == 2) {
                    pivotX = (int) (ev.getX(0) + ev.getX(1)) / 2;
                    pivotY = (int) (ev.getY(0) + ev.getY(1)) / 2;
                    float deltaX = ev.getX(0) - ev.getX(1);
                    float deltaY = ev.getY(0) - ev.getY(1);
//                    Log.i(TAG,"两指中点坐标:("+pivotX+","+pivotY+")");
//                    Log.i(TAG,"x轴方向的长度="+deltaX);
//                    Log.i(TAG,"y轴方向的长度="+deltaY);
                    initailDegrees = (int) Math.round(Math.toDegrees(Math.atan2(deltaY, deltaX)))-targetDegree%360;
                    initialDistance = (float) Math.abs(Math.sqrt(deltaX * deltaX + deltaY * deltaY));
                    Log.v(TAG, "initailDegrees:" + initailDegrees);
                    zoomScale = lastZoomScale;
//            matrix.setRotate(degrees,originOffsetX+bitmap.getWidth()/2f,
//                    originOffsetY+bitmap.getHeight()/2f);
//            matrix.setRotate(degrees,pivotX,
//                    pivotY);
                }
            case MotionEvent.ACTION_MOVE:
                if (ev.getPointerCount() == 2) {
                    pivotX = (int) (ev.getX(0) + ev.getX(1)) / 2;
                    pivotY = (int) (ev.getY(0) + ev.getY(1)) / 2;
                    float deltaX = ev.getX(0) - ev.getX(1);
                    float deltaY = ev.getY(0) - ev.getY(1);
//                    Log.i(TAG,"两指中点坐标:("+pivotX+","+pivotY+")");
//                    Log.i(TAG,"x轴方向的长度="+deltaX);
//                    Log.i(TAG,"y轴方向的长度="+deltaY);
                    currentDegrees = (int) Math.round(Math.toDegrees(Math.atan2(deltaY, deltaX)));
                    currentDistance = (float) Math.abs(Math.sqrt(deltaX * deltaX + deltaY * deltaY));
                    Log.d(TAG, "currentDegrees:" + currentDegrees);
                    deltaDegree = currentDegrees - initailDegrees;
                    deltaDistance = currentDistance - initialDistance;
                    zoomScale = Math.max(1,1 + deltaDistance / initialDistance);
                    lastZoomScale = zoomScale;
                    Log.i(TAG, "deltaDegree:" + deltaDegree);
                    Log.i(TAG, "deltaDistance:" + deltaDistance);
                    Log.i(TAG, "zoomScale:" + zoomScale);
//            matrix.setRotate(degrees,originOffsetX+bitmap.getWidth()/2f,
//                    originOffsetY+bitmap.getHeight()/2f);
//            matrix.setRotate(degrees,pivotX,
//                    pivotY);
                    invalidate();
                    Log.e(TAG, "degrees:" + deltaDegree);
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                if (ev.getPointerCount() == 2) {
                    Log.w("ACTION_POINTER_UP", "degrees:" + deltaDegree);
                    targetDegree = computeTargetDegree(deltaDegree);
                    if(Math.abs(targetDegree)==90||Math.abs(targetDegree)==270){
                        targetScale = largeScale;
                    }else {
                        targetScale = primaryScale;
                    }
                    Log.v("ACTION_POINTER_UP", "targetDegree:" + targetDegree);
                    deltaDegree = deltaDegree%360;
                    zoomScale = lastZoomScale;
                    float r = (float) Math.sqrt(Math.pow((pivotX - getWidth() / 2f), 2) + Math.pow((pivotY - getHeight() / 2f), 2));
                    offsetX = (float) (r*(1-Math.cos(deltaDegree)));
                    offsetY = (float) (r*Math.sin(deltaDegree));
                    Log.v("degreeRange", "从"+deltaDegree+"度,到"+targetDegree+"度");
                    getRotateAnimator().start();
                    getZoomAnimator().start();
                }
                break;
        }

        return true;
    }



    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        if(isFirst.getAndSet(false)){
//            canvas.drawBitmap(bitmap, originOffsetX, originOffsetY, paint);
//        }
//        canvas.rotate(deltaDegree,pivotX,pivotY);
        Log.i("DRAW",zoomScale+"");
//        canvas.scale(zoomScale,zoomScale, pivotX, pivotY);
        canvas.rotate(deltaDegree, originOffsetX + bitmap.getWidth() / 2f, originOffsetY + bitmap.getHeight() / 2f);
//        canvas.rotate(deltaDegree, pivotX, pivotY);
        canvas.translate(offsetX, offsetY);
        canvas.save();
        canvas.drawBitmap(bitmap, originOffsetX, originOffsetY, paint);
//        canvas.drawBitmap(bitmap,matrix,paint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        originOffsetX = (getWidth() - bitmap.getWidth()) / 2f;
        originOffsetY = (getHeight() - bitmap.getHeight()) / 2f;
        matrix.setTranslate(originOffsetX, originOffsetY);
        float bitmapRatio = (float) bitmap.getWidth() / bitmap.getHeight();
        float screenRatio = (float) getWidth() / getHeight();
        if (bitmapRatio > screenRatio) {
            //图片横向铺满
            primaryScale = (float) getWidth() / bitmap.getWidth();
            largeScale = (float) getHeight() / bitmap.getHeight();
        } else {
            //图片纵向铺满
            primaryScale = (float) getHeight() / bitmap.getHeight();
            largeScale = (float) getWidth() / bitmap.getWidth();
        }
    }

    private ObjectAnimator getRotateAnimator() {
        if (rotateAnimator == null) {
            rotateAnimator = ObjectAnimator.ofInt(this, "deltaDegree", 0);
        }
        rotateAnimator.setIntValues(deltaDegree, targetDegree);
        return rotateAnimator;
    }

    private ObjectAnimator getZoomAnimator() {
        if (zoomAnimator == null) {
            zoomAnimator = ObjectAnimator.ofFloat(this, "zoomScale", 0);
        }
        zoomAnimator.setFloatValues(lastZoomScale, targetScale);
        return zoomAnimator;
    }


    private int computeTargetDegree(int degree) {
        int [] degrees = {0,90,90,180,180,270,270,360};
        degree = degree % 360;
        int index = degree/45;
        if(index>0){
            return degrees[index];
        }else {
            return -1*degrees[-1*index];
        }
    }
}

写的很赶,还有一些细节未处理,仅作为笔记参考

BrowserView

package com.hfengxiang.example.myphotoview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

import java.util.Locale;

public class BrowserView extends View {

    private static final String TAG = "BrowserView";
    private Bitmap bitmap;
    private Paint paint;
    //图片左上角坐标
    private float originOffsetX;
    private float originOffsetY;
    //图片旋转90或270度后应放大的倍数
    private float targetScale;
    //两个手指按下瞬间两指中点的坐标
    private float fingersDownMidPointX;
    private float fingersDownMidPointY;
    //两个手指移动时两点的瞬时坐标
    private float fingersMoveMidPointX;
    private float fingersMoveMidPointY;
    //两个手指距离x轴分量
    private float fingersDownPointDx;
    //两个手指距离y轴分量
    private float fingersDownPointDy;
    //第二个手指按下时两指连线与x轴的夹角
    private float pointsDownDegree;
    //第二个手指按下时两指连线的长度
    private float pointsDownDistance;
    //两指移动时连线与按下时连线之间的夹角
    private float deltaPointsLineDegree;
    private float zoomMultiple;
    private float lastLineDegree;
    private float lastZoomMultiple;

    public BrowserView(Context context) {
        this(context, null);
    }

    public BrowserView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, -1);
    }

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

    private void init(Context context) {
        paint = new Paint();
        paint.setAntiAlias(true);
        bitmap = Utils.getPhoto(getResources(), getResources().getDisplayMetrics().widthPixels);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绕屏幕左上角(0,0)旋转
//        canvas.rotate(deltaPointsLineDegree);
        //绕图片左上角旋转
//        canvas.rotate(deltaPointsLineDegree,originOffsetX,originOffsetY);
        //绕图片中心旋转
        canvas.rotate(deltaPointsLineDegree, originOffsetX+bitmap.getWidth()/2f,
                originOffsetY+bitmap.getHeight()/2f);
        //绕两指移动时连线的中点旋转
//        canvas.rotate(deltaPointsLineDegree,fingersMoveMidPointX,fingersMoveMidPointY);
        //绕按下时两指之间连线的中点旋转
//        canvas.rotate(deltaPointsLineDegree, fingersDownMidPointX, fingersDownMidPointY);
        canvas.scale(zoomMultiple,zoomMultiple,fingersDownMidPointX,fingersDownMidPointY);
        canvas.drawBitmap(bitmap, originOffsetX, originOffsetY, paint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        String log = String.format(Locale.CHINA, "onSizeChanged:w=%d,h=%s,oldw=%d,oldh=%d", w, h, oldh, oldh);
        Log.i(TAG, log);
        //将图片的左上角移动至图片中心处于屏幕中心
        originOffsetX = (getWidth() - bitmap.getWidth()) / 2f;
        originOffsetY = (getHeight() - bitmap.getHeight()) / 2f;

        //分析图片旋转90或270度后应放大的倍数
        float bitmapRatio = (float) bitmap.getWidth() / bitmap.getHeight()*1f;
        float screenRatio = (float) getWidth() / getHeight()*1f;
        if (bitmapRatio > screenRatio) {
            //图片横向铺满
            targetScale = (float) getHeight() / bitmap.getHeight()*1f;
        } else {
            //图片纵向铺满
            targetScale = (float) getWidth() / bitmap.getWidth()*1f;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_DOWN:
                //第二个及之后的手指按下会触发
                if (event.getPointerCount() == 2) {
                    fingersDownMidPointX = (event.getX(0) + event.getX(1)) / 2;
                    fingersDownMidPointY = (event.getY(0) + event.getY(1)) / 2;
                    fingersDownPointDx = event.getX(0) - event.getX(1);
                    fingersDownPointDy = event.getY(0) - event.getY(1);
                    pointsDownDegree = (int) Math.round(Math.toDegrees(Math.atan2(fingersDownPointDy, fingersDownPointDx)))-lastLineDegree;
                    pointsDownDistance = (float) Math.abs(Math.sqrt(Math.pow(fingersDownPointDx, 2) + Math.pow(fingersDownPointDy, 2)));
                    String message = String.format(Locale.CHINA, "\n第二根手指按下时信息:\n" +
                            "\t两指中点坐标:(%f,%f)\n" +
                            "\t两指距离:%fpx\n" +
                            "\t两指连线与x轴夹角%f度", fingersDownMidPointX, fingersDownMidPointY, pointsDownDistance, pointsDownDegree);
                    Log.v(TAG, message);
     
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值