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