可拖拽和可縮放的ImageView

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);
} 

五鹊碍、參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市侈咕,隨后出現(xiàn)的幾起案子公罕,更是在濱河造成了極大的恐慌,老刑警劉巖耀销,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楼眷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)摩桶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門桥状,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)帽揪,“玉大人硝清,你說(shuō)我怎么就攤上這事∽” “怎么了芦拿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)查邢。 經(jīng)常有香客問(wèn)我蔗崎,道長(zhǎng),這世上最難降的妖魔是什么扰藕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任缓苛,我火速辦了婚禮,結(jié)果婚禮上邓深,老公的妹妹穿的比我還像新娘未桥。我一直安慰自己,他們只是感情好芥备,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布冬耿。 她就那樣靜靜地躺著,像睡著了一般萌壳。 火紅的嫁衣襯著肌膚如雪亦镶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天袱瓮,我揣著相機(jī)與錄音缤骨,去河邊找鬼。 笑死尺借,一個(gè)胖子當(dāng)著我的面吹牛荷憋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播褐望,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼勒庄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瘫里?” 一聲冷哼從身側(cè)響起实蔽,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谨读,沒(méi)想到半個(gè)月后局装,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年铐尚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拨脉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宣增,死狀恐怖玫膀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爹脾,我是刑警寧澤帖旨,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站灵妨,受9級(jí)特大地震影響解阅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泌霍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一货抄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朱转,春花似錦蟹地、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至凉蜂,卻和暖如春琼梆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窿吩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工介时, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留念脯,地道東北人琳骡。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓曙寡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親轧邪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刽脖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,762評(píng)論 22 665
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,145評(píng)論 25 707
  • 文/兮月 以前只知道要接你的人會(huì)在車站等你 為此你就感動(dòng)的不得了 發(fā)誓要對(duì)他好 可后來(lái)長(zhǎng)大了一點(diǎn)點(diǎn) 心也放寬了一點(diǎn)...
    親愛(ài)的兮月閱讀 171評(píng)論 1 0
  • “接下來(lái)該怎么辦曲管?” “我哪知道……” “你先去找一下這房間的燈在哪,這他媽什么都看不見(jiàn)硕糊!”胡建恨恨道院水。 其實(shí)汪木...
    百語(yǔ)閱讀 229評(píng)論 0 0
  • 雨天的香港 過(guò)關(guān)的隊(duì)伍 并沒(méi)有想象中的擁擠 雨水打濕的肩膀 并沒(méi)有多大的抱怨 雙層巴士腊徙,紅色的士 是香港街頭亮麗的...
    啊芼閱讀 278評(píng)論 3 4