android 實現圖片旋轉,移動,縮放,並且記錄變化值,用另外一張圖片顯示出來
最近公司要做一個戒指試戴的功能,就是把戒指通過手勢移動到你指定的手指處,並且儲存狀態,方便下次進入時顯示,可以參考APP“鑽石快線”的試戴功能,
圖片網上有很多的教程教你怎麼把圖片旋轉,移動,縮放,等等,卻沒有教你儲存狀態,而且網上的教程都亂七八糟,都是複製貼上,都不是自己想要的,所以有時間自己寫一個,現在把實現步驟,程式碼,原始碼貼出來,共同學習!
* 圖片的變化主要是matrix的變化,對matrix不懂的可以先了解下matrxi.
*
* 產品試戴的地方,需要儲存戒指的位置,之前用TouchImageView(可以去網上找找,我專案裡面也自帶了)來處理,因為是別人寫好的,要理解裡面意思很難,想了很多種方法,比如儲存當前的戒指圖片位置,獲取圖片的四個點座標, 畫出正方形,在用圖片來填充正方形,後來只能去記錄移動,旋轉,縮放的每一次事件,還是沒 有解決,到了第四天用google在搜了一下類似的,總於找到一個,而且是我想要的已對角線的中 點為座標,就很方便,也不會卡,現在說下思路
*
* 1.不要在ondraw()裡面建立物件,做任何複雜的計算和操作。因為ondraw()被呼叫執行的次數 量大的驚人。所以在外面建立物件 new Matrix();new Paint ();
* 2.ontouchEvent的狀態判斷,event有很多種型別,要熟知。
* 3.已bitmap中心點座標為中心
* 4.postTranslate///偏移量,只要相加就好了,postRotate//需要計算角度,,mScale大於1就是放大,少於1就是縮小
* 5.我這裡把偏移量等資料放在本地,ShareFileUtils
* 6.在重新給戒指確定位置的時候,排序有要求按postTranslate,postRotate,postScale去做, 不然後果就很清楚了
* 7.// 旋轉,不能以當時旋轉的對角線中心點,要以最後的對角線中心位置(通過偏移量計算),因為移動過程中可能沒有去旋轉,
* //以對角線為中心,在哪裡都是可以的,所以:::: 當前的座標=偏移量+最開始的對角線的座標
佈局檔案和例項化控制元件的activity就不寫了,主要就是2個自定義的view,一個做手勢,一個顯示。
FunnyView
public class FunnyView extends View { private static final String TAG = "FUNNYVIEW"; /* * 手指按下時可能是移動 也可能是拖動 */ private static final int ZOOM = -1; private static final int DRAG = 1; private int mode = 0; // 第一個觸控點 private PointF startPointF = new PointF(); // 第二個觸控點 private PointF mCurMovePointF = new PointF(); private float mDegree; private float mScale; private Bitmap mBitmap = null; private Matrix mMatrix = null; private Paint mPaint = null; // bitmap的中心點 private PointF mCenterPoint = new PointF(); float scale_sx = 1; float scale_sy = 1; float rotate_degrees = 0; float translate_dx = 0; float translate_dy = 0; boolean isload=false;//只讓他在第二遍執行 ondraw() public FunnyView(Context context) { this(context, null, 0); } public FunnyView(Context context, AttributeSet attrs) { super(context, attrs); init(context); Log.i(TAG, "context, attrs"); } public FunnyView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } public void getPicBitmap(Bitmap bitmap) { // TODO Auto-generated method stub mBitmap=bitmap; Log.i(TAG, "getPicBitmap"); isload=true; invalidate(); } private void init(Context context) { Log.i(TAG, "init"); ShareFileUtils.setContext(context); mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.diamond); mMatrix = new Matrix(); mPaint = new Paint(); mPaint.setColor(Color.BLUE); mPaint.setStrokeWidth(10); mPaint.setAntiAlias(true); // 計算bitmap中心點座標 mCenterPoint.set(mBitmap.getWidth() / 2, mBitmap.getHeight() / 2); ShareFileUtils.setFloat("getTryDiamandWidth", mBitmap.getWidth() / 2);// 將試戴的圖片的寬高儲存在數組裡面 ShareFileUtils.setFloat("getTryDiamandHeight", mBitmap.getHeight() / 2); } @Override protected void onDraw(Canvas canvas) { if (isload) { canvas.drawBitmap(mBitmap, mMatrix, mPaint); } super.onDraw(canvas); } /* * 計算角度和縮放 * * 這裡使用餘弦定理計算 容易理解 如果對android矩陣matrix底層原理清楚的同學 可以使用matrix計算出旋轉角度和縮放量 */ private void getRotationScale() { // 角度 double a = distance4PointF(mCenterPoint, startPointF); double b = distance4PointF(startPointF, 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); PointF centerToStartMove = new PointF((startPointF.x - mCenterPoint.x), (startPointF.y - mCenterPoint.y)); PointF centerToCurMove = new PointF( (mCurMovePointF.x - mCenterPoint.x), (mCurMovePointF.y - mCenterPoint.y)); // 向量叉乘結果, 如果結果為負數, 表示為逆時針, 結果為正數表示順時針 float result = centerToStartMove.x * centerToCurMove.y - centerToStartMove.y * centerToCurMove.x; if (result < 0) { newDegree = -newDegree; } mDegree = newDegree; mScale = (float) (c / a); } /** * 弧度換算成角度 * * @return */ public static double radianToDegree(double radian) { return radian * 180 / Math.PI; } /** * 兩個點之間的距離 */ 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); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: // 注意getX()和getRawX()的區別 startPointF.set(event.getX(), event.getY()); mode = DRAG; break; case MotionEvent.ACTION_MOVE: if (mode == DRAG) { float x = event.getX() - startPointF.x; float y = event.getY() - startPointF.y; mCenterPoint.x = mCenterPoint.x + x; mCenterPoint.y = mCenterPoint.y + y; startPointF.set(event.getX(), event.getY()); mMatrix.postTranslate(x, y);// 偏移量,只要相加就好了 translate_dx = translate_dx + (x); translate_dy = translate_dy + (y); // Log.i(TAG, "x="+x+"y="+y); ShareFileUtils.setFloat("translate_dx", translate_dx);// 將Translate的位置儲存在數組裡面 ShareFileUtils.setFloat("translate_dy", translate_dy); } else if (mode == ZOOM) { mCurMovePointF.set(event.getX(), event.getY()); getRotationScale(); /* * 旋轉和縮放的中心點都是bitmap的中心點,根據需求可以更改。 */ // 旋轉 mMatrix.postRotate(mDegree, mCenterPoint.x, mCenterPoint.y);// 旋轉的角度,X,Y的座標 // 縮放 mMatrix.postScale(mScale, mScale, mCenterPoint.x,mCenterPoint.y); // mScale大於1就是放大,少於1就是縮小 scale_sx = scale_sx *mScale; scale_sy = scale_sy*mScale; // Log.i(TAG, "mScale=" + mScale + " mCenterPoint.x="+ mCenterPoint.x + "mCenterPoint.y=" + mCenterPoint.y); // Log.i(TAG, "mDegree=" + mDegree); ShareFileUtils.setFloat("scale_sx", scale_sx);// 將縮放的位置儲存在數組裡面,因為是正方形,所以x,y是一樣的 ShareFileUtils.setFloat("scale_sy", scale_sy); rotate_degrees = rotate_degrees + mDegree;//360度為一圈 ShareFileUtils.setFloat("rotate_degrees", rotate_degrees);// 將Rotate,旋轉,的位置儲存在數組裡面 // 重新賦值起始點 startPointF.set(mCurMovePointF); } break; case MotionEvent.ACTION_POINTER_DOWN: mode = ZOOM; break; case MotionEvent.ACTION_POINTER_UP: mode = 0; break; case MotionEvent.ACTION_CANCEL: mode = 0; break; } invalidate(); // 返回true 事件不再往下分發 return true; } public void bitmapRecycle() { if (mBitmap!=null&& !mBitmap.isRecycled()) { Log.i(TAG, "mBitmap.recycle()"); mBitmap.recycle(); } } }
SelectedDiamondPosition
public class SelectedDiamondPosition extends View { private static final String TAG = "SELECTEDDIAMONDPOSITION"; private float[] position; private Context context; int widthScreen; int heightScreen; Matrix matrix; Bitmap mFieldBitmap = null; boolean isload=false;//只讓他在第二遍執�? ondraw() float topHight=0;//標題欄的高度 public SelectedDiamondPosition(Context context) { super(context); Log.i(TAG, "context"); } public SelectedDiamondPosition(Context context, AttributeSet attrs,int defStyle) { super(context, attrs, defStyle); Log.i(TAG, "context, attrs, defStyle)"); } public SelectedDiamondPosition(Context context, AttributeSet attrs) { super(context, attrs); Log.i(TAG, "context, attrs"); init(context); } public void getPicBitmap(Bitmap bitmap) { // TODO Auto-generated method stub this.mFieldBitmap = bitmap; Log.i(TAG, "getPicBitmap"); isload=true; invalidate();//注意 //Invalidate()之後: //...OnPaint()->OnPrepareDC()->OnDraw() //所以只是重新整理在OnPaint()和OnDraw()函式中的繪圖語句。其它地方沒有影響。 } public void init(Context context) { Log.i(TAG, "init"); ShareFileUtils.setContext(context); DisplayMetrics dm = new DisplayMetrics(); ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(dm); widthScreen = dm.widthPixels; heightScreen = dm.heightPixels; // mFieldBitmap = BitmapFactory.decodeResource(this.getResources(), // R.drawable.diamond); } @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { try { super.onDraw(canvas); Log.i(TAG, "onDraw"); if (isload) { matrix = new Matrix();// 矩陣; 模型; // 去除鋸齒毛邊 canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)); canvas.save(); Log.i(TAG,"translate_dx_ok="+ ShareFileUtils.getFloat("translate_dx", 0)); Log.i(TAG,"translate_dy_ok="+ ShareFileUtils.getFloat("translate_dy", 0)); float scale_sx = ShareFileUtils.getFloat("scale_sx", 1); float scale_sy = ShareFileUtils.getFloat("scale_sy", 1); float rotate_degrees = ShareFileUtils.getFloat("rotate_degrees", 0); float translate_dx = ShareFileUtils.getFloat("translate_dx", 0); float translate_dy = ShareFileUtils.getFloat("translate_dy", 0); float getTryDiamandWidth = ShareFileUtils.getFloat("getTryDiamandWidth", 1); float getTryDiamandHeight = ShareFileUtils.getFloat("getTryDiamandHeight", 1); Log.i(TAG, "getTryDiamandWidth=" + getTryDiamandWidth+ "getTryDiamandHeight=" + getTryDiamandHeight); Log.i(TAG, "translate_dx=" + translate_dx + "translate_dy"+ translate_dy); Log.i(TAG, "rotate_degrees=" + rotate_degrees); Log.i(TAG, "scale_sx=" + scale_sx + "scale_sy=" + scale_sy); Log.i(TAG, "topHight="+topHight); matrix.postTranslate(translate_dx, translate_dy);// 平移 matrix.postRotate(rotate_degrees,translate_dx + getTryDiamandWidth, translate_dy+ getTryDiamandHeight); // 旋轉,不能以當時旋轉的對角線中心點,要以最後的對角線中心位置(通過偏移量計算),因為移動過程中可能沒有去旋轉, // 以對角線為中心,在哪裡都是可以的,所以:::�? 當前的坐�?=偏移�?+�?�?始的對角線的座標+標題�? matrix.postScale(scale_sx, scale_sy, translate_dx+ getTryDiamandWidth, translate_dy + getTryDiamandHeight);// 縮放,同�? // // matrix.setScale(1.75f, 1.75f);//三種不同的情�? // matrix.resRotate(15); // matrix.postTranslate(0, 0); canvas.drawBitmap(mFieldBitmap, matrix, null); canvas.restore(); } } catch (Exception e) { System.out.println("MyImageView -> onDraw() Canvas: trying to use a recycled bitmap"); } // isload=false; } public void bitmapRecycle() { if (mFieldBitmap != null && !mFieldBitmap.isRecycled()) { Log.i(TAG, "mFieldBitmap.recycle()"); mFieldBitmap.recycle(); } } }
免費專案的原始碼:下載匯入eclipse,直接執行,studio也很方便,就把程式碼拷進studio的專案就好了,之所以不用studio是因為本人在弄unity3D,unity3D專案只能匯入eclipse中.
原始碼地址:http://download.csdn.net/detail/tangpengtp/9370559
親們,給我點個贊或者評論算是對我的支援,謝謝