PhotoDraweeView解析

前言

原理

PhotoDraweeView是基于PhotoView的設(shè)計(jì)思路實(shí)現(xiàn)的牺丙,其存在的意義是彌補(bǔ)PhotoView不支持Fresco的不足症概。PhotoDraweeView繼承自SimpleDraweeView個钢猛,實(shí)現(xiàn)IAttacher接口蒙畴,并重寫了onDraw來更新視圖胁附,通過Matrix來實(shí)現(xiàn)圖片的變換酪耕,以及通過重寫onTouch來處理手勢导梆。我們可以看一下其構(gòu)造函數(shù):

    public PhotoDraweeView(Context context) {
        super(context);
        init();
    }
    ……
    protected void init() {
        if (mAttacher == null || mAttacher.getDraweeView() == null) {
            mAttacher = new Attacher(this);
        }
    }

其實(shí)例化了一個Attacher,該類負(fù)責(zé)對圖片和手勢的處理相關(guān)操作迂烁,我們主要關(guān)注在于以下兩點(diǎn):

  • 手勢處理
  • 圖片處理

手勢處理

在PhotoDraweeView中看尼,手勢的相關(guān)處理交由了ScaleDragDetector和GestureDetectorCompat兩個類來管理,前者負(fù)責(zé)對多點(diǎn)觸發(fā)比例縮放和滑動拖拽等操作做處理盟步,后者主要接管了單擊和雙擊事件的處理藏斩。
相關(guān)的類的關(guān)系圖如下:

PhotoDraweeView

對于手勢的處理,根據(jù)對View的事件方法機(jī)制的學(xué)習(xí)却盘,我們可以知道狰域,一個手勢事件MotionEvent最終會onTouch的相關(guān)方法中處理,而在Attacher中黄橘,對DraweeView設(shè)置了OnTouchListener兆览,其接口的onTouch在PhotoDraweeView中實(shí)現(xiàn):

    @Override public boolean onTouch(View v, MotionEvent event) {
        int action = MotionEventCompat.getActionMasked(event);
        //避免父類直接攔截事件
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                ViewParent parent = v.getParent();
                if (parent != null) {
                    parent.requestDisallowInterceptTouchEvent(true);
                }
                cancelFling();
            }
            break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL: {
                ViewParent parent = v.getParent();
                if (parent != null) {
                    parent.requestDisallowInterceptTouchEvent(false);
                }
            }
            break;
        }

        ……

        //比例控制工具
        boolean handled = mScaleDragDetector.onTouchEvent(event);

        ……

        if (mGestureDetector.onTouchEvent(event)) {
            handled = true;
        }

        return handled;
    }

其在方法中,主要先屏蔽父類的Intercept塞关,這樣可以可以最大限度的獲取到事件抬探,是考慮多個View/ViewGroup事件沖突的時候的相關(guān)操作。然后進(jìn)一步調(diào)用了ScaleDragDetector和GestureDetector兩個對象來對手勢事件進(jìn)行處理描孟。

ScaleDragDetector

該類實(shí)現(xiàn)了OnScaleGestureListener驶睦,同時持有OnScaleDragGestureListener接口(該接口在PhotoDraweeView中實(shí)現(xiàn)),其負(fù)責(zé)了對手勢MotionEvent的初步拆解匿醒,并記錄一些點(diǎn)的數(shù)據(jù)。所有的事件處理都集中在了該類的onTouchEvent(手勢操作必經(jīng)之路):

    public boolean onTouchEvent(MotionEvent ev) {
        //數(shù)據(jù)傳遞到OnScaleGestureListener接口缠导,最終還是傳遞到OnScaleDragGestureListener中
        mScaleDetector.onTouchEvent(ev);
        final int action = MotionEventCompat.getActionMasked(ev);
        //獲取觸摸點(diǎn)的位置廉羔,多點(diǎn)取第一個點(diǎn)位置
        onTouchActivePointer(action, ev);
        //對手勢進(jìn)行拆解,分配到OnScaleDragGestureListener里的相應(yīng)方法中處理
        onTouchDragEvent(action, ev);
        return true;
    }

所以事件的最后還是傳遞到了OnScaleDragGestureListener中僻造。在經(jīng)過對手勢的處理判斷憋他,就可以根據(jù)結(jié)果來分別觸發(fā)該接口中的方法孩饼。該接口包含以下方法:

public interface OnScaleDragGestureListener {
    //拖拽的時候
    void onDrag(float dx, float dy);
    //快速滑動時
    void onFling(float startX, float startY, float velocityX, float velocityY);
    //比例變化
    void onScale(float scaleFactor, float focusX, float focusY);
    //變化結(jié)束
    void onScaleEnd();
}

接下來的事就變的簡單了,主要在個接口方法中做些簡單處理竹挡,然后傳遞到Matrix的對應(yīng)方法中即可實(shí)現(xiàn)圖像的變換镀娶。以onScale方法為例,其會先做些判斷揪罕,然后講變換的數(shù)據(jù)傳遞給其它回調(diào)接口梯码,最后通過Matrix更新,代碼如下:

    @Override public void onScale(float scaleFactor, float focusX, float focusY) {
        if (getScale() < mMaxScale || scaleFactor < 1.0F) {

            if (mScaleChangeListener != null) {
                mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
            }

            mMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
            checkMatrixAndInvalidate();
        }
    }

GestureDetector

在實(shí)際使用過程中好啰,用戶可以通過雙擊屏幕來進(jìn)行對圖片的縮小和放大轩娶,該接口就是用于響應(yīng)做些點(diǎn)擊事件的。在PhotoDraweeView中提供了一個默認(rèn)實(shí)現(xiàn)類DefaultOnDoubleTapListener框往,該類主要處理了如下兩個接口方法:

    //單擊事件
    @Override public boolean onSingleTapConfirmed(MotionEvent e) {

        ……
        //根據(jù)點(diǎn)的位置來確定點(diǎn)擊觸發(fā)的是圖片還是整個View(存在圖片區(qū)域小于View的情況)鳄抒,然后取調(diào)用不同的接口取處理。
        if (mAttacher.getOnPhotoTapListener() != null) {
            final RectF displayRect = mAttacher.getDisplayRect();

            if (null != displayRect) {
                final float x = e.getX(), y = e.getY();
                if (displayRect.contains(x, y)) {
                    float xResult = (x - displayRect.left) / displayRect.width();
                    float yResult = (y - displayRect.top) / displayRect.height();
                    mAttacher.getOnPhotoTapListener().onPhotoTap(draweeView, xResult, yResult);
                    return true;
                }
            }
        }

        if (mAttacher.getOnViewTapListener() != null) {
            mAttacher.getOnViewTapListener().onViewTap(draweeView, e.getX(), e.getY());
            return true;
        }

        return false;
    }
    //雙擊事件處理
    @Override public boolean onDoubleTap(MotionEvent event) {
        if (mAttacher == null) {
            return false;
        }

        try {
            float scale = mAttacher.getScale();
            float x = event.getX();
            float y = event.getY();

            // 根據(jù)當(dāng)前狀態(tài)椰弊,來進(jìn)行對應(yīng)的比例變換许溅,默認(rèn)有三中狀態(tài)-->min, mid, max
            if (scale < mAttacher.getMediumScale()) {
                mAttacher.setScale(mAttacher.getMediumScale(), x, y, true);
            } else if (scale >= mAttacher.getMediumScale() && scale < mAttacher.getMaximumScale()) {
                mAttacher.setScale(mAttacher.getMaximumScale(), x, y, true);
            } else {
                mAttacher.setScale(mAttacher.getMinimumScale(), x, y, true);
            }
        } catch (Exception e) {
            // Can sometimes happen when getX() and getY() is called
        }
        return true;
    }

圖片處理

一個View的圖像變化,肯定離不開onDraw方法秉版,在PhotoDraweeView中贤重,重寫了該方法,主要根據(jù)Matrix來更新界面沐飘,而對于比例縮放游桩,平移等操作,都可以通過invalidate()方法來更新視圖耐朴,以下是PhotoDraweeView中重寫的onDraw方法:

    protected void onDraw(@NonNull Canvas canvas) {
        int saveCount = canvas.save();
        if (mEnableDraweeMatrix) {
            canvas.concat(mAttacher.getDrawMatrix());
        }
        super.onDraw(canvas);
        canvas.restoreToCount(saveCount);
    }

其在圖像發(fā)送變化的時候借卧,會在重繪過程中調(diào)用canvas.concat(mAttacher.getDrawMatrix())來完成canvas的更新,因此筛峭,在如何對Matrix做了處理的方法中調(diào)用invalidate()即可實(shí)現(xiàn)更新铐刘。
  對圖片的處理,我們需要使用矩陣Matrix來進(jìn)行操作影晓。通過Matrix的3*3矩陣镰吵,我們可以對圖片進(jìn)行scale(縮放),skew(錯切)挂签,trans(平移)疤祭,persp(透視)等操作。而在PhotoDaweeView中饵婆,需要處理scale和trans兩個操作勺馆,對應(yīng)著postScale和postTranslate兩個方法。由手勢處理部分可知,最后的縮放會在onScale中執(zhí)行草穆,并通過Matrix實(shí)現(xiàn)操作灌灾,然后執(zhí)行checkMatrixAndInvalidate方法:

    mMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
    checkMatrixAndInvalidate();

進(jìn)一步查看checkMatrixAndInvalidate的代碼:

    public void checkMatrixAndInvalidate() {

        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();

        if (draweeView == null) {
            return;
        }

        if (checkMatrixBounds()) {
                draweeView.invalidate();
        }
    }

在checkMatrixBounds會判斷邊界,并控制平移的值悲柱,最終通過invalidate()方法去通知更新視圖锋喜。這就是一個完整的圖片處理過程。
  這里在強(qiáng)調(diào)下其帶動畫的變換豌鸡,在圖片被縮小到比最小尺寸還小時嘿般,會有一個恢復(fù)的動畫,而該動畫的實(shí)現(xiàn)機(jī)制直颅,是通過一個View去post一個runable實(shí)現(xiàn)的博个,如checkMinScale:

    private void checkMinScale() {
        DraweeView<GenericDraweeHierarchy> draweeView = getDraweeView();
        if (draweeView == null) {
            return;
        }

        if (getScale() < mMinScale) {
            RectF rect = getDisplayRect();
            if (null != rect) {
                draweeView.post(new AnimatedZoomRunnable(getScale(), mMinScale, rect.centerX(),
                        rect.centerY()));
            }
        }
    }

而在AnimatedZoomRunnable的run中,會不斷去post該請求直到變換結(jié)束功偿,類似一個while循環(huán):

    @Override public void run() {

        ……
        onScale(deltaScale, mFocalX, mFocalY);();

        if (t < 1f) {
            postOnAnimation(draweeView, this);
        }
    }

    //post最終通過View的hanlder去執(zhí)行
    private void postOnAnimation(View view, Runnable runnable) {
        if (Build.VERSION.SDK_INT >= 16) {
            view.postOnAnimation(runnable);
        } else {
            view.postDelayed(runnable, 16L);
        }
    }

相關(guān)資料

END

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盆佣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子械荷,更是在濱河造成了極大的恐慌共耍,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吨瞎,死亡現(xiàn)場離奇詭異痹兜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)颤诀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進(jìn)店門字旭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人崖叫,你說我怎么就攤上這事遗淳。” “怎么了心傀?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵屈暗,是天一觀的道長。 經(jīng)常有香客問我脂男,道長养叛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任宰翅,我火速辦了婚禮弃甥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汁讼。我一直安慰自己潘飘,他們只是感情好肮之,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布掉缺。 她就那樣靜靜地躺著卜录,像睡著了一般。 火紅的嫁衣襯著肌膚如雪眶明。 梳的紋絲不亂的頭發(fā)上艰毒,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機(jī)與錄音搜囱,去河邊找鬼丑瞧。 笑死,一個胖子當(dāng)著我的面吹牛蜀肘,可吹牛的內(nèi)容都是我干的绊汹。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼扮宠,長吁一口氣:“原來是場噩夢啊……” “哼西乖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坛增,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤获雕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后收捣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體届案,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年罢艾,在試婚紗的時候發(fā)現(xiàn)自己被綠了楣颠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡咐蚯,死狀恐怖童漩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仓蛆,我是刑警寧澤睁冬,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站看疙,受9級特大地震影響豆拨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜能庆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一施禾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧搁胆,春花似錦弥搞、人聲如沸邮绿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽船逮。三九已至,卻和暖如春粤铭,著一層夾襖步出監(jiān)牢的瞬間挖胃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工梆惯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酱鸭,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓垛吗,卻偏偏與公主長得像凹髓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子怯屉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評論 2 350

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,849評論 25 707
  • 1蔚舀、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,969評論 3 119
  • CSS背景 CSS 文本 CSS 字體 CSS 鏈接 CSS 列表 CSS 表格 CSS 輪廓 背景 backgr...
    雪_晟閱讀 191評論 0 0
  • 最近,家庭教育劇如雨后春筍般出現(xiàn)蚀之。在看劇的同時蝗敢,我們也看到了一個現(xiàn)實(shí)——鐵腕教育的觀念根深蒂固。我在思考一個問題足删,...
    Oski閱讀 187評論 0 0
  • 閑下來了寿谴,就想把自己的所思所得記錄下來,權(quán)當(dāng)是思想的枚枚剪影失受,放置在我的案頭讶泰。每天都是新的,每天的情思也是...
    冰夫閱讀 132評論 0 0