基于安卓平臺的手勢操控組件 PinchImageView-ohos,實現鴻蒙的功能化遷移和重構。代碼已經開源,歡迎各位開發者提出寶貴意見。
開源地址:
https://gitee.com/isrc_ohos/pinch-image-view-ohos
PinchImageView-ohos 是一個支持多點觸控的 ImageView 手勢操控組件,通過識別單指雙擊、雙指捏合、單指滑動等手勢指令,實現圖片的放大、縮小、滑動等效果。
該組件功能豐富且使用簡單,被廣泛應用于各類圖片預覽類應用。
01
組件效果展示
①雙指相向或相對捏合,實現圖片的縮放變化。
②單指雙擊實現圖片的放大縮小。
③單指雙擊后單指移動,實現圖片的放大后平移。
02
Sample 解析
Sample 部分主要負責整體顯示布局的搭建。首先為 PinchImageView-ohos 組件設置顯示圖片,然后將組件對象添加到顯示布局中。
下面將詳細介紹組件的使用方法:
步驟 1:創建整體的顯示布局。
步驟 2:導入相關類并實例化 PinchImageView-ohos 組件對象。
步驟 3:設置顯示圖片。
步驟 4:將 PinchImageView-ohos 組件對象添加到整體顯示布局中。
//步驟1 創建整體的顯示布局
DirectionalLayout directionalLayout = new DirectionalLayout(this);
//步驟2 導入相關類并實例化對象
PinchImageViewnew pinchImageView = new PinchImageViewnew(this);
//步驟3 設置顯示圖片
pinchImageView.setPixelMap(this, ResourceTable.Media_1111);
//步驟4 將pinchImageView添加到整體顯示布局中
directionalLayout.addComponent(pinchImageView);
setUIContent(directionalLayout);
03
Library 解析
Library 主要為 PinchImageView-ohos 組件實現手勢獲取功能和圖片操控功能。
開發者通過設置監聽器來捕捉各類手勢,根據不同的手勢執行不同的圖片操控方法,從而顯示不同的圖片操控效果,如放大、縮小、移動。
①手勢獲取方法
手勢獲取對實現 PinchImageView-ohos 組件的功能尤為重要,此處主要通過 onTouchEvent() 方法來捕捉對應的手勢。
主要用到的手勢包含:
PRIMARY_POINT_UP(最后一根手指從屏幕上抬起)
PRIMARY_POINT_DOWN(第一根手指觸摸屏幕)
OTHER_POINT_DOWN(當一根或多根手指已經觸摸屏幕時,另一個手指觸摸屏幕 )
OTHER_POINT_UP(一些手指從屏幕上抬起,而一些手指仍留在屏幕上 )
POINT_MOVE(手指在屏幕上移動)
通過監控各類手勢的操作順序和觸碰時間等條件,達到識別捏合、滑動、單擊、雙擊等復雜手勢的效果。
onTouchEvent() 函數首先通過 TouchEvent.getAction() 方法獲取當前的手勢,當手勢為:
(1)PRIMARY_POINT_UP
需要判斷圖片之前是否處于縮放模式(此時圖片處于縮放狀態)。如果是縮放模式,則觸發結束縮放動畫,后將手勢狀態置于自由模式。
//最后一個點抬起或者取消,結束所有模式if (action == TouchEvent.PRIMARY_POINT_UP || action == TouchEvent.CANCEL) {
//如果之前是縮放模式,還需要結束縮放動畫
if (mPinchMode == PINCH_MODE_SCALE) {
scaleEnd();//縮放結束
}
//手勢狀態置于自由模式
mPinchMode = PINCH_MODE_FREE;
}
(2)PRIMARY_POINT_DOWN
需要判斷圖片是否在縮放動畫中,若不在,圖片將切換到滾動模式(此時圖片處于可自由移動狀態),并保存觸發點的位置,用于(5)中的計算。
else if (action == TouchEvent.PRIMARY_POINT_DOWN) {
//在縮放動畫過程中不允許啟動滾動模式
if (?。╩ScaleAnimator != null && mScaleAnimator.isRunning())) {
//在動畫過程中不允許啟動滾動模式,停止所有動畫
cancelAllAnimator();
//切換到滾動模式
mPinchMode = PINCH_MODE_SCROLL;
//保存觸發點的位置用于(5)中的計算
mLastMovePoint.modify(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY());
}
}
(3)OTHER_POINT_DOWN
需要將圖片模式切換到縮放模式,并保存兩個觸發點的位置,用于(5)中的計算。
else if (action == TouchEvent.OTHER_POINT_DOWN) {
//在動畫過程中不允許啟動縮放模式,停止所有動畫
cancelAllAnimator();
//切換到縮放模式
mPinchMode = PINCH_MODE_SCALE;
//保存縮放的兩個觸發點的位置,用于(5)中的計算
saveScaleContext(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY(), event.getPointerPosition(1).getX(), event.getPointerPosition(1).getY());
}
(4)OTHER_POINT_UP
需要判斷手指抬起后圖片是否處于縮放模式。如果處于縮放模式下,判斷識別到的手指是否超過兩個。
在剩余手指超過兩個(縮放模式未結束)的情況下,第一個觸摸的手指抬起,那么讓第二個觸摸的手指和第三個觸摸的手指所在的點作為縮放控制點。
在剩余手指超過兩個(縮放模式未結束)的情況下,第二個觸摸的手指抬起,那么讓第一個觸摸的手指和第三個觸摸的手指所在的點作為縮放控制點。
如果處于縮放模式下,判斷識別到的手指只有一個。此時不能允許它切換到滾動模式,因為圖片可能沒有在初始的位置上。
手指抬起后圖片未處于縮放模式時(屏幕上僅剩余一個手指),開啟滾動模式,并記錄開始滾動的點。
else if (action == TouchEvent.OTHER_POINT_UP) {
//多個手指情況下抬起一個手指,此時需要是縮放模式才觸發
if (mPinchMode == PINCH_MODE_SCALE) {
//抬起的點如果大于2,那么縮放模式還有效,但是有可能初始點變了,重新測量初始點
if (event.getPointerCount() 》 2) {
//如果還沒結束縮放模式,但是第一個點抬起了,那么讓第二個點和第三個點作為縮放控制點
if (event.getAction() 》》 8 == 0) {
event.getPointerPosition(1).getX();
saveScaleContext(event.getPointerPosition(1).getX(), event.getPointerPosition(1).getY(), event.getPointerPosition(2).getX(), event.getPointerPosition(2).getY());
//如果還沒結束縮放模式,但是第二個點抬起了,那么讓第一個點和第三個點作為縮放控制點
} else if (event.getAction() 》》 8 == 1) {
saveScaleContext(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY(), event.getPointerPosition(2).getX(), event.getPointerPosition(2).getY());
}
}
//如果抬起的點等于2,那么此時只剩下一個點,也不允許進入單指模式,因為此時可能圖片沒有在正確的位置上
}
}
(5)POINT_MOVE
需要判斷當前圖片的模式。當為滾動模式時,執行 scrollBy() 方法來實現圖片的移動效果;當它為縮放模式時,計算兩個縮放點的距離和縮放點的中心,并執行 scale() 方法實現圖片的縮放效果。
scrollBy() 方法和 scale() 方法的具體邏輯在圖片操控方法中有詳細介紹,此處就不做過多贅述。
else if (action == TouchEvent.POINT_MOVE) {
if (?。╩ScaleAnimator != null && mScaleAnimator.isRunning())) {
//在滾動模式下移動
if (mPinchMode == PINCH_MODE_SCROLL) {
//每次移動產生一個差值累積到圖片位置上
scrollBy(event.getPointerPosition(0).getX() - mLastMovePoint.position[0], event.getPointerPosition(0).getY() - mLastMovePoint.position[1]);
//記錄新的移動點
mLastMovePoint.modify(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY());
//在縮放模式下移動
} else if (mPinchMode == PINCH_MODE_SCALE && event.getPointerCount() 》 1) {
//兩個縮放點間的距離
float distance = MathUtils.getDistance(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY(), event.getPointerPosition(1).getX(), event.getPointerPosition(1).getY());
//保存縮放點中心
float[] lineCenter = MathUtils.getCenterPoint(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY(), event.getPointerPosition(1).getX(), event.getPointerPosition(1).getY());
mLastMovePoint.modify(lineCenter[0], lineCenter[1]);
//處理縮放
scale(mScaleCenter, mScaleBase, distance, mLastMovePoint);
}
}
}
②圖片操控方法
1、圖片縮放
雙指捏合:顧名思義是表示兩根手指向相反方向移動的操作,該操作可實現圖片放大縮小的效果。雙指捏合完成圖片縮放的功能是由 scale() 方法實現的。
在 scale() 方法體中需要設置各種縮放參數:scaleBase 是縮放系數、scaleCenter 代表圖片縮放中點、distance 指兩指間距離、lineCenter 是兩指中點。
scaleBase 和 distance 相乘會得到縮放比例,圖片依舊縮放比例進行變化。
在縮放過程中,圖片縮放中點 scaleCenter 會跟隨兩指中點 lineCenter 移動,實現以兩指中點為中心對圖片進行放大縮小的效果,縮放效果如圖 4 所示。圖 4:圖片雙指縮放
private void scale(Point scaleCenter, float scaleBase, float distance, Point lineCenter) {
if (!isReady()) {
return;
}
//計算圖片從fit center狀態到目標狀態的縮放比例
float scale = scaleBase * distance;
Matrix matrix = MathUtils.matrixTake();
//按照圖片縮放中心縮放,并且讓縮放中心在縮放點中點上
matrix.postScale(scale, scale, scaleCenter.position[0], scaleCenter.position[1]);
//讓圖片的縮放中點跟隨手指縮放中點
matrix.postTranslate(lineCenter.position[0] - scaleCenter.position[0], lineCenter.position[1] - scaleCenter.position[1]);
//應用變換
mOuterMatrix.setMatrix(matrix);
MathUtils.matrixGiven(matrix);
dispatchOuterMatrixChanged();
//重繪
invalidate();
}
單指雙擊:表示用單根手指雙擊屏幕的操作,該操作可實現圖片放大縮小的效果,單指雙擊完成圖片縮放的功能是由 doubleTap() 方法實現的。
在 doubleTap() 方法體中我們初始化了一個縮放動畫的對象 mScaleAnimator(),它有兩個參數分別為 mOuterMatrix(開始矩陣)和 animEnd(結束矩陣)。
開始矩陣表示圖片原來的位置與大??;結束矩陣表示圖片縮放后的位置與大小,是根據放大比例和雙擊點位置確定的。
確定圖片的開始和結束矩陣后,啟動縮放動畫,便可以實現縮放效果,如圖 5 所示:
private void doubleTap(float x, float y) {
...
//開始計算縮放動畫的結果矩陣
Matrix animEnd = MathUtils.matrixTake(mOuterMatrix);
//計算還需縮放的倍數
animEnd.postScale(nextScale / currentScale, nextScale / currentScale, x, y);
//將放大點移動到控件中心
animEnd.postTranslate(displayWidth / 2f - x, displayHeight / 2f - y);
RectFloat testBound = MathUtils.rectFTake(0,0,mp.getImageInfo().size.width,mp.getImageInfo().size.height);
...
//清理當前可能正在執行的動畫
cancelAllAnimator();
//啟動矩陣動畫
mScaleAnimator = new ScaleAnimator(mOuterMatrix, animEnd);
mScaleAnimator.start();
...
}
2、圖片在縮放狀態下移動
單指滑動表示手指在屏幕上完成矢量平移,是圖片移動的唯一方式。該功能是通過 scrollBy() 方法實現的。
以實現圖片左右移動為例,在 scrollBy() 方法中,需要判斷縮放狀態下圖片位移的最大距離,有以下幾種不同的情況:
圖片移動后,左側邊緣超出控件的左側邊緣,圖片無法移動。
圖片移動后,右側邊緣超出控件的右側邊緣,圖片無法移動。
圖片移動后,兩側都未超出控件邊緣的情況下,將以手指觸碰點作為控制點,對圖片進行水平移動。
圖片上下平移的情況與左右平移類似,這里不做贅述,圖片移動效果如圖 6 所示:
圖 6:圖片移動的最大距離
public boolean scrollBy(float xDiff, float yDiff) {
...
if (bound.right - bound.left 《 displayWidth) {
xDiff = 0;
//如果圖片左邊在移動后超出控件左邊
} else if (bound.left + xDiff 》 0) {
//如果在移動之前是沒超出的,計算應該移動的距離
if (bound.left 《 0) {
xDiff = -bound.left;
//否則無法移動
} else {
xDiff = 0;
}
//如果圖片右邊在移動后超出控件右邊
} else if (bound.right + xDiff 《 displayWidth) {
//如果在移動之前是沒超出的,計算應該移動的距離
if (bound.right 》 displayWidth) {
xDiff = displayWidth - bound.right;
//否則無法移動
} else {
xDiff = 0;
}
}
...
}
責任編輯:haq
-
操作系統
+關注
關注
37文章
6861瀏覽量
123502 -
鴻蒙系統
+關注
關注
183文章
2637瀏覽量
66508 -
HarmonyOS
+關注
關注
79文章
1980瀏覽量
30329
原文標題:鴻蒙手勢操控組件,代碼已開源!
文章出處:【微信號:gh_834c4b3d87fe,微信公眾號:OpenHarmony技術社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論