Android單點(diǎn)觸控實(shí)現(xiàn)圖片平移、縮放、旋轉(zhuǎn)功能
相信大家使用多點(diǎn)對(duì)圖片進(jìn)行縮放,平移的操作很熟悉了,大部分大圖的瀏覽都具有此功能,有些app還可以對(duì)圖片進(jìn)行旋轉(zhuǎn)操作,QQ的大圖瀏覽就可以對(duì)圖片進(jìn)行旋轉(zhuǎn)操作,大家都知道對(duì)圖片進(jìn)行縮放,平移,旋轉(zhuǎn)等操作可以使用Matrix來(lái)實(shí)現(xiàn),Matrix就是一個(gè)3X3的矩陣,對(duì)圖片的處理可分為四個(gè)基礎(chǔ)變換操作,Translate(平移變換)、Rotate(旋轉(zhuǎn)變換)、Scale (縮放變換)、Skew(錯(cuò)切變換),如果大家對(duì)Matrix不太了解的話可以看看這篇文章(點(diǎn)擊查看),作者對(duì)每一種Matrix的變換寫(xiě)的很清楚,但是如果使用一個(gè)手指對(duì)圖片進(jìn)行縮放,平移,旋轉(zhuǎn)等操作大家是否了解呢,其實(shí)單手指操作跟多手指操作差不多,當(dāng)然也是使用Matrix來(lái)實(shí)現(xiàn)的,無(wú)非是在縮放比例和旋轉(zhuǎn)角度的計(jì)算上面有些不一樣,也許你會(huì)有疑問(wèn),多點(diǎn)操作圖片縮放旋轉(zhuǎn)是兩個(gè)手指操作,平移的時(shí)候是一個(gè)手指操作,那么你單手在圖片即平移,又縮放旋轉(zhuǎn)難道不會(huì)有沖突嗎?是的,這樣子肯定是不行的,我們必須將平移和縮放旋轉(zhuǎn)進(jìn)行分開(kāi)。如下圖

圖片外面的框是一個(gè)邊框,如果我們手指觸摸的是上面的藍(lán)色小圖標(biāo)我們就對(duì)其進(jìn)行縮放旋轉(zhuǎn)操作,如果是觸摸到其他的區(qū)域我們就對(duì)其進(jìn)行平移操作,這樣就避免了上面所說(shuō)的沖突問(wèn)題,這里對(duì)圖片的平移操作并沒(méi)有使用Matrix來(lái)實(shí)現(xiàn),而是使用layout()方法來(lái)對(duì)其進(jìn)行位置的變換。

計(jì)算縮放比例比較簡(jiǎn)單,使用手指移動(dòng)的點(diǎn)到圖片所在中心點(diǎn)的距離除以圖片對(duì)角線的一半就是縮放比例了,接下來(lái)就計(jì)算旋轉(zhuǎn)角度,如下圖

preMove是手指移動(dòng)前一個(gè)點(diǎn),curMove就是當(dāng)前手指所在的點(diǎn),還有一個(gè)中心點(diǎn)center,知道三個(gè)點(diǎn)求旋轉(zhuǎn)的夾角是不是很簡(jiǎn)單呢,就是線段a和線段c的一個(gè)夾角,假設(shè)夾角為o, o的余弦值 cos o = (a * a + c * c - b * b) / (2 * a * c), 知道余弦值夾角就出來(lái)了,但是這里還有一個(gè)問(wèn)題,我們?cè)谑褂肕atrix對(duì)圖片進(jìn)行旋轉(zhuǎn)的時(shí)候需要區(qū)別順時(shí)針旋轉(zhuǎn)還是逆時(shí)針旋轉(zhuǎn),順時(shí)針旋轉(zhuǎn)角度為正,所以上面我們只求出了旋轉(zhuǎn)的角度,并不知道是順時(shí)針還是逆時(shí)針。
具體怎么求是順時(shí)針角度還是逆時(shí)針角度呢?有些同學(xué)可能會(huì)根據(jù)curMove和ProMove的x ,y 的大小來(lái)判斷,比如上面的圖中,如果curMove.x > proMove.x則為順時(shí)針,否則為逆時(shí)針,這當(dāng)然是一種辦法,可是你想過(guò)這種方法只適合在第二象限,在第一,第三,第四象限這樣子判斷就不行了,當(dāng)然你可以判斷當(dāng)前的點(diǎn)在第幾象限,然后在不同的象限采用不同的判斷,這樣子判斷起來(lái)會(huì)很復(fù)雜。
有沒(méi)有更加簡(jiǎn)單的方法來(lái)判斷呢?答案是肯定的,我們可以使用數(shù)學(xué)中的向量叉乘來(lái)判斷。假如向量A(x1, y1)和向量B(x2, y2),我們可以使用向量叉乘 |A X B| = x1*y2 - x2*y1 = |A|×|B|×sin(向量A到B的夾角), 所以這個(gè)值的正負(fù)也就是A到B旋轉(zhuǎn)角sin值的正負(fù), 順時(shí)針旋轉(zhuǎn)角度0~180,sin>0, 順時(shí)針旋轉(zhuǎn)角度180~360或者說(shuō)逆時(shí)針旋轉(zhuǎn)0~180,sin<0, 所以我們可以用個(gè)center到proMove的向量 叉乘 center到curMove的向量來(lái)判斷是順時(shí)針旋轉(zhuǎn)還是逆時(shí)針旋轉(zhuǎn)。
接下來(lái)我們就開(kāi)始動(dòng)手實(shí)現(xiàn)此功能,我們采用一個(gè)自定義的View來(lái)實(shí)現(xiàn),這里就叫SingleTouchView,直接繼承View, 從上面的圖中我們可以定義出一些自定義的屬性,比如用于縮放的圖片,控制縮放旋轉(zhuǎn)的小圖標(biāo),圖片邊框的顏色等,我定義了如下的屬性
<declare-styleable name="SingleTouchView">
<attr name="src" format="reference" /> <!-- 用于縮放旋轉(zhuǎn)的圖標(biāo) -->
<attr name="editable" format="boolean"/> <!-- 是否處于可編輯狀態(tài) -->
<attr name="frameColor" format="color" /> <!-- 邊框顏色 -->
<attr name="frameWidth" format="dimension" /> <!-- 邊框線寬度 -->
<attr name="framePadding" format="dimension" /> <!-- 邊框與圖片的間距 -->
<attr name="degree" format="float" /> <!-- 旋轉(zhuǎn)角度 -->
<attr name="scale" format="float" /> <!-- 縮放比例 -->
<attr name="controlDrawable" format="reference"/> <!-- 控制圖標(biāo) -->
<attr name="controlLocation"> <!-- 控制圖標(biāo)的位置 -->
<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>
接下來(lái)就是自定義SingleTouchView的代碼,代碼有點(diǎn)長(zhǎng),注釋還是蠻詳細(xì)的
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;
/**
* 單手對(duì)圖片進(jìn)行縮放,旋轉(zhuǎn),平移操作,詳情請(qǐng)查看
*
* @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;
/**
* 控制縮放,旋轉(zhuǎn)圖標(biāo)所在四個(gè)點(diǎn)得位置
*/
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;
/**
* 一些默認(rèn)的常量
*/
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;
/**
* 用于旋轉(zhuǎn)縮放的Bitmap
*/
private Bitmap mBitmap;
/**
* SingleTouchView的中心點(diǎn)坐標(biāo),相對(duì)于其父類布局而言的
*/
private PointF mCenterPoint = new PointF();
/**
* View的寬度和高度,隨著圖片的旋轉(zhuǎn)而變化(不包括控制旋轉(zhuǎn),縮放圖片的寬高)
*/
private int mViewWidth, mViewHeight;
/**
* 圖片的旋轉(zhuǎn)角度
*/
private float mDegree = DEFAULT_DEGREE;
/**
* 圖片的縮放比例
*/
private float mScale = DEFAULT_SCALE;
/**
* 用于縮放,旋轉(zhuǎn),平移的矩陣
*/
private Matrix matrix = new Matrix();
/**
* SingleTouchView距離父類布局的左間距
*/
private int mViewPaddingLeft;
/**
* SingleTouchView距離父類布局的上間距
*/
private int mViewPaddingTop;
/**
* 圖片四個(gè)點(diǎn)坐標(biāo)
*/
private Point mLTPoint;
private Point mRTPoint;
private Point mRBPoint;
private Point mLBPoint;
/**
* 用于縮放,旋轉(zhuǎn)的控制點(diǎn)的坐標(biāo)
*/
private Point mControlPoint = new Point();
/**
* 用于縮放,旋轉(zhuǎn)的圖標(biāo)
*/
private Drawable controlDrawable;
/**
* 縮放,旋轉(zhuǎn)圖標(biāo)的寬和高
*/
private int mDrawableWidth, mDrawableHeight;
/**
* 畫(huà)外圍框的Path
*/
private Path mPath = new Path();
/**
* 畫(huà)外圍框的畫(huà)筆
*/
private Paint mPaint ;
/**
* 初始狀態(tài)
*/
public static final int STATUS_INIT = 0;
/**
* 拖動(dòng)狀態(tài)
*/
public static final int STATUS_DRAG = 1;
/**
* 旋轉(zhuǎn)或者放大狀態(tài)
*/
public static final int STATUS_ROTATE_ZOOM = 2;
/**
* 當(dāng)前所處的狀態(tài)
*/
private int mStatus = STATUS_INIT;
/**
* 外邊框與圖片之間的間距, 單位是dip
*/
private int framePadding = DEFAULT_FRAME_PADDING;
/**
* 外邊框顏色
*/
private int frameColor = DEFAULT_FRAME_COLOR;
/**
* 外邊框線條粗細(xì), 單位是 dip
*/
private int frameWidth = DEFAULT_FRAME_WIDTH;
/**
* 是否處于可以縮放,平移,旋轉(zhuǎn)狀態(tài)
*/
private boolean isEditable = DEFAULT_EDITABLE;
private DisplayMetrics metrics;
private PointF mPreMovePointF = new PointF();
private PointF mCurMovePointF = new PointF();
/**
* 圖片在旋轉(zhuǎn)時(shí)x方向的偏移量
*/
private int offsetX;
/**
* 圖片在旋轉(zhuǎn)時(shí)y方向的偏移量
*/
private int offsetY;
/**
* 控制圖標(biāo)所在的位置(比如左上,右上,左下,右下)
*/
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所在父布局的中心點(diǎn)
ViewGroup mViewGroup = (ViewGroup) getParent();
if(null != mViewGroup){
int parentWidth = mViewGroup.getWidth();
int parentHeight = mViewGroup.getHeight();
mCenterPoint.set(parentWidth/2, parentHeight/2);
}
}
/**
* 調(diào)整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);
}
/**
* 設(shè)置旋轉(zhuǎn)圖
* @param bitmap
*/
public void setImageBitamp(Bitmap bitmap){
this.mBitmap = bitmap;
transformDraw();
}
/**
* 設(shè)置旋轉(zhuǎn)圖
* @param drawable
*/
public void setImageDrawable(Drawable drawable){
this.mBitmap = drawable2Bitmap(drawable);
transformDraw();
}
/**
* 從Drawable中獲取Bitmap對(duì)象
* @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;
}
}
/**
* 根據(jù)id設(shè)置旋轉(zhuǎn)圖
* @param resId
*/
public void setImageResource(int resId){
Drawable drawable = getContext().getResources().getDrawable(resId);
setImageDrawable(drawable);
}
@Override
protected void onDraw(Canvas canvas) {
//每次draw之前調(diào)整View的位置和大小
super.onDraw(canvas);
if(mBitmap == null) return;
canvas.drawBitmap(mBitmap, matrix, mPaint);
//處于可編輯狀態(tài)才畫(huà)邊框和控制圖標(biāo)
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);
//畫(huà)旋轉(zhuǎn), 縮放圖標(biāo)
controlDrawable.setBounds(mControlPoint.x - mDrawableWidth / 2,
mControlPoint.y - mDrawableHeight / 2, mControlPoint.x + mDrawableWidth
/ 2, mControlPoint.y + mDrawableHeight / 2);
controlDrawable.draw(canvas);
}
adjustLayout();
}
/**
* 設(shè)置Matrix, 強(qiáng)制刷新
*/
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);
//設(shè)置縮放比例
matrix.setScale(mScale, mScale);
//繞著圖片中心進(jìn)行旋轉(zhuǎn)
matrix.postRotate(mDegree % 360, bitmapWidth/2, bitmapHeight/2);
//設(shè)置畫(huà)該圖片的起始點(diǎn)
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 ;
//圖片某個(gè)點(diǎn)到圖片中心的距離
float bitmapToCenterDistance = FloatMath.sqrt(halfBitmapWidth * halfBitmapWidth + halfBitmapHeight * halfBitmapHeight);
//移動(dòng)的點(diǎn)到圖片中心的距離
float moveToCenterDistance = distance4PointF(mCenterPoint, mCurMovePointF);
//計(jì)算縮放比例
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來(lái)實(shí)現(xiàn)
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));
//向量叉乘結(jié)果, 如果結(jié)果為負(fù)數(shù), 表示為逆時(shí)針, 結(jié)果為正數(shù)表示順時(shí)針
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) {
// 修改中心點(diǎn)
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;
}
/**
* 獲取四個(gè)點(diǎn)和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);
//計(jì)算X坐標(biāo)最大的值和最小的值
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 ;
//計(jì)算Y坐標(biāo)最大的值和最小的值
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中心點(diǎn)的坐標(biāo)
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的四個(gè)點(diǎn)的X的坐標(biāo)移動(dòng)offsetX + halfDrawableWidth
mLTPoint.x += (offsetX + halfDrawableWidth);
mRTPoint.x += (offsetX + halfDrawableWidth);
mRBPoint.x += (offsetX + halfDrawableWidth);
mLBPoint.x += (offsetX + halfDrawableWidth);
//將Bitmap的四個(gè)點(diǎn)的Y坐標(biāo)移動(dòng)offsetY + halfDrawableHeight
mLTPoint.y += (offsetY + halfDrawableHeight);
mRTPoint.y += (offsetY + halfDrawableHeight);
mRBPoint.y += (offsetY + halfDrawableHeight);
mLBPoint.y += (offsetY + halfDrawableHeight);
mControlPoint = LocationToPoint(controlLocation);
}
/**
* 根據(jù)位置判斷控制圖標(biāo)處于那個(gè)點(diǎn)
* @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;
}
/**
* 獲取變長(zhǎng)參數(shù)最大的值
* @param array
* @return
*/
public int getMaxValue(Integer...array){
List<Integer> list = Arrays.asList(array);
Collections.sort(list);
return list.get(list.size() -1);
}
/**
* 獲取變長(zhǎng)參數(shù)最大的值
* @param array
* @return
*/
public int getMinValue(Integer...array){
List<Integer> list = Arrays.asList(array);
Collections.sort(list);
return list.get(0);
}
/**
* 獲取旋轉(zhuǎn)某個(gè)角度之后的點(diǎn)
* @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;
//沒(méi)旋轉(zhuǎn)之前的弧度
double originRadian = 0;
//沒(méi)旋轉(zhuǎn)之前的角度
double originDegree = 0;
//旋轉(zhuǎn)之后的角度
double resultDegree = 0;
//旋轉(zhuǎn)之后的弧度
double resultRadian = 0;
//經(jīng)過(guò)旋轉(zhuǎn)之后點(diǎn)的坐標(biāo)
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) {
// 計(jì)算與x正方向的夾角
originRadian = Math.asin(disPoint.y / distance);
// 第二象限
} else if (disPoint.x < 0 && disPoint.y >= 0) {
// 計(jì)算與x正方向的夾角
originRadian = Math.asin(Math.abs(disPoint.x) / distance);
originRadian = originRadian + Math.PI / 2;
// 第三象限
} else if (disPoint.x < 0 && disPoint.y < 0) {
// 計(jì)算與x正方向的夾角
originRadian = Math.asin(Math.abs(disPoint.y) / distance);
originRadian = originRadian + Math.PI;
} else if (disPoint.x >= 0 && disPoint.y < 0) {
// 計(jì)算與x正方向的夾角
originRadian = Math.asin(disPoint.x / distance);
originRadian = originRadian + Math.PI * 3 / 2;
}
// 弧度換算成角度
originDegree = radianToDegree(originRadian);
resultDegree = originDegree + degree;
// 角度轉(zhuǎn)弧度
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;
}
/**
* 根據(jù)點(diǎn)擊的位置判斷是否點(diǎn)中控制旋轉(zhuǎn),縮放的圖片, 初略的計(jì)算
* @param x
* @param y
* @return
*/
private int JudgeStatus(float x, float y){
PointF touchPoint = new PointF(x, y);
PointF controlPointF = new PointF(mControlPoint);
//點(diǎn)擊的點(diǎn)到控制旋轉(zhuǎn),縮放點(diǎn)的距離
float distanceToControl = distance4PointF(touchPoint, controlPointF);
//如果兩者之間的距離小于 控制圖標(biāo)的寬度,高度的最小值,則認(rèn)為點(diǎn)中了控制圖標(biāo)
if(distanceToControl < Math.min(mDrawableWidth/2, mDrawableHeight/2)){
return STATUS_ROTATE_ZOOM;
}
return STATUS_DRAG;
}
public float getImageDegree() {
return mDegree;
}
/**
* 設(shè)置圖片旋轉(zhuǎn)角度
* @param degree
*/
public void setImageDegree(float degree) {
if(this.mDegree != degree){
this.mDegree = degree;
transformDraw();
}
}
public float getImageScale() {
return mScale;
}
/**
* 設(shè)置圖片縮放比例
* @param scale
*/
public void setImageScale(float scale) {
if(this.mScale != scale){
this.mScale = scale;
transformDraw();
};
}
public Drawable getControlDrawable() {
return controlDrawable;
}
/**
* 設(shè)置控制圖標(biāo)
* @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();
}
/**
* 設(shè)置控制圖標(biāo)的位置, 設(shè)置的值只能選擇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;
}
/**
* 設(shè)置圖片中心點(diǎn)位置,相對(duì)于父布局而言
* @param mCenterPoint
*/
public void setCenterPoint(PointF mCenterPoint) {
this.mCenterPoint = mCenterPoint;
adjustLayout();
}
public boolean isEditable() {
return isEditable;
}
/**
* 設(shè)置是否處于可縮放,平移,旋轉(zhuǎn)狀態(tài)
* @param isEditable
*/
public void setEditable(boolean isEditable) {
this.isEditable = isEditable;
invalidate();
}
/**
* 兩個(gè)點(diǎn)之間的距離
* @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居中,我們需要獲取父布局的長(zhǎng)和寬,我們?cè)趏nMeasure()中來(lái)獲取,當(dāng)然如果我們不需要居中顯示我們也可以調(diào)用setCenterPoint方法來(lái)設(shè)置其位置.
onTouchEvent()方法中,mPreMovePointF和mCurMovePointF點(diǎn)的坐標(biāo)不是相對(duì)View來(lái)的,首先如果采用相對(duì)于View本身(getX(), getY())肯定是不行的,假如你往x軸方向移動(dòng)一段距離,這個(gè)SingleTouchView也會(huì)移動(dòng)一段距離,mPreMovePointF和mCurMovePointF點(diǎn)和SingleTouchView的中心點(diǎn)都是會(huì)變化的,所以在移動(dòng)的時(shí)候會(huì)不停的閃爍,相對(duì)于屏幕左上角(getRawX(), getRawY())是可以的,但是由于mCenterPointF并不是相對(duì)于屏幕的坐標(biāo),而是相對(duì)于父類布局的,所以將需要將mPreMovePointF和mCurMovePointF的坐標(biāo)換算成相對(duì)于父類布局。
這里面最重要的方法就是transformDraw()方法,它主要做的是調(diào)用computeRect()方法求出圖片的四個(gè)角的坐標(biāo)點(diǎn)mLTPoint,mRTPoint,mRBPoint,mLBPoint(這幾點(diǎn)的坐標(biāo)是相對(duì)于SingleTouchView本身)和SingleTouchView的寬度和高度,以及控制圖標(biāo)所在圖標(biāo)四個(gè)點(diǎn)中的哪個(gè)點(diǎn)。如下圖

上面的圖忽略了控制旋轉(zhuǎn),縮放圖標(biāo),黑色的框是開(kāi)始的View的大小,而經(jīng)過(guò)旋轉(zhuǎn)之后,VIew的大小變成最外層的虛線框了,所以我們需要調(diào)用adjustLayout()方法來(lái)重新設(shè)置View的位置和大小,接下來(lái)就是設(shè)置Matrix了
matrix.setScale(mScale, mScale); matrix.postRotate(mDegree % 360, bitmapWidth/2, bitmapHeight/2); matrix.postTranslate(offsetX + mDrawableWidth/2, offsetY + mDrawableHeight/2);
先設(shè)置縮放比例, 然后設(shè)置圍繞圖片的中心點(diǎn)旋轉(zhuǎn)mDegree,postTranslate( float dx, float dy)方法是畫(huà)該圖片的起始點(diǎn)進(jìn)行平移dx, dy個(gè)單位,而不是移動(dòng)到dx,dy這個(gè)點(diǎn)。
接下來(lái)就來(lái)使用,定義一個(gè)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>
在里面寫(xiě)了一些自定義的屬性,寫(xiě)自定義屬性之前需要聲明xmlns:app="http://schemas.android.com/apk/res-auto",Activity只需要設(shè)置這個(gè)布局文件作為ContentView就行了,接下來(lái)運(yùn)行程序看下效果。

怎么樣?效果還是不錯(cuò)的吧,如果我們想去掉藍(lán)色的邊框和用于縮放旋轉(zhuǎn)的小圖標(biāo),直接調(diào)用setEditable(false)就可以了,設(shè)置了setEditable(false)該View的點(diǎn)擊事件,長(zhǎng)按事件是正常的。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家學(xué)習(xí)Android軟件編程有所幫助。
- Android自定義View實(shí)現(xiàn)豎向滑動(dòng)回彈效果
- android實(shí)現(xiàn)可上下回彈的scrollview
- Android實(shí)現(xiàn)回彈ScrollView的原理
- Android自定義實(shí)現(xiàn)可回彈的ScollView
- Android自定義ScrollView實(shí)現(xiàn)阻尼回彈
- Android旋轉(zhuǎn)、平移、縮放和透明度漸變的補(bǔ)間動(dòng)畫(huà)
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)多點(diǎn)觸摸縮放平移圖片效果(二)
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)多點(diǎn)觸摸縮放平移圖片效果
- 基于Android 實(shí)現(xiàn)圖片平移、縮放、旋轉(zhuǎn)同時(shí)進(jìn)行
- Android實(shí)現(xiàn)橡皮筋回彈和平移縮放效果
相關(guān)文章
Android實(shí)現(xiàn)圖片異步加載及本地緩存
這篇文章主要介紹了Android實(shí)現(xiàn)圖片異步加載及本地緩存的相關(guān)資料,需要的朋友可以參考下2016-02-02
Android自定義view實(shí)現(xiàn)滑動(dòng)解鎖效果
這篇文章主要為大家詳細(xì)介紹了Android自定義view實(shí)現(xiàn)滑動(dòng)解鎖效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05
Android Studio 導(dǎo)入新工程項(xiàng)目圖解
這篇文章主要介紹了Android Studio 導(dǎo)入新工程項(xiàng)目圖解,需要的朋友可以參考下2017-12-12
flutter實(shí)現(xiàn)頁(yè)面多個(gè)webview的方案詳解
這篇文章主要為大家詳細(xì)介紹了flutter如何實(shí)現(xiàn)頁(yè)面多個(gè)webview的效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解下2023-09-09
Android eclipse使用gradle打包的圖文教程
本文通過(guò)圖文并茂的形式給大家介紹了Android eclipse使用gradle打包的方法,需要的朋友可以參考下2018-10-10
Android仿拉手網(wǎng)團(tuán)購(gòu)App產(chǎn)品詳情界面效果
這篇文章主要介紹了Android仿拉手網(wǎng)團(tuán)購(gòu)App產(chǎn)品詳情界面效果,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-05-05
Android ListView分頁(yè)功能實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了Android ListView分頁(yè)功能的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05

