編輯:關於Android編程
之前寫過一篇文章Android TextView 橫豎排切換(字方向不變) 是自定義了一個LinearLayout, 實現了當然還不夠, 還要對它進行操作, 平移,旋轉 and 縮放, 相信很多小伙伴都知道對圖片的平移等等操作最好用的就是矩陣了,因為有個方法叫做imageview.setImageMatrix(matrix), 直接構造一個矩陣對象然後設置到圖片上就進行相關操作了, 那我就會想了,其他的View沒有這個方法麼, 有興趣的伙伴可以試試,結果是只有getMatrix(), 那對於不是圖片的情況呢?下面我們一起一步步的研究吧。
先列一下矩陣的基本方法, 後面也要用到的:
平移操作:matrix.postTranslate(translateX, translateY); 參數為平移的距離,而不是平移到哪!
旋轉操作:matrix.postRotate(newRotation, midP.x, midP.y); 參數為旋轉的角度以及以midP.x, midP.y 為原點進行旋轉
縮放操作:matrix.postScale(scale, scale, midP.x, midP.y); 參數為縮放的倍數以及縮放原點。
先說一下對圖片操作的思路:
首先一定要對一個imageview添加一個OnTouchListener監聽, 在MotionEvent.ACTION_DOWN(按下), MotionEvent.ACTION_MOVE(移動),MotionEvent.ACTION_UP(抬起) 主要是這三個事件, 當然還有其他的事件要監聽, 比如雙指按下然後抬起一個的時候, 如果不監聽這個事件的話那一個手指在屏幕的時候就會執行單擊事件, 後面代碼注釋我會寫到。
那根據矩陣的方法我會想到 在 手指按下 的時候我記錄當前點擊的坐標, 在手指移動的時候我計算出移動了多少, 然後matrix.postTranslate(translateX, translateY);執行平移,同理 縮放和旋轉也要計算, 然後在不斷移動的過程當中 不斷的把生成的矩陣對象設置到圖片上。我覺得描述出來就是這樣了, 接下來看下我的效果圖:
下面分析一下主要代碼, 我寫了詳細的注釋 方便查看
首先要添加一個view
private void addMyFrame() {
//新添加一個view的話要把其他的都設置成未選中, 只有新添加的是選中
for (int i = (addFrameHolders.size() - 1); i >= 0; i--) {
AddWordFrame addWordFrame = addFrameHolders.get(i).getAddWordFrame();
if (addWordFrame.isSelect()) {
addWordFrame.setSelect(false);
break;
}
}
addWordFrame = new AddWordFrame(this);
addWordFrame.setSelect(true);
//添加到屏幕上
frame.addView(addWordFrame);
layout = addWordFrame.getLayout();
addWordBitmap = BitmapUtils.convertViewToBitmap(layout);
addWordWidth = addWordBitmap.getWidth();
addWordHeight = addWordBitmap.getHeight();
//這裡是想平移到屏幕比較好看的位置
addWordx1 = width/2 - addWordWidth /2;
addWordy1 = height/3;
//這裡設置四個角的坐標是為了後續判斷是不是點擊到了刪除或者旋轉
//原圖左上角
addWordFrame.leftTop.set(addWordx1, addWordy1);
// 原圖右上角
addWordFrame.rightTop.set(addWordx1 + addWordWidth, addWordy1);
// 原圖左下角
addWordFrame.leftBottom.set(addWordx1, addWordy1 + addWordHeight);
// 原圖右下角
addWordFrame.rightBottom.set(addWordx1 + addWordWidth, addWordy1 + addWordHeight);
addWordMatrix = new Matrix();
addWordMatrix.postTranslate(addWordx1, addWordy1);
addWordFrame.setMatrix(addWordMatrix);
//這個類裡面主要是存儲當前view的區域
AddWordFrameState addWordFrameState = new AddWordFrameState();
addWordFrameState.setLeft(addWordx1);
addWordFrameState.setTop(addWordy1);
addWordFrameState.setRight(addWordx1 + addWordWidth);
addWordFrameState.setBottom(addWordy1 + addWordHeight);
AddFrameHolder addFrameHolder = new AddFrameHolder();
addFrameHolder.setAddWordFrame(addWordFrame);
addFrameHolder.setState(addWordFrameState);
addFrameHolders.add(addFrameHolder);
//設置一個監聽
addWordFrame.setOnTouchListener(new AddWordMyOntouch());
AddWordSelectImageCount = addFrameHolders.size() - 1;
}
接下來主要看下監聽的類: (這裡要是做圖片縮放代碼差不多), 在action_move裡面完成對view的平移旋轉和縮放
class AddWordMyOntouch implements View.OnTouchListener {
//倆個手指間的距離
private float baseValue = 0;
//原來的角度
private float oldRotation;
//旋轉和縮放的中點
private PointF midP;
//點中的要進行縮放的點與圖片中點的距離
private float imgLengHalf;
//保存剛開始按下的點
private PointF startPoinF = new PointF();
private int NONE = 0; // 無
private int DRAG = 1; // 移動
private int ZOOM = 2; // 變換
@Override
public boolean onTouch(View v, MotionEvent event) {
int eventaction = event.getAction();
float event_x = (int) event.getRawX();
float event_y = (int) event.getRawY() - StatusBarHeightUtil.getStatusBarHeight(context);
//這裡算是一個點擊區域值 點中刪除或者點中變換的100 * 100 的矩形區域 用這個區域來判斷是否點中
int tempInt = 100;
int addint = 100;
switch (eventaction & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: // touch down so check if the
startPoinF.set(event_x, event_y);// 保存剛開始按下的坐標
//因為可能要添加多個這樣的view 所以要按選中了哪個
selectMyFrame(event_x, event_y);
//如果有選中狀態的額view
if (AddWordSelectImageCount != -1) {
addWordFrame = addFrameHolders.get(AddWordSelectImageCount).getAddWordFrame();
addWordMatrix = addFrameHolders.get(AddWordSelectImageCount).getAddWordFrame().getMatrix();
addWordSavedMatrix.set(addWordMatrix);
AddWordMode = DRAG;
//構造一個旋轉按鈕的矩形區域
Rect moveRect = new Rect((int) addWordFrame.rightBottom.x - tempInt,
(int) addWordFrame.rightBottom.y - tempInt, (int) addWordFrame.rightBottom.x + addint,
(int) addWordFrame.rightBottom.y + addint);
//刪除按鈕的矩形區域
Rect deleteRect = new Rect((int) addWordFrame.leftTop.x - tempInt,
(int) addWordFrame.leftTop.y - tempInt, (int) addWordFrame.leftTop.x + addint,
(int) addWordFrame.leftTop.y + addint);
//如果點中了變換
if(moveRect.contains((int)event_x, (int)event_y)){
LogUtils.e("點中了變換");
// 點中了變換
midP = midPoint(addWordFrame.leftTop, addWordFrame.rightBottom);
imgLengHalf = spacing(midP, addWordFrame.rightBottom);
oldRotation = rotation(midP, startPoinF);
AddWordMode = ZOOM;
}else if (deleteRect.contains((int)event_x, (int)event_y)) {
// 點中了刪除
LogUtils.e("點中了刪除");
deleteMyFrame();
}
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
midP = midPoint(addWordFrame.leftTop, addWordFrame.rightBottom);
imgLengHalf = spacing(midP, addWordFrame.rightBottom);
oldRotation = rotationforTwo(event);
break;
case MotionEvent.ACTION_MOVE: // touch drag with the ball
//如果是雙指點中
if (event.getPointerCount() == 2) {
if (AddWordSelectImageCount != -1) {
AddWordMode = NONE;
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
float value = (float) Math.sqrt(x * x + y * y);// 計算兩點的距離
//旋轉的角度
float newRotation = rotationforTwo(event) - oldRotation;
if (baseValue == 0) {
baseValue = value;
} else {
//旋轉到一定角度再執行 不能剛點擊就執行旋轉或者縮放
if (value - baseValue >= 10 || value - baseValue <= -10) {
float scale = value / baseValue;// 當前兩點間的距離除以手指落下時兩點間的距離就是需要縮放的比例。
addWordMatrix.set(addWordSavedMatrix);
addWordMatrix.postScale(scale, scale, midP.x, midP.y);
addWordMatrix.postRotate(newRotation, midP.x, midP.y);
}
}
}
} else if (event.getPointerCount() == 1) {
//單指點擊
if (AddWordSelectImageCount != -1) {
if (AddWordMode == DRAG) {
if (event_x < MyApplication.getInstance().getScreenWidth() - 50 && event_x > 50
&& event_y > 100
&& event_y < MyApplication.getInstance().getScreenHeight() - 100) {
addWordMatrix.set(addWordSavedMatrix);
// 圖片移動的距離
float translateX = event_x - startPoinF.x;
float translateY = event_y - startPoinF.y;
addWordMatrix.postTranslate(translateX, translateY);
}
} else if (AddWordMode == ZOOM) {
//點擊到了縮放旋轉按鈕
PointF movePoin = new PointF(event_x, event_y);
float moveLeng = spacing(startPoinF, movePoin);
float newRotation = rotation(midP, movePoin) - oldRotation;
if (moveLeng > 10f) {
float moveToMidLeng = spacing(midP, movePoin);
float scale = moveToMidLeng / imgLengHalf;
addWordMatrix.set(addWordSavedMatrix);
addWordMatrix.postScale(scale, scale, midP.x, midP.y);
addWordMatrix.postRotate(newRotation, midP.x, midP.y);
}
}
}
}
if (AddWordSelectImageCount != -1) {
//最後在action_move 執行完前設置好矩陣 設置view的位置
addWordFrame = addFrameHolders.get(AddWordSelectImageCount).getAddWordFrame();
adjustLocation(addWordMatrix, addWordFrame);
}
break;
case MotionEvent.ACTION_POINTER_UP: //一只手指離開屏幕,但還有一只手指在上面會觸此事件
//什麼都沒做
AddWordMode = NONE;
break;
case MotionEvent.ACTION_UP:
AddWordMode = NONE;
break;
}
return true;
}
}
那有了添加和相關操作, 那怎麼判斷選中狀態呢 ,判斷是否選中:
private void selectMyFrame(float x, float y) {
//選取消所有的選中 後面只有點擊到的才是選中狀態
for (int i = (addFrameHolders.size() - 1); i >= 0; i--) {
AddFrameHolder addFrameHolder = addFrameHolders.get(i);
if (addFrameHolder.getAddWordFrame().isSelect()) {
addFrameHolder.getAddWordFrame().setSelect(false);
break;
}
}
for (int i = (addFrameHolders.size() - 1); i >= 0; i--) {
AddFrameHolder addFrameHolder = addFrameHolders.get(i);
//創建一個矩形區域 這裡的getLeft getTop等等的意思是當前view的最左邊 最上邊 最右邊和最下邊 只有點擊到這個區域裡面才是選中
Rect rect = new Rect((int)addFrameHolder.getState().getLeft(),
(int)addFrameHolder.getState().getTop(),
(int)addFrameHolder.getState().getRight(),
(int)addFrameHolder.getState().getBottom());
if (rect.contains((int) x, (int) y)) {
//如果選中 當前view圖層提到最上面
addFrameHolder.getAddWordFrame().bringToFront();
addFrameHolder.getAddWordFrame().setSelect(true);
//記錄選中了哪個
AddWordSelectImageCount = i;
LogUtils.e("選中");
break;
}
AddWordSelectImageCount = -1;
LogUtils.e("沒選中");
}
}
上述的代碼都非常的清晰,那麼接下來還有一個難點就是上面的代碼已經可以對普通的圖片進行平移相關操作了, 那對其他的自定義view呢? 這裡我們用到了Canvas類,大家都知道要自定義一個view裡面的內容只需要在Ondraw(Canvas canvas)裡面畫就可以了, 那我們把當前的matrix對象設置到這裡面可不可以呢 ,當然是可以的,在我的AddWordFrame類的ondraw裡面,看下代碼:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.setLayerType(View.LAYER_TYPE_HARDWARE, null);
canvas.concat(matrix);
this.setLayerType(View.LAYER_TYPE_NONE, null);
}
特別注意 ,首先canvas.concat方法, canvas還有canvas.setMatrix()方法, 當然這倆個是有區別的, concat會和之前的matrix進行計算操作,有一種疊加的感覺, 而setMatrix會替換掉之前的matrix。
另外更要注意這倆句:
this.setLayerType(View.LAYER_TYPE_HARDWARE, null);
和this.setLayerType(View.LAYER_TYPE_NONE, null);
如果不加這倆句的話平移的時候大部分幾率會出現重影想過, 小伙伴們可以注釋掉試試哈。這裡我的理解是做了一部緩存, 這個setLayerType一定要慎用! 感興趣的可以深入的研究它。
這裡其實我是對當前view所在的畫布(Canvas)進行了平移, 旋轉和縮放的動作 , 實際上是畫布在動,而不是view在動, 而圖片的矩陣縮放是imageview本身在動
如果有小伙伴有類似的需求可以留言或者在我的github上提出來:
源代碼github地址:
https://github.com/jinguangyue/AddwordDemo
package com.dchan.myweather;import java.io.UnsupportedEncodingException;import java.n
說到自定義view就不得提到LayoutInflater,雖然我們在代碼中可以直接用new方法構造出各種View,然後再添加各種屬性去控制View的大小和位置等布局,但是
主題Theme就是用來設置界面UI風格,可以設置整個應用或者某個活動Activity的界面風格。在Android SDK中內置了下面的Theme,可以按標題欄Title
目前,各種App的社區或者用戶曬照片、發說說的地方,都提供了評論功能,為了更好地學習,自己把這個功能實現了一下,做了個小的Demo。首先推薦一款實用的插件LayoutCr