ZoomImageView
一、簡(jiǎn)介
? 一個(gè)支持手勢(shì)拖拽垫桂、縮放的ImageView回右。
Github地址:https://github.com/Fiend96/ZoomImageView
? 手勢(shì)操作是智能手機(jī)的一大特色,特別是多點(diǎn)手勢(shì)有著良好的交互體驗(yàn)锦担。手勢(shì)操縱圖片廣泛應(yīng)用于Android的各大app,包括QQ慨削、微信和微博等洞渔。本項(xiàng)目正是基于上述app的體驗(yàn)作為參考而開(kāi)發(fā)的一個(gè)控件。其可以支持拖動(dòng)缚态、點(diǎn)擊磁椒、雙點(diǎn)滑動(dòng)等手勢(shì)操縱圖片的變換。
二玫芦、主要實(shí)現(xiàn)技術(shù)
- OnTouchListener觸摸事件:處理多點(diǎn)觸摸操作
GestureDetector手勢(shì)輔助類:處理滑動(dòng)浆熔、單擊和快速滑動(dòng)(滑動(dòng)后有慣性)事件
-
Matrix變化:實(shí)現(xiàn)圖片的縮放和移動(dòng)效果,需要設(shè)置
super.setScaleType(ScaleType.MATRIX);
然后通過(guò)下述幾種變化實(shí)現(xiàn)縮放和移動(dòng)效果:
postScale(scaleX, scaleY);//改變大小
postTranslate(tranX, tranY);//改變位置
setImageMatrix(matrix);//ImageView的方法桥帆,設(shè)置當(dāng)前圖片的矩陣
-
ValueAnimator實(shí)現(xiàn)動(dòng)畫(huà)效果医增,有以下情況需要?jiǎng)赢?huà)
- 縮放倍數(shù)小于正常倍數(shù)時(shí)需要恢復(fù)正常倍數(shù)
- 拖拽時(shí)超過(guò)了邊界時(shí)需要恢復(fù)與邊界重合
詳細(xì)實(shí)現(xiàn)請(qǐng)看代碼
三慎皱、實(shí)現(xiàn)細(xì)節(jié)
1、操作模式
? 判斷處于那個(gè)狀態(tài)而進(jìn)行不同的操作
int NONE = 0;//無(wú)法操作
int DRAG = 1;//拖動(dòng)模式叶骨,在放大的情況下
int ZOOM = 2;//縮放模式
int DOUBLECLICK = 4;//雙擊模式
2茫多、大小模式
? 需要判斷不同的大小來(lái)決定操作模式
int ENLARGE = 5;//放大模式
int NARROW = 6;//縮小模式
float MAX_SCALE = 4f;//最大放大倍數(shù)
float MIN_SCALE = 0.5f;//最小縮放倍數(shù)
3、拖動(dòng)
? 判斷是否可以進(jìn)入拖拽模式
if (sizeMode == ENLARGE || ableTranY) {// 放大或Y軸可拖動(dòng)
mode = DRAG;// 單點(diǎn)進(jìn)入拖動(dòng)模式
startPoint.set(e.getX(), e.getY());//保存開(kāi)始拖動(dòng)時(shí)點(diǎn)的數(shù)據(jù)
lastPoint.set(e.getX(), e.getY());
}
? 在GestureDetector的onSroll方法中處理拖動(dòng)效果
if (mode == DRAG) {// 拖動(dòng)模式
mCurrentMatrix.set(mSaveMatrix);//每次拖動(dòng)都是基于開(kāi)始拖動(dòng)時(shí)候的參數(shù)來(lái)計(jì)算的
dragImage(e2);//處理拖動(dòng)
}
? 拖動(dòng)的具體操作
float currentX = event.getX();// 獲取當(dāng)前的x坐標(biāo)
float currentY = event.getY();// 獲取當(dāng)前的Y坐標(biāo)
float tranX = currentX - startPoint.x;// 移動(dòng)的x軸距離
float tranY = currentY - startPoint.y;// 移動(dòng)的y軸距離
mCurrentMatrix.postTranslate(tranX, tranY);
setImageMatrix(mCurrentMatrix);
4忽刽、縮放
? 判斷是否可以進(jìn)入縮放狀態(tài)
case MotionEvent.ACTION_POINTER_DOWN: //第二次down事件天揖,表示兩點(diǎn)同時(shí)觸摸
getSizeMode();
oldDistance = spacing(event); //計(jì)算兩點(diǎn)的距離
if (oldDistance > 10f) { //兩點(diǎn)超過(guò)10才可以拖動(dòng)
mode = ZOOM;
}
? 在OnTouchListener中進(jìn)行縮放操作
case MotionEvent.ACTION_MOVE:
if (mode == ZOOM) {// 縮放模式
mCurrentMatrix.set(mSaveMatrix);//每次縮放都是基于開(kāi)始縮放時(shí)候的參數(shù)來(lái)計(jì)算的
zoomImage(event);
}
? 縮放的具體操作
float newDistance = spacing(event);// 獲取新的距離
float scale = newDistance / oldDistance;// 縮放比例
scale = getScaleType(scale);// 獲取縮放的類型
midPoint(midPoint, event);//計(jì)算中點(diǎn)位置,根據(jù)中點(diǎn)位置來(lái)進(jìn)行縮放
mCurrentMatrix.postScale(scale, scale, midPoint.x, midPoint.y);
setImageMatrix(mCurrentMatrix);
5跪帝、恢復(fù)動(dòng)畫(huà)
? 在縮放倍數(shù)小于正常倍數(shù)(初始化的倍數(shù))時(shí)需要恢復(fù)到正常倍數(shù)宝剖。在拖拽圖片超出了邊界也需要恢復(fù)到不超出邊界。顯然歉甚,只有在手指離開(kāi)屏幕的時(shí)候才會(huì)需要恢復(fù)万细,所以需要監(jiān)控UP事件來(lái)判斷是否需要恢復(fù)
case MotionEvent.ACTION_UP:
// 拖拽模式
if (mode == DRAG) {
after();
mode = NONE;
// 縮放模式
} else if (mode == ZOOM) {
after();
mode = NONE;
} else if (mode == DOUBLECLICK) {
mode = NONE;
}
? 恢復(fù)正常狀態(tài)的操作就是判斷倍數(shù)是否小于正常倍數(shù)
if (mScale < getNolmalScale()) {
//處理
}
? 和是否拖出邊界
if (mTranX > 0) { //拖出左邊界
//處理
} else if (Math.abs(mTranX) > imageWidth - mViewWidth) {//拖出右邊界
//處理
}
// 對(duì)Y軸移動(dòng)處理,若圖片高度小于控件高度則無(wú)法進(jìn)行Y軸拖動(dòng)(圖片寬度一定大于或等于控件寬度)
if (imageHeight > mViewHeight) {
if (mTranY > 0) {// 拖出上邊界
//處理
} else if (Math.abs(mTranY) > imageHeight - mViewHeight) { //拖出下邊界
//處理
}
} else if (imageHeight < mViewHeight) { //無(wú)法進(jìn)行拖動(dòng)
//處理
}
6纸泄、恢復(fù)初始化狀態(tài)
? 默認(rèn)雙擊恢復(fù)初始化狀態(tài)赖钞,也可以調(diào)用方法來(lái)恢復(fù)初始化狀態(tài)。具體計(jì)算與拖拽縮放類似聘裁,這里不做更多解釋雪营,詳細(xì)請(qǐng)看代碼。
四衡便、解決與ViewPager的滑動(dòng)沖突
? 在ViewPager中使用該控件需要判斷什么時(shí)候ViewPager需要處理事件(翻頁(yè))献起。這里是在拖動(dòng)滑出邊界時(shí)將事件交給ViewPager來(lái)處理,使用的接口為
//isInterrupt true時(shí)父View不攔截事件镣陕,false時(shí)View可以攔截事件
parent.requestDisallowInterceptTouchEvent(isInterrupt);
? 因?yàn)槭录枰獜母覆季种袀魅肭床停员仨氃贒OWN事件(父布局無(wú)法攔截DOWN事件)中通知父布局不攔截事件
setInterrupt(true);
if (sizeMode == NONE && !ableTranY) {
// 判斷sizeMode是因?yàn)槌跏蓟癄顟B(tài)只能進(jìn)行縮放操作
// ableTranY表示Y軸可拖動(dòng),因?yàn)槌跏蓟瘯r(shí)圖片寬度于控件寬度相同呆抑,所以X軸無(wú)法拖動(dòng)岂嗓,若圖片Y軸可拖動(dòng)同樣可進(jìn)行拖動(dòng)操作
setInterrupt(false);
}
? 在拖動(dòng)過(guò)程中判斷是否將事件交給父布局
//isInterruptLeft和isInterruptRight為可設(shè)置的參數(shù),表示強(qiáng)制攔截
boolean ableToTurnLeft = mTranX > 3 && isInterruptLeft;
boolean ableToTurnRight = Math.abs(mTranX) > Math.abs(mImageWidth - mViewWidth) + 3 && isInterruptRight;
//將滑動(dòng)控制權(quán)返回給父view同時(shí)調(diào)整位置
if (ableToTurnLeft) {
values[Matrix.MTRANS_X] = 0; //將圖片恢復(fù)到不超過(guò)邊界的狀態(tài)
setInterrupt(false);
mCurrentMatrix.setValues(values);
} else if (ableToTurnRight) {
values[Matrix.MTRANS_X] = mViewWidth - mImageWidth;//將圖片恢復(fù)到不超過(guò)邊界的狀態(tài)
mCurrentMatrix.setValues(values);
setInterrupt(false);
}
五鹊碍、參考
- QQ厌殉、微信和微博的圖片控件
- PhotoView https://github.com/chrisbanes/PhotoView
- 《Android開(kāi)發(fā)藝術(shù)探索》的滑動(dòng)事件講解