Android單點觸控實現(xiàn)圖片平移、縮放、旋轉(zhuǎn)功能
相信大家使用多點對圖片進(jìn)行縮放,平移的操作很熟悉了,大部分大圖的瀏覽都具有此功能,有些app還可以對圖片進(jìn)行旋轉(zhuǎn)操作,QQ的大圖瀏覽就可以對圖片進(jìn)行旋轉(zhuǎn)操作,大家都知道對圖片進(jìn)行縮放,平移,旋轉(zhuǎn)等操作可以使用Matrix來實現(xiàn),Matrix就是一個3X3的矩陣,對圖片的處理可分為四個基礎(chǔ)變換操作,Translate(平移變換)、Rotate(旋轉(zhuǎn)變換)、Scale (縮放變換)、Skew(錯切變換),如果大家對Matrix不太了解的話可以看看這篇文章(點擊查看),作者對每一種Matrix的變換寫的很清楚,但是如果使用一個手指對圖片進(jìn)行縮放,平移,旋轉(zhuǎn)等操作大家是否了解呢,其實單手指操作跟多手指操作差不多,當(dāng)然也是使用Matrix來實現(xiàn)的,無非是在縮放比例和旋轉(zhuǎn)角度的計算上面有些不一樣,也許你會有疑問,多點操作圖片縮放旋轉(zhuǎn)是兩個手指操作,平移的時候是一個手指操作,那么你單手在圖片即平移,又縮放旋轉(zhuǎn)難道不會有沖突嗎?是的,這樣子肯定是不行的,我們必須將平移和縮放旋轉(zhuǎn)進(jìn)行分開。如下圖
圖片外面的框是一個邊框,如果我們手指觸摸的是上面的藍(lán)色小圖標(biāo)我們就對其進(jìn)行縮放旋轉(zhuǎn)操作,如果是觸摸到其他的區(qū)域我們就對其進(jìn)行平移操作,這樣就避免了上面所說的沖突問題,這里對圖片的平移操作并沒有使用Matrix來實現(xiàn),而是使用layout()方法來對其進(jìn)行位置的變換。
計算縮放比例比較簡單,使用手指移動的點到圖片所在中心點的距離除以圖片對角線的一半就是縮放比例了,接下來就計算旋轉(zhuǎn)角度,如下圖
preMove是手指移動前一個點,curMove就是當(dāng)前手指所在的點,還有一個中心點center,知道三個點求旋轉(zhuǎn)的夾角是不是很簡單呢,就是線段a和線段c的一個夾角,假設(shè)夾角為o, o的余弦值 cos o = (a * a + c * c - b * b) / (2 * a * c), 知道余弦值夾角就出來了,但是這里還有一個問題,我們在使用Matrix對圖片進(jìn)行旋轉(zhuǎn)的時候需要區(qū)別順時針旋轉(zhuǎn)還是逆時針旋轉(zhuǎn),順時針旋轉(zhuǎn)角度為正,所以上面我們只求出了旋轉(zhuǎn)的角度,并不知道是順時針還是逆時針。
具體怎么求是順時針角度還是逆時針角度呢?有些同學(xué)可能會根據(jù)curMove和ProMove的x ,y 的大小來判斷,比如上面的圖中,如果curMove.x > proMove.x則為順時針,否則為逆時針,這當(dāng)然是一種辦法,可是你想過這種方法只適合在第二象限,在第一,第三,第四象限這樣子判斷就不行了,當(dāng)然你可以判斷當(dāng)前的點在第幾象限,然后在不同的象限采用不同的判斷,這樣子判斷起來會很復(fù)雜。
有沒有更加簡單的方法來判斷呢?答案是肯定的,我們可以使用數(shù)學(xué)中的向量叉乘來判斷。假如向量A(x1, y1)和向量B(x2, y2),我們可以使用向量叉乘 |A X B| = x1*y2 - x2*y1 = |A|×|B|×sin(向量A到B的夾角), 所以這個值的正負(fù)也就是A到B旋轉(zhuǎn)角sin值的正負(fù), 順時針旋轉(zhuǎn)角度0~180,sin>0, 順時針旋轉(zhuǎn)角度180~360或者說逆時針旋轉(zhuǎn)0~180,sin<0, 所以我們可以用個center到proMove的向量 叉乘 center到curMove的向量來判斷是順時針旋轉(zhuǎn)還是逆時針旋轉(zhuǎn)。
接下來我們就開始動手實現(xiàn)此功能,我們采用一個自定義的View來實現(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>
接下來就是自定義SingleTouchView的代碼,代碼有點長,注釋還是蠻詳細(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; /** * 單手對圖片進(jìn)行縮放,旋轉(zhuǎn),平移操作,詳情請查看 * * @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)所在四個點得位置 */ 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的中心點坐標(biāo),相對于其父類布局而言的 */ 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; /** * 圖片四個點坐標(biāo) */ private Point mLTPoint; private Point mRTPoint; private Point mRBPoint; private Point mLBPoint; /** * 用于縮放,旋轉(zhuǎn)的控制點的坐標(biāo) */ private Point mControlPoint = new Point(); /** * 用于縮放,旋轉(zhuǎn)的圖標(biāo) */ private Drawable controlDrawable; /** * 縮放,旋轉(zhuǎn)圖標(biāo)的寬和高 */ private int mDrawableWidth, mDrawableHeight; /** * 畫外圍框的Path */ private Path mPath = new Path(); /** * 畫外圍框的畫筆 */ private Paint mPaint ; /** * 初始狀態(tài) */ public static final int STATUS_INIT = 0; /** * 拖動狀態(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)時x方向的偏移量 */ private int offsetX; /** * 圖片在旋轉(zhuǎn)時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所在父布局的中心點 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對象 * @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)才畫邊框和控制圖標(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); //畫旋轉(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è)置畫該圖片的起始點 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來實現(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ù), 表示為逆時針, 結(jié)果為正數(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) { // 修改中心點 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坐標(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 ; //計算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中心點的坐標(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的四個點的X的坐標(biāo)移動offsetX + halfDrawableWidth mLTPoint.x += (offsetX + halfDrawableWidth); mRTPoint.x += (offsetX + halfDrawableWidth); mRBPoint.x += (offsetX + halfDrawableWidth); mLBPoint.x += (offsetX + halfDrawableWidth); //將Bitmap的四個點的Y坐標(biāo)移動offsetY + halfDrawableHeight mLTPoint.y += (offsetY + halfDrawableHeight); mRTPoint.y += (offsetY + halfDrawableHeight); mRBPoint.y += (offsetY + halfDrawableHeight); mLBPoint.y += (offsetY + halfDrawableHeight); mControlPoint = LocationToPoint(controlLocation); } /** * 根據(jù)位置判斷控制圖標(biāo)處于那個點 * @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; } /** * 獲取變長參數(shù)最大的值 * @param array * @return */ public int getMaxValue(Integer...array){ List<Integer> list = Arrays.asList(array); Collections.sort(list); return list.get(list.size() -1); } /** * 獲取變長參數(shù)最大的值 * @param array * @return */ public int getMinValue(Integer...array){ List<Integer> list = Arrays.asList(array); Collections.sort(list); return list.get(0); } /** * 獲取旋轉(zhuǎ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; //沒旋轉(zhuǎn)之前的弧度 double originRadian = 0; //沒旋轉(zhuǎn)之前的角度 double originDegree = 0; //旋轉(zhuǎn)之后的角度 double resultDegree = 0; //旋轉(zhuǎn)之后的弧度 double resultRadian = 0; //經(jīng)過旋轉(zhuǎ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) { // 計算與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; // 角度轉(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ù)點擊的位置判斷是否點中控制旋轉(zhuǎn),縮放的圖片, 初略的計算 * @param x * @param y * @return */ private int JudgeStatus(float x, float y){ PointF touchPoint = new PointF(x, y); PointF controlPointF = new PointF(mControlPoint); //點擊的點到控制旋轉(zhuǎn),縮放點的距離 float distanceToControl = distance4PointF(touchPoint, controlPointF); //如果兩者之間的距離小于 控制圖標(biāo)的寬度,高度的最小值,則認(rè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è)置圖片中心點位置,相對于父布局而言 * @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(); } /** * 兩個點之間的距離 * @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()中來獲取,當(dāng)然如果我們不需要居中顯示我們也可以調(diào)用setCenterPoint方法來設(shè)置其位置.
onTouchEvent()方法中,mPreMovePointF和mCurMovePointF點的坐標(biāo)不是相對View來的,首先如果采用相對于View本身(getX(), getY())肯定是不行的,假如你往x軸方向移動一段距離,這個SingleTouchView也會移動一段距離,mPreMovePointF和mCurMovePointF點和SingleTouchView的中心點都是會變化的,所以在移動的時候會不停的閃爍,相對于屏幕左上角(getRawX(), getRawY())是可以的,但是由于mCenterPointF并不是相對于屏幕的坐標(biāo),而是相對于父類布局的,所以將需要將mPreMovePointF和mCurMovePointF的坐標(biāo)換算成相對于父類布局。
這里面最重要的方法就是transformDraw()方法,它主要做的是調(diào)用computeRect()方法求出圖片的四個角的坐標(biāo)點mLTPoint,mRTPoint,mRBPoint,mLBPoint(這幾點的坐標(biāo)是相對于SingleTouchView本身)和SingleTouchView的寬度和高度,以及控制圖標(biāo)所在圖標(biāo)四個點中的哪個點。如下圖
上面的圖忽略了控制旋轉(zhuǎn),縮放圖標(biāo),黑色的框是開始的View的大小,而經(jīng)過旋轉(zhuǎn)之后,VIew的大小變成最外層的虛線框了,所以我們需要調(diào)用adjustLayout()方法來重新設(shè)置View的位置和大小,接下來就是設(shè)置Matrix了
matrix.setScale(mScale, mScale); matrix.postRotate(mDegree % 360, bitmapWidth/2, bitmapHeight/2); matrix.postTranslate(offsetX + mDrawableWidth/2, offsetY + mDrawableHeight/2);
先設(shè)置縮放比例, 然后設(shè)置圍繞圖片的中心點旋轉(zhuǎn)mDegree,postTranslate( float dx, float dy)方法是畫該圖片的起始點進(jìn)行平移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只需要設(shè)置這個布局文件作為ContentView就行了,接下來運行程序看下效果。
怎么樣?效果還是不錯的吧,如果我們想去掉藍(lán)色的邊框和用于縮放旋轉(zhuǎn)的小圖標(biāo),直接調(diào)用setEditable(false)就可以了,設(shè)置了setEditable(false)該View的點擊事件,長按事件是正常的。
以上就是本文的全部內(nèi)容,希望對大家學(xué)習(xí)Android軟件編程有所幫助。
- Android自定義View實現(xiàn)豎向滑動回彈效果
- android實現(xiàn)可上下回彈的scrollview
- Android實現(xiàn)回彈ScrollView的原理
- Android自定義實現(xiàn)可回彈的ScollView
- Android自定義ScrollView實現(xiàn)阻尼回彈
- Android旋轉(zhuǎn)、平移、縮放和透明度漸變的補(bǔ)間動畫
- Android實現(xiàn)手勢滑動多點觸摸縮放平移圖片效果(二)
- Android實現(xiàn)手勢滑動多點觸摸縮放平移圖片效果
- 基于Android 實現(xiàn)圖片平移、縮放、旋轉(zhuǎn)同時進(jìn)行
- Android實現(xiàn)橡皮筋回彈和平移縮放效果
相關(guān)文章
flutter實現(xiàn)頁面多個webview的方案詳解
這篇文章主要為大家詳細(xì)介紹了flutter如何實現(xiàn)頁面多個webview的效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解下2023-09-09Android eclipse使用gradle打包的圖文教程
本文通過圖文并茂的形式給大家介紹了Android eclipse使用gradle打包的方法,需要的朋友可以參考下2018-10-10Android仿拉手網(wǎng)團(tuán)購App產(chǎn)品詳情界面效果
這篇文章主要介紹了Android仿拉手網(wǎng)團(tuán)購App產(chǎn)品詳情界面效果,代碼簡單易懂,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-05-05Android ListView分頁功能實現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了Android ListView分頁功能的實現(xiàn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-05-05