編輯:Android資訊
相信大家使用多點對圖片進行縮放,平移的操作很熟悉了,大部分大圖的浏覽都具有此功能,有些app還可以對圖片進行旋轉操作,QQ的大圖浏覽就可以對圖片進行旋轉操作,大家都知道對圖片進行縮放,平移,旋轉等操作可以使用Matrix來實現,Matrix就是一個3X3的矩陣,對圖片的處理可分為四個基礎變換操作,Translate(平移變換)、Rotate(旋轉變換、Scale (縮放變換)、Skew(錯切變換),如果大家對Matrix不太了解的話可以看看這篇文章(點擊查看),作者對每一種Matrix的變換寫的很清楚,但是如果使用一個手指對圖片進行縮放,平移,旋轉等操作大家是否了解呢,其實單手指操作跟多手指操作差不多,當然也是使用Matrix來實現的,無非是在縮放比例和旋轉角度的計算上面有些不一樣,也許你會有疑問,多點操作圖片縮放旋轉是兩個手指操作,平移的時候是一個手指操作,那麼你單手在圖片即平移,又縮放旋轉難道不會有沖突嗎?是的,這樣子肯定是不行的,我們必須將平移和縮放旋轉進行分開。如下圖
圖片外面的框是一個邊框,如果我們手指觸摸的是上面的藍色小圖標我們就對其進行縮放旋轉操作,如果是觸摸到其他的區域我們就對其進行平移操作,這樣就避免了上面所說的沖突問題,這裡對圖片的平移操作並沒有使用Matrix來實現,而是使用layout()方法來對其進行位置的變換。
計算縮放比例比較簡單,使用手指移動的點到圖片所在中心點的距離除以圖片對角線的一半就是縮放比例了,接下來就計算旋轉角度,如下圖
preMove是手指移動前一個點,curMove就是當前手指所在的點,還有一個中心點center,知道三個點求旋轉的夾角是不是很簡單呢,就是線段a和線段c的一個夾角,假設夾角為o, o的余弦值 cos o = (a * a + c * c - b * b) / (2 * a * c), 知道余弦值夾角就出來了,但是這裡還有一個問題,我們在使用Matrix對圖片進行旋轉的時候需要區別順時針旋轉還是逆時針旋轉,順時針旋轉角度為正,所以上面我們只求出了旋轉的角度,並不知道是順時針還是逆時針。
具體怎麼求是順時針角度還是逆時針角度呢?有些同學可能會根據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.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 = 10.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; /** * 用於旋轉縮放的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); if(srcDrawble instanceof BitmapDrawable){ BitmapDrawable bd = (BitmapDrawable) srcDrawble; this.mBitmap = bd.getBitmap(); } 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); } } /** * 設置旋轉圖 * @param bitmap */ public void setImageBitamp(Bitmap bitmap){ this.mBitmap = bitmap; transformDraw(); } /** * 設置旋轉圖 * @param drawable */ public void setImageDrawable(Drawable drawable){ if(drawable instanceof BitmapDrawable){ BitmapDrawable bd = (BitmapDrawable) drawable; this.mBitmap = bd.getBitmap(); transformDraw(); }else{ throw new NotSupportedException("SingleTouchView not support this Drawable " + drawable); } } /** * 根據id設置旋轉圖 * @param resId */ public void setImageResource(int resId){ Drawable drawable = getContext().getResources().getDrawable(resId); setImageDrawable(drawable); } @Override protected void onDraw(Canvas canvas) { //每次draw之前調整View的位置和大小 adjustLayout(); super.onDraw(canvas); if(mBitmap == null) return; canvas.drawBitmap(mBitmap, matrix, null); //處於可編輯狀態才畫邊框和控制圖標 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); } } /** * 設置Matrix, 強制刷新 */ private void transformDraw(){ 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); invalidate(); } 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; 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); } @SuppressWarnings("serial") public static class NotSupportedException extends RuntimeException{ private static final long serialVersionUID = 1674773263868453754L; public NotSupportedException() { super(); } public NotSupportedException(String detailMessage) { super(detailMessage); } } }
為了讓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>
Activity只需要設置這個布局文件作為ContentView就行了,接下來運行程序看下效果。
怎麼樣?效果還是不錯的吧,如果我們想去掉藍色的邊框和用於縮放旋轉的小圖標,直接調用setEditable(false)就可以了,設置了setEditable(false)該View的點擊事件,長按事件是正常的,今天的講解就到這裡了,有疑問的同學可以在下面留言,我會為大家解答的!項目源碼,點擊下載
對於Android開發者而言,隨著工程不斷的壯大,Android項目的編譯時間也逐漸變長,即便是有時候添加一行代碼也需要等待好久才能看見期待的效果。之前加快And
黑白棋 黑白棋,又叫蘋果棋,最早流行於西方國家。游戲通過相互翻轉對方的棋子,最後以棋盤上誰的棋子多來判斷勝負。黑白棋非常易於上手,但精通則需要考慮許多因素,比如角
Android開發人員都知道,原生的模擬器啟動比較慢,操作起來也不流暢,還會出現莫名的問題。當然很多人都會選擇直接使用android手機來開發,但是有時候需要在投
Button,就是按鈕,是android中應用最多的組件之一,Button有兩種用法,一種是XML中配置,另一種是在程序中直接使用 在XML布局文件裡,會遇到如下