自定義Imageview控制元件實現多種手勢操作 (拖動、水平縮放、豎直縮放、等比例縮放、雙擊、長按)
阿新 • • 發佈:2019-01-05
專案中需要使用自定義控制元件的多種手勢操作,之前在網上查閱資料的時候發現能找到的一般是隻實現了其中的幾種,這次就把我做的控制元件分享一下,人人為我,我為人人嘛,哈哈!
這個自定義控制元件實現的主要功能是控制元件的拖動和縮放(注意:不是對控制元件中的圖片進行操作,話說很多帖子都把這兩個混了),其中縮放可以按照三個方向進行,就是水平、豎直和等比例。雙擊操作只做了一個提示,長按加上了一個簡單的彈出選單。
抱歉的是沒有足夠的時間寫詳細註釋了,如果跟你需要的功能相同就請直接呼叫,要是需要改程式碼就費點神自己讀懂程式碼吧,看不懂的歡迎提問
自定義控制元件程式碼:
主程式程式碼:package drag.view.ex; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.FloatMath; import android.view.MotionEvent; import android.widget.ImageView; public class EditView extends ImageView { private int maxWidth, maxHeight, minWidth, minHeight;// 極限值 private int img_H, img_W, img_X, img_Y;//實時寬高和位置資訊 private int current_Top, current_Right, current_Bottom, current_Left;// 當前圖片上下左右座標 private int start_x, start_y, current_left, current_top;// 觸控位置 private int maxLeft,maxRight,maxTop,maxBottom; private float beforeLenght, beforeLenght_X, beforeLenght_Y, afterLenght, afterLenght_X, afterLenght_Y;// 兩觸點距離 private float scale_temp;// 縮放比例 private int mode = 0;// 操作標誌:0-無操作 1-拖拽 20-等比例縮放 21-水平縮放 22-豎直縮放 private int firstX,firstY,secondX,secondY,leftInc=0,rightInc=0,topInc=0,bottomInc=0; /** 構造方法 **/ public EditView(Context context) { super(context); } public void setmaxLocation(int [] maxlocation){ this.maxLeft=maxlocation[0]; this.maxRight=maxlocation[1]; this.maxTop=maxlocation[2]; this.maxBottom=maxlocation[3]; maxWidth = this.maxRight-this.maxLeft; maxHeight = this.maxBottom-this.maxTop; minWidth = maxWidth/30; minHeight = maxHeight / 30; } /** 設定圖片寬度 **/ public void setimg_W(int img_W) { this.img_W = img_W; } /** 設定圖片高度 **/ public void setimg_H(int img_H) { this.img_H = img_H; } /** 獲取圖片寬度 **/ public int getimg_W() { return img_W; } /** 獲取圖片高度 **/ public int getimg_H() { return img_H; } /** 獲取圖片橫座標 **/ public int getimg_X() { return img_X; } /** 獲取圖片縱座標 **/ public int getimg_Y() { return img_Y; } /** 獲取圖片的操作狀態 **/ public int getMode(){ return this.mode; } public EditView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); }// 必須繼承此方法否則就會造成縮放的時候圖片消失! /*** * touch 事件 */ @Override public boolean onTouchEvent(MotionEvent event) { /** 處理單點、多點觸控 **/ switch (event.getAction() & MotionEvent.ACTION_MASK) {//多點觸控,獲取下一個動作 case MotionEvent.ACTION_DOWN: firstX=(int) event.getX(); firstY=(int) event.getY(); onTouchDown(event); postInvalidate(); break; case MotionEvent.ACTION_POINTER_DOWN:// 另一個觸控點按下 secondX=(int) event.getX(1); secondY=(int) event.getY(1); if((secondX<=this.getimg_W())&&(secondX>=0)&&(secondY>=0)&&(secondY<=this.getimg_H())){ onPointerDown(event); } break; case MotionEvent.ACTION_MOVE: onTouchMove(event); break; case MotionEvent.ACTION_UP: mode = 0; break; case MotionEvent.ACTION_POINTER_UP: mode = 0; break; } return true; } /** 按下 **/ void onTouchDown(MotionEvent event) { mode = 1; current_left = this.getLeft(); current_top = this.getTop();// 獲取的是相對於螢幕左上角的座標 start_x = (int) event.getRawX(); start_y = (int) event.getRawY();// 獲取是相對於控制元件左上角的座標 img_X = this.getLeft(); img_Y = this.getTop(); } /** 兩個手指 只能放大縮小 **/ void onPointerDown(MotionEvent event) { if (event.getPointerCount() == 2) { int x_long = (int) Math.abs(event.getX(0) - event.getX(1)); int y_long = (int) Math.abs(event.getY(0) - event.getY(1)); if (y_long == 0) y_long = 1;//分母不得為零 double xdy = x_long / y_long; if (xdy >= 2f){ int smfX=(int) (event.getX(1)-event.getX(0)); leftInc=(smfX>0)?(0):(1); rightInc=(smfX>0)?(1):(0); mode = 21; } else if (xdy <= 0.5f){ int smfY=(int) (event.getY(1)-event.getY(0)); topInc=(smfY>0)?(0):(1); bottomInc=(smfY>0)?(1):(0); mode = 22; } else{ int smfX=(int) (event.getX(1)-event.getX(0)); leftInc=(smfX>0)?(0):(1); rightInc=(smfX>0)?(1):(0); int smfY=(int) (event.getY(1)-event.getY(0)); topInc=(smfY>0)?(0):(1); bottomInc=(smfY>0)?(1):(0); mode = 20;/*根據x/y的豎直判斷縮放型別*/ } beforeLenght = getDistance(event);// 獲取兩點的距離 beforeLenght_X = getDistance_X(event);// 獲取兩點的X距離 beforeLenght_Y = getDistance_Y(event);// 獲取兩點的Y距離 } } /** 獲取兩點的距離 **/ float getDistance(MotionEvent event) { float x = firstX - event.getX(1); float y = firstY - event.getY(1); return FloatMath.sqrt(x * x + y * y); } float getDistance_X(MotionEvent event) { float x = firstX - event.getX(1); return Math.abs(x); } float getDistance_Y(MotionEvent event) { float y = firstY - event.getY(1); return Math.abs(y); } /** 移動的處理 **/ void onTouchMove(MotionEvent event) { int left = 0, top = 0, right = 0, bottom = 0; /** 處理拖動 **/ if (mode == 1) { /** 獲取相應的l,t,r ,b **/ left=current_left+(int) event.getRawX()-start_x; right=current_left+(int) event.getRawX()-start_x+img_W; top=current_top+(int) event.getRawY()-start_y; bottom=current_top+(int) event.getRawY()-start_y+img_H; start_x=(int) event.getRawX(); start_y=(int) event.getRawY(); current_left=left; current_top=top; img_H=this.getHeight(); img_W=this.getWidth(); /** 在這裡要進行判斷處理,防止在drag時候越界 **/ if (left <= maxLeft) { left = maxLeft; right = this.getWidth(); } if (right >= maxRight) { left = maxRight - this.getWidth(); right = maxRight; } if (top <= maxTop) { top = maxTop; bottom =maxTop + img_H; } if (bottom >= maxBottom) { top = maxBottom - img_H; bottom = maxBottom; } img_X = left; img_Y = top; this.setPosition(left, top, right, bottom);//更新位置 } /** 等比例縮放 **/ else if (mode == 20) { afterLenght = getDistance(event); float gapLenght = afterLenght - beforeLenght;// 變化的長度 if (Math.abs(gapLenght) > 5f) { scale_temp = afterLenght / beforeLenght;// 求的縮放的比例 this.setScale(scale_temp); beforeLenght = afterLenght; } /** 水平縮放 **/ } else if (mode == 21) { afterLenght_X = getDistance_X(event); float gapLenght_X = afterLenght_X - beforeLenght_X;// 變化的長度 if (Math.abs(gapLenght_X) > 3f) { scale_temp = afterLenght_X / beforeLenght_X;// 求的縮放的比例 this.setScale(scale_temp); beforeLenght_X = afterLenght_X; } /** 豎直縮放 **/ } else if (mode == 22) { afterLenght_Y = getDistance_Y(event); float gapLenght_Y = afterLenght_Y - beforeLenght_Y;// 變化的長度 if (Math.abs(gapLenght_Y) > 3f) { scale_temp = afterLenght_Y / beforeLenght_Y;// 求的縮放的比例 this.setScale(scale_temp); beforeLenght_Y = afterLenght_Y; } } } /** 實現處理拖動 **/ private void setPosition(int left, int top, int right, int bottom) { this.layout(left, top, right, bottom); } /** 處理縮放 **/ void setScale(float scale) { int disX = (int) (this.getWidth() * Math.abs(1 - scale)) / 4;// 獲取縮放水平距離 int disY = (int) (this.getHeight() * Math.abs(1 - scale)) / 4;// 獲取縮放垂直距離 // 放大 if (scale > 1 && this.getWidth() <= (maxWidth) && this.getHeight() <= (maxHeight)) { if (20 == mode) { if (current_Left > maxLeft) { current_Left = this.getLeft() - disX*leftInc; }else{ current_Left = this.getLeft(); } firstX+=disX*leftInc; if (current_Top > maxTop) { current_Top = this.getTop() - disY*topInc; }else{ current_Top = this.getTop(); } firstY+=disY*topInc; if (current_Right < maxRight) { current_Right = this.getRight() + disX*rightInc; }else{ current_Right = this.getRight(); } if (current_Bottom < maxBottom) { current_Bottom = this.getBottom() + disY*bottomInc; }else{ current_Bottom = this.getBottom(); } } else if (22 == mode) { current_Left = this.getLeft(); if (current_Top > maxTop) { current_Top = this.getTop() - disY*topInc; }else{ current_Top = this.getTop(); } firstY+=disY*topInc; current_Right = this.getRight(); if (current_Bottom < maxBottom) { current_Bottom = this.getBottom() + disY*bottomInc; }else{ current_Bottom = this.getBottom(); } } else if (21 == mode) { if (current_Left > maxLeft) { current_Left = this.getLeft() - disX*leftInc; }else{ current_Left = this.getLeft(); } firstX+=disX*leftInc; current_Top = this.getTop(); if (current_Right < maxRight) { current_Right = this.getRight() + disX*rightInc; }else{ current_Right = this.getRight(); } current_Bottom = this.getBottom(); } img_W = current_Right - current_Left; img_H = current_Bottom - current_Top; /* if (img_W >= layout_W) { current_Left = maxLeft;////////////////////////////////////////////////////////////// current_Right = maxRight;///////////////////////////////////////////////////////// } if (img_H >= screen_H) { current_Top = maxTop;//////////////////////////////////////////////////// current_Bottom = maxBottom;//////////////////////////////////////big }*/ if (current_Left <= maxLeft) { current_Left = maxLeft; //current_Right = img_W; } if (current_Right >= maxRight) {//////////////////////////////////////////////////////////////bigright //current_Left = layout_W - img_W; current_Right = maxRight; } if (current_Top <= maxTop) { current_Top = maxTop; //current_Bottom = img_H; } if (current_Bottom >= maxBottom) { //current_Top = screen_H - img_H; current_Bottom = maxBottom; }// ////////////防止放大控制元件時,圖片出邊界 img_X = current_Left; img_Y = current_Top; img_W = current_Right - current_Left; img_H = current_Bottom - current_Top; this.setFrame(current_Left, current_Top, current_Right, current_Bottom); postInvalidate(); /*** * 此時因為考慮到對稱,所以只做一遍判斷就可以了。 */ } // 縮小 else if (scale < 1 && this.getWidth() >= minWidth && this.getHeight() >= minHeight) { if (20 == mode) { current_Left = this.getLeft() + disX*leftInc; firstX-=disX*leftInc; current_Top = this.getTop() + disY*topInc; firstY-=disY*topInc; current_Right = this.getRight() - disX*rightInc; current_Bottom = this.getBottom() - disY*bottomInc; } else if (22 == mode) { current_Left = this.getLeft(); current_Top = this.getTop() + disY*topInc; firstY-=disY*topInc; current_Right = this.getRight(); current_Bottom = this.getBottom() - disY*bottomInc; } else if (21 == mode) { current_Left = this.getLeft() + disX*leftInc; firstX-=disX*leftInc; current_Top = this.getTop(); current_Right = this.getRight() - disX*rightInc; current_Bottom = this.getBottom(); } img_X = current_Left; img_Y = current_Top; img_W = current_Right - current_Left; img_H = current_Bottom - current_Top; this.setFrame(current_Left, current_Top, current_Right, current_Bottom); postInvalidate();//更新紅色邊框 } } }
package com.example.editdemo; import android.app.Activity; import android.os.Bundle; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.GestureDetector; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.RelativeLayout; import android.widget.Toast; import drag.view.ex.EditView; public class MainActivity extends Activity { private imgClickListener imgclicklistener = new imgClickListener(); private GestureDetector mGestureDetector;// 手勢監聽變數 private int[] maxLocation = new int[4];//控制元件移動的最大範圍:0-左 1-右 2-上 3-下 private EditView editImg; private RelativeLayout.LayoutParams editParam;// 圖片佈局資訊 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mGestureDetector = new GestureDetector(MainActivity.this, new LearnGestureListener());//註冊手勢監聽器 editImg = (EditView)findViewById(R.id.editView); initUi( );//初始化控制元件 } public void initUi(){ editImg.setFocusable(true); editImg.setClickable(true); editImg.setLongClickable(true); mGestureDetector.setIsLongpressEnabled(true);//允許雙擊長按手勢 editImg.setImageResource(R.drawable.pal1); editImg.setmaxLocation(new int[] { 0,800,0,800 }); editParam = new RelativeLayout.LayoutParams(300,200); editImg.setimg_W(300); editImg.setimg_H(200);// 將視窗高寬初始值傳給自定義控制元件 editParam.leftMargin = 200; editParam.topMargin = 200;// 視窗初始位置 editImg.setLayoutParams(editParam); registerForContextMenu(editImg);// 註冊上下文選單 editImg.setOnTouchListener(imgclicklistener);// 註冊觸控事件監聽 } private class imgClickListener implements OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_POINTER_DOWN:// 另一個觸控點按下 break; case MotionEvent.ACTION_UP: editImg.setimg_W(editImg.getWidth()); editImg.setimg_H(editImg.getHeight());//固定控制元件的高和寬 editParam = new RelativeLayout.LayoutParams( editImg.getWidth(), editImg.getHeight());//佈局的寬和高從自定義控制元件中獲取 editParam.leftMargin = editImg.getimg_X();//佈局左邊界 editParam.topMargin = editImg.getimg_Y();//佈局上邊界 editImg.setLayoutParams(editParam); break; case MotionEvent.ACTION_POINTER_UP: break; } mGestureDetector.onTouchEvent(event);// 執行雙擊和長按手勢操作 return false; } } /* 手勢監聽器 */ class LearnGestureListener extends GestureDetector.SimpleOnGestureListener { /* 雙擊操作 */ @Override public boolean onDoubleTap(MotionEvent arg0) { Toast.makeText(MainActivity.this, "雙擊操作!",Toast.LENGTH_LONG).show(); return false; } /* 長按操作 */ @Override public void onLongPress(MotionEvent ev) { editImg.showContextMenu();// 彈出上下文選單 } } /* 上下文選項選單設定 */ @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); menu.setHeaderTitle("視窗操作"); menu.add(0, 0, 0, "刪除視窗"); } /* 上下文選項選單響應 */ @Override public boolean onContextItemSelected(MenuItem item) { //執行選中選單相應的操作 return super.onContextItemSelected(item); } }