Android單點觸控技術,對圖片進行平移,縮放,旋轉操作
相信大家使用多點對圖片進行縮放,平移的操作很熟悉了,大部分大圖的瀏覽都具有此功能,有些app還可以對圖片進行旋轉操作,QQ的大圖瀏覽就可以對圖片進行旋轉操作,大家都知道對圖片進行縮放,平移,旋轉等操作可以使用Matrix來實現,Matrix就是一個3X3的矩陣,對圖片的處理可分為四個基礎變換操作,Translate(平移變換)、Rotate(旋轉變換)、Scale (縮放變換)、Skew(錯切變換),如果大家對Matrix不太瞭解的話可以看看這篇文章(點選檢視),作者對每一種Matrix的變換寫的很清楚,但是如果使用一個手指對圖片進行縮放,平移,旋轉等操作大家是否瞭解呢,其實單手指操作跟多手指操作差不多,當然也是使用Matrix來實現的,無非是在縮放比例和旋轉角度的計算上面有些不一樣,也許你會有疑問,多點操作圖片縮放旋轉是兩個手指操作,平移的時候是一個手指操作,那麼你單手在圖片即平移,又縮放旋轉難道不會有衝突嗎?是的,這樣子肯定是不行的,我們必須將平移和縮放旋轉進行分開。如下圖
圖片外面的框是一個邊框,如果我們手指觸控的是上面的藍色小圖示我們就對其進行縮放旋轉操作,如果是觸控到其他的區域我們就對其進行平移操作,這樣就避免了上面所說的衝突問題,這裡對圖片的平移操作並沒有使用Matrix來實現,而是使用layout()方法來對其進行位置的變換。
計算縮放比例比較簡單,使用手指移動的點到圖片所在中心點的距離除以圖片對角線的一半就是縮放比例了,接下來就計算旋轉角度,如下圖
preMove是手指移動前一個點,curMove就是當前手指所在的點,還有一箇中心點center,知道三個點求旋轉的夾角是不是很簡單呢,就是線段a和線段c的一個夾角,假設夾角為o, o的餘弦值 cos o = (
具體怎麼求是順時針角度還是逆時針角度呢?有些同學可能會根據curMove和ProMove的x ,y 的大小來判斷,比如上面的圖中,如果curMove.x > proMove.x則為順時針,否則為逆時針,這當然是一種辦法,可是你想過這種方法只適合在第二象限,在第一,第三,第四象限這樣子判斷就不行了,當然你可以判斷當前的點在第幾象限,然後在不同的象限採用不同的判斷,這樣子判斷起來會很複雜。
有沒有更加簡單的方法來判斷呢?答案是肯定的,我們可以使用數學中的向量叉乘來判斷。假如向量A(x1, y1)和向量B(x2, y2),我們可以使用向量叉乘 |A X B| = x1*y2 - x2*y1 = |A|×|B|×sin(向量A到B的夾角), 所以這個值的正負也就是A到B旋轉角sin值的正負, 順時針旋轉角度0~180,sin>0, 順時針旋轉角度180~360或者說逆時針旋轉0~180,sin<0, 所以我們可以用個center到proMove的向量 叉乘 center到curMove的向量來判斷是順時針旋轉還是逆時針旋轉。
接下來我們就開始動手實現此功能,我們採用一個自定義的View來實現,這裡就叫SingleTouchView,直接繼承View, 從上面的圖中我們可以定義出一些自定義的屬性,比如用於縮放的圖片,控制縮放旋轉的小圖示,圖片邊框的顏色等,我定義瞭如下的屬性
<declare-styleable name="SingleTouchView">
<attr name="src" format="reference" /> <!-- 用於縮放旋轉的圖示 -->
<attr name="editable" format="boolean"/> <!-- 是否處於可編輯狀態 -->
<attr name="frameColor" format="color" /> <!-- 邊框顏色 -->
<attr name="frameWidth" format="dimension" /> <!-- 邊框線寬度 -->
<attr name="framePadding" format="dimension" /> <!-- 邊框與圖片的間距 -->
<attr name="degree" format="float" /> <!-- 旋轉角度 -->
<attr name="scale" format="float" /> <!-- 縮放比例 -->
<attr name="controlDrawable" format="reference"/> <!-- 控制圖示 -->
<attr name="controlLocation"> <!-- 控制圖示的位置 -->
<enum name="left_top" value="0" />
<enum name="right_top" value="1" />
<enum name="right_bottom" value="2" />
<enum name="left_bottom" value="3" />
</attr>
</declare-styleable>
接下來就是自定義SingleTouchView的程式碼,程式碼有點長,註釋還是蠻詳細的
package com.example.singletouchview;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.FloatMath;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
/**
* 單手對圖片進行縮放,旋轉,平移操作,詳情請檢視
*
* @blog http://blog.csdn.net/xiaanming/article/details/42833893
*
* @author xiaanming
*
*/
public class SingleTouchView extends View {
/**
* 圖片的最大縮放比例
*/
public static final float MAX_SCALE = 4.0f;
/**
* 圖片的最小縮放比例
*/
public static final float MIN_SCALE = 0.3f;
/**
* 控制縮放,旋轉圖示所在四個點得位置
*/
public static final int LEFT_TOP = 0;
public static final int RIGHT_TOP = 1;
public static final int RIGHT_BOTTOM = 2;
public static final int LEFT_BOTTOM = 3;
/**
* 一些預設的常量
*/
public static final int DEFAULT_FRAME_PADDING = 8;
public static final int DEFAULT_FRAME_WIDTH = 2;
public static final int DEFAULT_FRAME_COLOR = Color.WHITE;
public static final float DEFAULT_SCALE = 1.0f;
public static final float DEFAULT_DEGREE = 0;
public static final int DEFAULT_CONTROL_LOCATION = RIGHT_TOP;
public static final boolean DEFAULT_EDITABLE = true;
public static final int DEFAULT_OTHER_DRAWABLE_WIDTH = 50;
public static final int DEFAULT_OTHER_DRAWABLE_HEIGHT = 50;
/**
* 用於旋轉縮放的Bitmap
*/
private Bitmap mBitmap;
/**
* SingleTouchView的中心點座標,相對於其父類佈局而言的
*/
private PointF mCenterPoint = new PointF();
/**
* View的寬度和高度,隨著圖片的旋轉而變化(不包括控制旋轉,縮放圖片的寬高)
*/
private int mViewWidth, mViewHeight;
/**
* 圖片的旋轉角度
*/
private float mDegree = DEFAULT_DEGREE;
/**
* 圖片的縮放比例
*/
private float mScale = DEFAULT_SCALE;
/**
* 用於縮放,旋轉,平移的矩陣
*/
private Matrix matrix = new Matrix();
/**
* SingleTouchView距離父類佈局的左間距
*/
private int mViewPaddingLeft;
/**
* SingleTouchView距離父類佈局的上間距
*/
private int mViewPaddingTop;
/**
* 圖片四個點座標
*/
private Point mLTPoint;
private Point mRTPoint;
private Point mRBPoint;
private Point mLBPoint;
/**
* 用於縮放,旋轉的控制點的座標
*/
private Point mControlPoint = new Point();
/**
* 用於縮放,旋轉的圖示
*/
private Drawable controlDrawable;
/**
* 縮放,旋轉圖示的寬和高
*/
private int mDrawableWidth, mDrawableHeight;
/**
* 畫外圍框的Path
*/
private Path mPath = new Path();
/**
* 畫外圍框的畫筆
*/
private Paint mPaint ;
/**
* 初始狀態
*/
public static final int STATUS_INIT = 0;
/**
* 拖動狀態
*/
public static final int STATUS_DRAG = 1;
/**
* 旋轉或者放大狀態
*/
public static final int STATUS_ROTATE_ZOOM = 2;
/**
* 當前所處的狀態
*/
private int mStatus = STATUS_INIT;
/**
* 外邊框與圖片之間的間距, 單位是dip
*/
private int framePadding = DEFAULT_FRAME_PADDING;
/**
* 外邊框顏色
*/
private int frameColor = DEFAULT_FRAME_COLOR;
/**
* 外邊框線條粗細, 單位是 dip
*/
private int frameWidth = DEFAULT_FRAME_WIDTH;
/**
* 是否處於可以縮放,平移,旋轉狀態
*/
private boolean isEditable = DEFAULT_EDITABLE;
private DisplayMetrics metrics;
private PointF mPreMovePointF = new PointF();
private PointF mCurMovePointF = new PointF();
/**
* 圖片在旋轉時x方向的偏移量
*/
private int offsetX;
/**
* 圖片在旋轉時y方向的偏移量
*/
private int offsetY;
/**
* 控制圖示所在的位置(比如左上,右上,左下,右下)
*/
private int controlLocation = DEFAULT_CONTROL_LOCATION;
public SingleTouchView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SingleTouchView(Context context) {
this(context, null);
}
public SingleTouchView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
obtainStyledAttributes(attrs);
init();
}
/**
* 獲取自定義屬性
* @param attrs
*/
private void obtainStyledAttributes(AttributeSet attrs){
metrics = getContext().getResources().getDisplayMetrics();
framePadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_FRAME_PADDING, metrics);
frameWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_FRAME_WIDTH, metrics);
TypedArray mTypedArray = getContext().obtainStyledAttributes(attrs,
R.styleable.SingleTouchView);
Drawable srcDrawble = mTypedArray.getDrawable(R.styleable.SingleTouchView_src);
mBitmap = drawable2Bitmap(srcDrawble);
framePadding = mTypedArray.getDimensionPixelSize(R.styleable.SingleTouchView_framePadding, framePadding);
frameWidth = mTypedArray.getDimensionPixelSize(R.styleable.SingleTouchView_frameWidth, frameWidth);
frameColor = mTypedArray.getColor(R.styleable.SingleTouchView_frameColor, DEFAULT_FRAME_COLOR);
mScale = mTypedArray.getFloat(R.styleable.SingleTouchView_scale, DEFAULT_SCALE);
mDegree = mTypedArray.getFloat(R.styleable.SingleTouchView_degree, DEFAULT_DEGREE);
controlDrawable = mTypedArray.getDrawable(R.styleable.SingleTouchView_controlDrawable);
controlLocation = mTypedArray.getInt(R.styleable.SingleTouchView_controlLocation, DEFAULT_CONTROL_LOCATION);
isEditable = mTypedArray.getBoolean(R.styleable.SingleTouchView_editable, DEFAULT_EDITABLE);
mTypedArray.recycle();
}
private void init(){
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(frameColor);
mPaint.setStrokeWidth(frameWidth);
mPaint.setStyle(Style.STROKE);
if(controlDrawable == null){
controlDrawable = getContext().getResources().getDrawable(R.drawable.st_rotate_icon);
}
mDrawableWidth = controlDrawable.getIntrinsicWidth();
mDrawableHeight = controlDrawable.getIntrinsicHeight();
transformDraw();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取SingleTouchView所在父佈局的中心點
ViewGroup mViewGroup = (ViewGroup) getParent();
if(null != mViewGroup){
int parentWidth = mViewGroup.getWidth();
int parentHeight = mViewGroup.getHeight();
mCenterPoint.set(parentWidth/2, parentHeight/2);
}
}
/**
* 調整View的大小,位置
*/
private void adjustLayout(){
int actualWidth = mViewWidth + mDrawableWidth;
int actualHeight = mViewHeight + mDrawableHeight;
int newPaddingLeft = (int) (mCenterPoint.x - actualWidth /2);
int newPaddingTop = (int) (mCenterPoint.y - actualHeight/2);
if(mViewPaddingLeft != newPaddingLeft || mViewPaddingTop != newPaddingTop){
mViewPaddingLeft = newPaddingLeft;
mViewPaddingTop = newPaddingTop;
// layout(newPaddingLeft, newPaddingTop, newPaddingLeft + actualWidth, newPaddingTop + actualHeight);
}
layout(newPaddingLeft, newPaddingTop, newPaddingLeft + actualWidth, newPaddingTop + actualHeight);
}
/**
* 設定旋轉圖
* @param bitmap
*/
public void setImageBitamp(Bitmap bitmap){
this.mBitmap = bitmap;
transformDraw();
}
/**
* 設定旋轉圖
* @param drawable
*/
public void setImageDrawable(Drawable drawable){
this.mBitmap = drawable2Bitmap(drawable);
transformDraw();
}
/**
* 從Drawable中獲取Bitmap物件
* @param drawable
* @return
*/
private Bitmap drawable2Bitmap(Drawable drawable) {
try {
if (drawable == null) {
return null;
}
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
int intrinsicWidth = drawable.getIntrinsicWidth();
int intrinsicHeight = drawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(
intrinsicWidth <= 0 ? DEFAULT_OTHER_DRAWABLE_WIDTH
: intrinsicWidth,
intrinsicHeight <= 0 ? DEFAULT_OTHER_DRAWABLE_HEIGHT
: intrinsicHeight, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (OutOfMemoryError e) {
return null;
}
}
/**
* 根據id設定旋轉圖
* @param resId
*/
public void setImageResource(int resId){
Drawable drawable = getContext().getResources().getDrawable(resId);
setImageDrawable(drawable);
}
@Override
protected void onDraw(Canvas canvas) {
//每次draw之前調整View的位置和大小
super.onDraw(canvas);
if(mBitmap == null) return;
canvas.drawBitmap(mBitmap, matrix, mPaint);
//處於可編輯狀態才畫邊框和控制圖示
if(isEditable){
mPath.reset();
mPath.moveTo(mLTPoint.x, mLTPoint.y);
mPath.lineTo(mRTPoint.x, mRTPoint.y);
mPath.lineTo(mRBPoint.x, mRBPoint.y);
mPath.lineTo(mLBPoint.x, mLBPoint.y);
mPath.lineTo(mLTPoint.x, mLTPoint.y);
mPath.lineTo(mRTPoint.x, mRTPoint.y);
canvas.drawPath(mPath, mPaint);
//畫旋轉, 縮放圖示
controlDrawable.setBounds(mControlPoint.x - mDrawableWidth / 2,
mControlPoint.y - mDrawableHeight / 2, mControlPoint.x + mDrawableWidth
/ 2, mControlPoint.y + mDrawableHeight / 2);
controlDrawable.draw(canvas);
}
adjustLayout();
}
/**
* 設定Matrix, 強制重新整理
*/
private void transformDraw(){
if(mBitmap == null) return;
int bitmapWidth = (int)(mBitmap.getWidth() * mScale);
int bitmapHeight = (int)(mBitmap.getHeight()* mScale);
computeRect(-framePadding, -framePadding, bitmapWidth + framePadding, bitmapHeight + framePadding, mDegree);
//設定縮放比例
matrix.setScale(mScale, mScale);
//繞著圖片中心進行旋轉
matrix.postRotate(mDegree % 360, bitmapWidth/2, bitmapHeight/2);
//設定畫該圖片的起始點
matrix.postTranslate(offsetX + mDrawableWidth/2, offsetY + mDrawableHeight/2);
adjustLayout();
}
public boolean onTouchEvent(MotionEvent event) {
if(!isEditable){
return super.onTouchEvent(event);
}
switch (event.getAction() ) {
case MotionEvent.ACTION_DOWN:
mPreMovePointF.set(event.getX() + mViewPaddingLeft, event.getY() + mViewPaddingTop);
mStatus = JudgeStatus(event.getX(), event.getY());
break;
case MotionEvent.ACTION_UP:
mStatus = STATUS_INIT;
break;
case MotionEvent.ACTION_MOVE:
mCurMovePointF.set(event.getX() + mViewPaddingLeft, event.getY() + mViewPaddingTop);
if (mStatus == STATUS_ROTATE_ZOOM) {
float scale = 1f;
int halfBitmapWidth = mBitmap.getWidth() / 2;
int halfBitmapHeight = mBitmap.getHeight() /2 ;
//圖片某個點到圖片中心的距離
float bitmapToCenterDistance = FloatMath.sqrt(halfBitmapWidth * halfBitmapWidth + halfBitmapHeight * halfBitmapHeight);
//移動的點到圖片中心的距離
float moveToCenterDistance = distance4PointF(mCenterPoint, mCurMovePointF);
//計算縮放比例
scale = moveToCenterDistance / bitmapToCenterDistance;
//縮放比例的界限判斷
if (scale <= MIN_SCALE) {
scale = MIN_SCALE;
} else if (scale >= MAX_SCALE) {
scale = MAX_SCALE;
}
// 角度
double a = distance4PointF(mCenterPoint, mPreMovePointF);
double b = distance4PointF(mPreMovePointF, mCurMovePointF);
double c = distance4PointF(mCenterPoint, mCurMovePointF);
double cosb = (a * a + c * c - b * b) / (2 * a * c);
if (cosb >= 1) {
cosb = 1f;
}
double radian = Math.acos(cosb);
float newDegree = (float) radianToDegree(radian);
//center -> proMove的向量, 我們使用PointF來實現
PointF centerToProMove = new PointF((mPreMovePointF.x - mCenterPoint.x), (mPreMovePointF.y - mCenterPoint.y));
//center -> curMove 的向量
PointF centerToCurMove = new PointF((mCurMovePointF.x - mCenterPoint.x), (mCurMovePointF.y - mCenterPoint.y));
//向量叉乘結果, 如果結果為負數, 表示為逆時針, 結果為正數表示順時針
float result = centerToProMove.x * centerToCurMove.y - centerToProMove.y * centerToCurMove.x;
if (result < 0) {
newDegree = -newDegree;
}
mDegree = mDegree + newDegree;
mScale = scale;
transformDraw();
}
else if (mStatus == STATUS_DRAG) {
// 修改中心點
mCenterPoint.x += mCurMovePointF.x - mPreMovePointF.x;
mCenterPoint.y += mCurMovePointF.y - mPreMovePointF.y;
System.out.println(this + "move = " + mCenterPoint);
adjustLayout();
}
mPreMovePointF.set(mCurMovePointF);
break;
}
return true;
}
/**
* 獲取四個點和View的大小
* @param left
* @param top
* @param right
* @param bottom
* @param degree
*/
private void computeRect(int left, int top, int right, int bottom, float degree){
Point lt = new Point(left, top);
Point rt = new Point(right, top);
Point rb = new Point(right, bottom);
Point lb = new Point(left, bottom);
Point cp = new Point((left + right) / 2, (top + bottom) / 2);
mLTPoint = obtainRoationPoint(cp, lt, degree);
mRTPoint = obtainRoationPoint(cp, rt, degree);
mRBPoint = obtainRoationPoint(cp, rb, degree);
mLBPoint = obtainRoationPoint(cp, lb, degree);
//計算X座標最大的值和最小的值
int maxCoordinateX = getMaxValue(mLTPoint.x, mRTPoint.x, mRBPoint.x, mLBPoint.x);
int minCoordinateX = getMinValue(mLTPoint.x, mRTPoint.x, mRBPoint.x, mLBPoint.x);;
mViewWidth = maxCoordinateX - minCoordinateX ;
//計算Y座標最大的值和最小的值
int maxCoordinateY = getMaxValue(mLTPoint.y, mRTPoint.y, mRBPoint.y, mLBPoint.y);
int minCoordinateY = getMinValue(mLTPoint.y, mRTPoint.y, mRBPoint.y, mLBPoint.y);
mViewHeight = maxCoordinateY - minCoordinateY ;
//View中心點的座標
Point viewCenterPoint = new Point((maxCoordinateX + minCoordinateX) / 2, (maxCoordinateY + minCoordinateY) / 2);
offsetX = mViewWidth / 2 - viewCenterPoint.x;
offsetY = mViewHeight / 2 - viewCenterPoint.y;
int halfDrawableWidth = mDrawableWidth / 2;
int halfDrawableHeight = mDrawableHeight /2;
//將Bitmap的四個點的X的座標移動offsetX + halfDrawableWidth
mLTPoint.x += (offsetX + halfDrawableWidth);
mRTPoint.x += (offsetX + halfDrawableWidth);
mRBPoint.x += (offsetX + halfDrawableWidth);
mLBPoint.x += (offsetX + halfDrawableWidth);
//將Bitmap的四個點的Y座標移動offsetY + halfDrawableHeight
mLTPoint.y += (offsetY + halfDrawableHeight);
mRTPoint.y += (offsetY + halfDrawableHeight);
mRBPoint.y += (offsetY + halfDrawableHeight);
mLBPoint.y += (offsetY + halfDrawableHeight);
mControlPoint = LocationToPoint(controlLocation);
}
/**
* 根據位置判斷控制圖示處於那個點
* @return
*/
private Point LocationToPoint(int location){
switch(location){
case LEFT_TOP:
return mLTPoint;
case RIGHT_TOP:
return mRTPoint;
case RIGHT_BOTTOM:
return mRBPoint;
case LEFT_BOTTOM:
return mLBPoint;
}
return mLTPoint;
}
/**
* 獲取變長引數最大的值
* @param array
* @return
*/
public int getMaxValue(Integer...array){
List<Integer> list = Arrays.asList(array);
Collections.sort(list);
return list.get(list.size() -1);
}
/**
* 獲取變長引數最大的值
* @param array
* @return
*/
public int getMinValue(Integer...array){
List<Integer> list = Arrays.asList(array);
Collections.sort(list);
return list.get(0);
}
/**
* 獲取旋轉某個角度之後的點
* @param viewCenter
* @param source
* @param degree
* @return
*/
public static Point obtainRoationPoint(Point center, Point source, float degree) {
//兩者之間的距離
Point disPoint = new Point();
disPoint.x = source.x - center.x;
disPoint.y = source.y - center.y;
//沒旋轉之前的弧度
double originRadian = 0;
//沒旋轉之前的角度
double originDegree = 0;
//旋轉之後的角度
double resultDegree = 0;
//旋轉之後的弧度
double resultRadian = 0;
//經過旋轉之後點的座標
Point resultPoint = new Point();
double distance = Math.sqrt(disPoint.x * disPoint.x + disPoint.y * disPoint.y);
if (disPoint.x == 0 && disPoint.y == 0) {
return center;
// 第一象限
} else if (disPoint.x >= 0 && disPoint.y >= 0) {
// 計算與x正方向的夾角
originRadian = Math.asin(disPoint.y / distance);
// 第二象限
} else if (disPoint.x < 0 && disPoint.y >= 0) {
// 計算與x正方向的夾角
originRadian = Math.asin(Math.abs(disPoint.x) / distance);
originRadian = originRadian + Math.PI / 2;
// 第三象限
} else if (disPoint.x < 0 && disPoint.y < 0) {
// 計算與x正方向的夾角
originRadian = Math.asin(Math.abs(disPoint.y) / distance);
originRadian = originRadian + Math.PI;
} else if (disPoint.x >= 0 && disPoint.y < 0) {
// 計算與x正方向的夾角
originRadian = Math.asin(disPoint.x / distance);
originRadian = originRadian + Math.PI * 3 / 2;
}
// 弧度換算成角度
originDegree = radianToDegree(originRadian);
resultDegree = originDegree + degree;
// 角度轉弧度
resultRadian = degreeToRadian(resultDegree);
resultPoint.x = (int) Math.round(distance * Math.cos(resultRadian));
resultPoint.y = (int) Math.round(distance * Math.sin(resultRadian));
resultPoint.x += center.x;
resultPoint.y += center.y;
return resultPoint;
}
/**
* 弧度換算成角度
*
* @return
*/
public static double radianToDegree(double radian) {
return radian * 180 / Math.PI;
}
/**
* 角度換算成弧度
* @param degree
* @return
*/
public static double degreeToRadian(double degree) {
return degree * Math.PI / 180;
}
/**
* 根據點選的位置判斷是否點中控制旋轉,縮放的圖片, 初略的計算
* @param x
* @param y
* @return
*/
private int JudgeStatus(float x, float y){
PointF touchPoint = new PointF(x, y);
PointF controlPointF = new PointF(mControlPoint);
//點選的點到控制旋轉,縮放點的距離
float distanceToControl = distance4PointF(touchPoint, controlPointF);
//如果兩者之間的距離小於 控制圖示的寬度,高度的最小值,則認為點中了控制圖示
if(distanceToControl < Math.min(mDrawableWidth/2, mDrawableHeight/2)){
return STATUS_ROTATE_ZOOM;
}
return STATUS_DRAG;
}
public float getImageDegree() {
return mDegree;
}
/**
* 設定圖片旋轉角度
* @param degree
*/
public void setImageDegree(float degree) {
if(this.mDegree != degree){
this.mDegree = degree;
transformDraw();
}
}
public float getImageScale() {
return mScale;
}
/**
* 設定圖片縮放比例
* @param scale
*/
public void setImageScale(float scale) {
if(this.mScale != scale){
this.mScale = scale;
transformDraw();
};
}
public Drawable getControlDrawable() {
return controlDrawable;
}
/**
* 設定控制圖示
* @param drawable
*/
public void setControlDrawable(Drawable drawable) {
this.controlDrawable = drawable;
mDrawableWidth = drawable.getIntrinsicWidth();
mDrawableHeight = drawable.getIntrinsicHeight();
transformDraw();
}
public int getFramePadding() {
return framePadding;
}
public void setFramePadding(int framePadding) {
if(this.framePadding == framePadding)
return;
this.framePadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, framePadding, metrics);
transformDraw();
}
public int getFrameColor() {
return frameColor;
}
public void setFrameColor(int frameColor) {
if(this.frameColor == frameColor)
return;
this.frameColor = frameColor;
mPaint.setColor(frameColor);
invalidate();
}
public int getFrameWidth() {
return frameWidth;
}
public void setFrameWidth(int frameWidth) {
if(this.frameWidth == frameWidth)
return;
this.frameWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, frameWidth, metrics);
mPaint.setStrokeWidth(frameWidth);
invalidate();
}
/**
* 設定控制圖示的位置, 設定的值只能選擇LEFT_TOP ,RIGHT_TOP, RIGHT_BOTTOM,LEFT_BOTTOM
* @param controlLocation
*/
public void setControlLocation(int location) {
if(this.controlLocation == location)
return;
this.controlLocation = location;
transformDraw();
}
public int getControlLocation() {
return controlLocation;
}
public PointF getCenterPoint() {
return mCenterPoint;
}
/**
* 設定圖片中心點位置,相對於父佈局而言
* @param mCenterPoint
*/
public void setCenterPoint(PointF mCenterPoint) {
this.mCenterPoint = mCenterPoint;
adjustLayout();
}
public boolean isEditable() {
return isEditable;
}
/**
* 設定是否處於可縮放,平移,旋轉狀態
* @param isEditable
*/
public void setEditable(boolean isEditable) {
this.isEditable = isEditable;
invalidate();
}
/**
* 兩個點之間的距離
* @param x1
* @param y1
* @param x2
* @param y2
* @return
*/
private float distance4PointF(PointF pf1, PointF pf2) {
float disX = pf2.x - pf1.x;
float disY = pf2.y - pf1.y;
return FloatMath.sqrt(disX * disX + disY * disY);
}
}
為了讓SingleTouchView居中,我們需要獲取父佈局的長和寬,我們在onMeasure()中來獲取,當然如果我們不需要居中顯示我們也可以呼叫setCenterPoint方法來設定其位置.
onTouchEvent()方法中,mPreMovePointF和mCurMovePointF點的座標不是相對View來的,首先如果採用相對於View本身(getX(), getY())肯定是不行的,假如你往x軸方向移動一段距離,這個SingleTouchView也會移動一段距離,mPreMovePointF和mCurMovePointF點和SingleTouchView的中心點都是會變化的,所以在移動的時候會不停的閃爍,相對於螢幕左上角(getRawX(), getRawY())是可以的,但是由於mCenterPointF並不是相對於螢幕的座標,而是相對於父類佈局的,所以將需要將mPreMovePointF和mCurMovePointF的座標換算成相對於父類佈局。
這裡面最重要的方法就是transformDraw()方法,它主要做的是呼叫computeRect()方法求出圖片的四個角的座標點mLTPoint,mRTPoint,mRBPoint,mLBPoint(這幾點的座標是相對於SingleTouchView本身)和SingleTouchView的寬度和高度,以及控制圖示所在圖示四個點中的哪個點。如下圖
上面的圖忽略了控制旋轉,縮放圖示,黑色的框是開始的View的大小,而經過旋轉之後,VIew的大小變成最外層的虛線框了,所以我們需要呼叫adjustLayout()方法來重新設定View的位置和大小,接下來就是設定Matrix了
matrix.setScale(mScale, mScale);
matrix.postRotate(mDegree % 360, bitmapWidth/2, bitmapHeight/2);
matrix.postTranslate(offsetX + mDrawableWidth/2, offsetY + mDrawableHeight/2);
先設定縮放比例, 然後設定圍繞圖片的中心點旋轉mDegree,postTranslate(float dx, float dy)方法是畫該圖片的起始點進行平移dx, dy個單位,而不是移動到dx,dy這個點。
接下來就來使用,定義一個xml佈局檔案
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<com.example.singletouchview.SingleTouchView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/SingleTouchView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:scale="1.2"
app:src="@drawable/scale"
app:frameColor="#0022ff"
app:controlLocation="right_top"/>
</merge>
在裡面寫了一些自定義的屬性,寫自定義屬性之前需要宣告xmlns:app="http://schemas.android.com/apk/res-auto",Activity只需要設定這個佈局檔案作為ContentView就行了,接下來執行程式看下效果。
怎麼樣?效果還是不錯的吧,如果我們想去掉藍色的邊框和用於縮放旋轉的小圖示,直接呼叫setEditable(false)就可以了,設定了setEditable(false)該View的點選事件,長按事件是正常的,今天的講解就到這裡了,有疑問的同學可以在下面留言,我會為大家解答的!
連結中的原始碼對於動態的新增多個SingleTouchView存在位置的問題,部落格中的原始碼對其進行了修改,大家可以拷貝部落格中的原始碼來使用!!!!!