Android側滑原來能夠這麼優雅

前言

側滑手勢在Android App應用得很是普遍,常見的使用場景包括:滑動抽屜、側滑刪除、側滑返回、下拉刷新以及側滑封面等。因爲這些使用場景實在是太通用了,各路大神們八仙過海各顯神通,每種側滑場景都開源出了不少很是實用的框架,讓咱們的業務開發便利了不少。html

目前,咱們須要爲每種場景引入不一樣的側滑框架,因爲App中的側滑場景不少,咱們項目中也就須要引入多個側滑框架,而每一個框架的使用方式各有不一樣,須要單獨學習,團隊的學習成本較高。java

那麼問題來了,有沒有一種框架能解決全部側滑需求呢?android

一個框架解決全部側滑需求?你肯定不是在開玩笑?

在剛開始學習面向對象編程概念的時候咱們就知道一個道理:解決一個軟件問題,首先要將它抽象出來git

針對側滑這個手勢,咱們能不能將它的概念抽象一下,到底側滑指的是什麼呢?github

  • 狹義側滑:從屏幕的某側的邊緣開始向着遠離該邊緣的方向滑動
  • 廣義側滑:手指在屏幕上按下以後向着某一側方向滑動

個人理解是,廣義側滑包含狹義側滑,只不過是觸發區域是否在屏幕邊緣的區別罷了。web

因而,側滑的概念就這樣被清晰地抽象出來了。編程

從這個抽象概念能夠看出:側滑手勢同一時間只處理上下左右4個方向中的一個方向
複製代碼

若是咱們將這個抽象概念封裝出來,將手勢事件的識別、攔截及數據加工在框架內部處理好,並向外實時地輸出側滑方向距離及相關的回調, 理論上咱們就能夠實現全部的側滑需求了。canvas

至於具體的側滑效果,學過策略模式的都知道:
每一種具體的側滑效果實現均可以看作是一種側滑策略。
複製代碼

說的那麼玄乎,到底咋弄?

胸擡,憋急!磨刀不誤砍柴工,站在巨人的肩膀上你就有可能比巨人高那麼一點點。bash

Google在android support庫中爲側滑菜單的需求提供了SlidingPaneLayout和DrawerLayout兩種實現,看源碼會發現二者都是基於ViewDragHelper來實現的,那麼ViewDragHelper又是何方神聖呢?微信

ViewDragHelper是android support庫中的一個工具類。它能夠幫助咱們處理控件的拖拽,它的使用方式爲:先建立一個自定義ViewGroup,將被拖動的控件添加到這個自定義ViewGroup中,並用ViewDragHelper來處理控件的拖拽,能夠經過Callback來指定拖拽區域及捕獲子控件的邏輯。

經過閱讀ViewDragHelper的源碼發現,它對view在父容器中的拖拽行爲進行了封裝,經過攔截父容器控件的手勢事件,捕獲須要拖拽的子控件,並實時根據手指的移動改變它的座標,從而實現拖拽效果。

ViewDragHelper封裝的很優雅,也很強大, 有些開源側滑框架也是基於ViewDragHelper來實現的,例如: ikew0ng/SwipeBackLayout / daimajia/AndroidSwipeLayout

不過,ViewDragHelper封裝的是子控件的拖拽,而不是側滑,它計算距離的基準是控件的top和left座標,雖然能夠將其中一個方向(橫向或縱向)的拖動範圍設置爲0來模擬側滑手勢,但它不符合咱們側滑手勢的抽象定義,沒法解決側滑時不是控件移動的效果。

例如:MIUI系統側滑返回效果及小米公司出品的App廣泛使用的彈性拉伸效果等

別扯那些沒用的,趕忙講側滑

既然側滑已經被清晰地抽象出來了,一樣是對觸摸滑動事件的處理,咱們徹底能夠借鑑ViewDragHelper的思想:將它對子控件的捕獲和拖動,改爲對側滑方向的捕獲和側滑距離的計算,並將它的Callback改形成側滑距離的消費者(具體的側滑效果就看消費者用哪一種方式來消費掉這個側滑距離)。

側滑行爲的2個核心要素:
側滑方向、側滑距離

根據這個思路,我封裝了一個智能的側滑框架:SmartSwipe,能夠解決你所(chui)有(niu)的(bi)側滑需求。請大聲說出它的slogan!

關於側滑,有這一個就夠了

固然,這是吹牛逼的!

框架只是封裝了側滑行爲事件的捕獲、分發及多點交替滑動的處理,具體的側滑效果(消費側滑距離的策略)須要你本身來實現。。。哎。。。等等,胸擡,先別走啊!還沒說完呢,SmartSwipe中內置了十多種常見側滑效果,有動圖爲證:

1. 一行代碼讓頁面動起來

//仿iOS的彈性留白效果:
//側滑時表現爲彈性留白效果,結束後自動恢復
SmartSwipe.wrap(view)
    .addConsumer(new SpaceConsumer())
    .enableVertical(); //工做方向:縱向
複製代碼

彈性留白效果

2. 一行代碼讓頁面具備彈性

//仿MIUI的彈性拉伸效果:
//側滑時表現爲彈性拉伸效果,結束後自動恢復
SmartSwipe.wrap(view)
    .addConsumer(new StretchConsumer())
    .enableVertical(); //工做方向:縱向
複製代碼

彈性拉伸效果

3. 一行代碼添加滑動抽屜

抽屜顯示在主view之上,相似於DrawerLayout

SmartSwipe.wrap(view)
    .addConsumer(new DrawerConsumer())    //抽屜效果
    //能夠設置橫向(左右兩側)的抽屜爲同一個view
    //也能夠爲不一樣方向分別設置不一樣的view
    .setHorizontalDrawerView(menuLayout) 
    .setScrimColor(0x2F000000) //設置遮罩的顏色
    .setShadowColor(0x80000000)    //設置邊緣的陰影顏色
    ;
複製代碼

滑動抽屜

4. 一行代碼添加帶聯動效果的滑動抽屜

抽屜顯示在主view之下

SmartSwipe.wrap(view)
    .addConsumer(new SlidingConsumer())
    .setHorizontalDrawerView(textView)
    .setScrimColor(0x2F000000)
    //設置聯動係數
    // 0:不聯動,視覺效果爲:主體移動後顯示下方的抽屜
    // 0~1: 半聯動,視覺效果爲:抽屜視圖按照聯動係數與主體之間存在相對移動效果
    // 1:全聯動,視覺效果爲:抽屜跟隨主體一塊兒移動(pixel by pixel)
    .setRelativeMoveFactor(0.5F) 
    ;
複製代碼

聯動抽屜

5. 一行代碼添加滑動透明效果

側滑透明效果,側滑後可顯示被其遮擋的view,可用做側滑刪除,也能夠用來製做封面效果

//側滑刪除
SmartSwipe.wrap(view)
    .addConsumer(new TranslucentSlidingConsumer())
    .enableHorizontal() //啓用左右兩側側滑
    .addListener(new SimpleSwipeListener(){
        @Override
        public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
            //側滑打開時,移除
            ViewParent parent = wrapper.getParent();
            if (parent instanceof ViewGroup) {
                ((ViewGroup) parent).removeView(wrapper);
            }
            //adapter.removeItem(getAdapterPosition());// 也可用做從recyclerView中移除該項
        }
    })
    ;
複製代碼

側滑透明

6. 一行代碼添加側滑手勢識別功能

側滑時,主view保持不動,手指釋放時,識別滑動方向及速率,以肯定是否執行對應的側滑邏輯。

//demo:用StayConsumer來作activity側滑返回
SmartSwipe.wrap(this)
    .addConsumer(new StayConsumer())
    .enableAllDirections()
    .addListener(new SimpleSwipeListener(){
        @Override
        public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
            finish();
        }
    })
    ;
複製代碼

手勢識別

7. 一行代碼添加百葉窗效果

側滑時主view像百葉窗同樣打開,透明顯示下層的視圖。

可用來製做封面、輪播圖等

//用ShuttersConsumer實現百葉窗側滑刪除
SmartSwipe.wrap(view)
    .addConsumer(new ShuttersConsumer())
    .enableHorizontal() //啓用左右兩側側滑
    .addListener(new SimpleSwipeListener(){
        @Override
        public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
            //側滑打開時,移除
            ViewParent parent = wrapper.getParent();
            if (parent instanceof ViewGroup) {
                ((ViewGroup) parent).removeView(wrapper);
            }
            //adapter.removeItem(getAdapterPosition());// 也可用做從recyclerView中移除該項
        }
    })
    ;
複製代碼

百葉窗效果

8. 一行代碼添加開門效果

側滑時,主view像開門同樣從中間向兩邊(上下 或 左右)分開,透明顯示它下層的視圖

可用來製做封面、輪播圖等

//用DoorConsumer實現百葉窗側滑刪除
SmartSwipe.wrap(view)
    .addConsumer(new DoorConsumer())
    .enableHorizontal() //啓用左右兩側側滑
    .addListener(new SimpleSwipeListener(){
        @Override
        public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
            //側滑打開時,移除
            ViewParent parent = wrapper.getParent();
            if (parent instanceof ViewGroup) {
                ((ViewGroup) parent).removeView(wrapper);
            }
            //adapter.removeItem(getAdapterPosition());// 也可用做從recyclerView中移除該項
        }
    })
    ;
複製代碼

開門效果

9. 一行代碼添加貝塞爾曲線返回效果

側滑時,在控件側滑的邊緣顯示一個貝塞爾曲線的返回效果

可用於activity返回、fragment返回,也可用於webview的返回/前進

//activity側滑返回
SmartSwipe.wrap(this)
    .addConsumer(new BezierBackConsumer())
    .enableAllDirections()
    .addListener(new SimpleSwipeListener() {
        @Override
        public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
            finish();
        }
    })
    ;
複製代碼

貝塞爾曲線返回效果

10. 一行代碼添加仿微信Activity聯動側滑返回效果

沒錯,專爲activity側滑返回而做的一種效果,帶聯動功能

//activity側滑返回
SmartSwipe.wrap(this)
    .addConsumer(new ActivitySlidingBackConsumer(this))
    //設置聯動係數
    .setRelativeMoveFactor(0.5F)
    //指定可側滑返回的方向,如:enableLeft() 僅左側可側滑返回
    .enableAllDirections() 
    ;
複製代碼

仿微信Activity側滑返回

11. 一行代碼添加Activity百葉窗側滑返回效果

沒錯,也是專爲activity側滑返回而做的一種效果,透明顯示前一個activity

//activity側滑返回
SmartSwipe.wrap(this)
    .addConsumer(new ActivityShuttersBackConsumer(this))
    .setScrimColor(0x7F000000)
    .enableAllDirections()
    ;
複製代碼

Activity百葉窗返回

12. 一行代碼添加Activity開門側滑返回效果

沒錯,這仍是一個專爲activity側滑返回而做的效果,透明顯示前一個activity

//activity側滑返回
SmartSwipe.wrap(this)
    .addConsumer(new ActivitySlidingBackConsumer(this))
    .setRelativeMoveFactor(0.5F)
    .enableAllDirections()
    ;
複製代碼

Activity開門返回

怎麼都是一行代碼?還敢不敢再來點?

SmartSwipe中絕大多數的使用均可以經過鏈式編程在一行代碼內完成,API的設計風格以下:

SmartSwipe.wrap(...) 		//view or Activity
	.addConsumer(...) 		//添加consumer
	.enableDirection(...) 	//指定consumer接收哪一個方向的側滑事件
	.setXxx(...) 			//[可選]一些其它設置項
	.addListener(...); 		//[可選]給consumer添加監聽
複製代碼

除了基礎的側滑效果外,爲了方便開發者使用,還封裝了工具類:SmartSwipeBackSmartSwipeRefresh

一行代碼實現全局Activity側滑返回

  • 全局只需一行代碼便可搞定全部Activity側滑返回
  • 可選樣式:開門、百葉窗、仿微信、仿QQ及仿MIUI貝塞爾曲線
  • 無需透明主題
  • 無需繼承某個特定的Activity
  • 不須要侵入xml佈局文件
  • 也不須要侵入BaseActivity
  • 支持全屏側滑和(/或)邊緣側滑返回
  • 支持 上/下/左/右 4個方向側滑返回
//仿手機QQ的手勢滑動返回
SmartSwipeBack.activityStayBack(application, null);		
//仿微信帶聯動效果的透明側滑返回
SmartSwipeBack.activitySlidingBack(application, null);	
//側滑開門樣式關閉activity
SmartSwipeBack.activityDoorBack(application, null);		
//側滑百葉窗樣式關閉activity
SmartSwipeBack.activityShuttersBack(application, null);	
//仿小米MIUI系統的貝塞爾曲線返回效果
SmartSwipeBack.activityBezierBack(application, null);
複製代碼

全局側滑返回效果

一行代碼添加下拉刷新功能

可用於任意view

//xxxMode第二個參數爲false,表示工做方向爲縱向:下拉刷新&上拉加載更多
//若是第二個參數設置爲true,則表示工做方向爲橫向:右拉刷新&左拉加載更多
SmartSwipeRefresh.drawerMode(view, false).setDataLoader(loader);
SmartSwipeRefresh.behindMode(view, false).setDataLoader(loader);
SmartSwipeRefresh.scaleMode(view, false).setDataLoader(loader);
SmartSwipeRefresh.translateMode(view, false).setDataLoader(loader);
複製代碼
樣式 效果圖
drawerMode
drawerMode
behindMode
behindMode
scaleMode
scaleMode
translateMode
translateMode

header和footer可以使用第三方炫酷的自定義view,如:基於Ifxcyr/ArrowDrawableArrowHeader,效果圖以下:

ArrowHeader

看起來是蠻diǎo的,但是我要的側滑效果你這裏沒有啊

這就須要自定義SwipeConsumer了,步驟以下:

    1. 新建一個類,繼承SwipeConsumer
    1. [可選]在構造方法中進行一些初始化(須要context對象才能初始化的屬性,能夠放在onAttachToWrapper方法中初始化)
    1. [可選]若是有額外的捕獲邏輯,能夠重寫父類的tryAcceptMovingtryAcceptSettling方法
    1. [可選]重寫onSwipeAccepted方法,因爲此時已經肯定捕獲了側滑事件,並肯定好了側滑的方向(mDirection),能夠爲本次側滑事件作一些初始化工做
    1. [可選]重寫clampDistanceHorizontalclampDistanceHorizontal方法,可在知足必定條件下才真正執行側滑
    1. 重寫onDisplayDistanceChanged方法,執行具體的側滑的UI效果呈現
    1. [可選]若是UI呈現效果中包含佈局控件的移動,須要重寫onLayout方法,在此方法中也要按照側滑後的邏輯進行控件佈局定位
    1. 重寫onDetachFromWrapper方法,還原現場,移除當前consumer的全部改動痕跡

以框架內置彈性拉伸效果StretchConsumer爲例

根據側滑距離,對contentView進行縮放和平移,從而實現彈性拉伸效果
複製代碼

代碼以下:

public class StretchConsumer extends SwipeConsumer {
    @Override
    public void onDetachFromWrapper() {
        super.onDetachFromWrapper();
        View contentView = mWrapper.getContentView();
        if (contentView != null) {
            contentView.setScaleX(1);
            contentView.setScaleY(1);
            contentView.setTranslationX(0);
            contentView.setTranslationY(0);
        }
    }

    @Override
    public void onDisplayDistanceChanged(int distanceXToDisplay, int distanceYToDisplay, int dx, int dy) {
        View contentView = mWrapper.getContentView();
        if (contentView != null) {
            if (distanceXToDisplay >= 0 && isLeftEnable() || distanceXToDisplay <= 0 && isRightEnable()) {
                contentView.setScaleX(1 + Math.abs((float) distanceXToDisplay) / mWidth);
                contentView.setTranslationX(distanceXToDisplay / 2F);
            }
            if (distanceYToDisplay >= 0 && isTopEnable() || distanceYToDisplay <= 0 && isBottomEnable()) {
                contentView.setScaleY(1 + Math.abs((float) distanceYToDisplay) / mHeight);
                contentView.setTranslationY(distanceYToDisplay / 2F);
            }
        }
    }
}
複製代碼

以上就是實現彈性拉伸效果的所有代碼,很簡單,不是嗎?

這樣看來,也許還真能實現全部側滑效果誒?

能實現全部側滑效果只存在於理論上,確定還須要不斷地完善,開源出來也是但願能利用開源社區的力量來完善它,讓android側滑更簡單!

怎麼作到的呢?

SmartSwipe的大體封裝思路以下:

  • 用一個ViewGroup將須要處理側滑事件的控件View包裹起來,被包裹起來的控件做爲它的contentView,能夠爲這個ViewGroup添加一些附屬控件View(如:滑動抽屜)
  • 攔截這個ViewGroup的touch事件,並將touch事件轉換爲側滑距離交給SwipeConsumer進行消費
  • SwipeConsumer在消費側滑事件的過程當中,對contentView及附屬控件的UI呈現(位置、縮放、透明等)進行合理的加工,從而實現各類側滑的效果。

先明確框架中幾個關鍵類的角色定義:

  • SmartSwipe:
    • 框架入口類
    • 用於將一個view包裹起來並返回包裹它的SmartSwipeWrapper
  • SmartSwipeWrapper:
    • 自定義控件,繼承自ViewGroup,效率高於FrameLayout
    • 用於包裹須要添加側滑效果的view,被它包裹的view稱爲它的contentView
    • 有了它,開發者在使用時再也不須要自定義ViewGroup了,用起來更方便
  • SwipeConsumer:
    • 側滑策略的基類
    • 完成一些公共邏輯,如:側滑事件的捕獲策略、側滑距的離計算等
    • 公共屬性的設置及對應的邏輯處理
  • SwipeListener:
    • 側滑生命週期監聽類
    • 添加到SwipeConsumer中,各回調函數在SwipeConsumer對應的生命週期被觸發
  • SwipeHelper:
    • 根據ViewDragHelper修改而來

1. 將SmartSwipeWrapper、SwipeHelper和SwipeConsumer三者關聯起來

相似於使用ViewDragHelper,在SmartSwipeWrapper中,使用SwipeHelper來處理touch事件

public class SmartSwipeWrapper extends ViewGroup {
    protected SwipeHelper mHelper;
    //省略代碼若干...
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mHelper.processTouchEvent(event);
        return true;
    }
}
複製代碼

SwipeHelper攔截到滑動事件後,須要交給SwipeConsumer來消費,因此,須要將SwipeConsumer與SwipeHelper關聯起來(SwipeConsumer代替了ViewDragHelper中Callback的角色

public class SmartSwipeWrapper extends ViewGroup {
    protected SwipeConsumer mConsumer;
    protected SwipeHelper mHelper;
    //省略代碼若干...
    
    public <T extends SwipeConsumer> T addConsumer(T consumer) {
        if (consumer != null) {
            mConsumer = consumer;
            mHelper = SwipeHelper.create(this, consumer.getSensitivity(), consumer, consumer.getInterpolator());
            //省略代碼若干...
        }
        return consumer;
    }
}
複製代碼

因而,SmartSwipeWrapper、SwipeHelper 和 SwipeConsumer三者就創建起了關聯

2. 在SwipeHelper中攔截touch事件並

public class SwipeHelper {
    //當前SwipeHelper關聯的SwipeConsumer對象
    private final SwipeConsumer mSwipeConsumer;
    
    //當前X軸的側滑距離
    private int mClampedDistanceX;
    //當前Y軸的側滑距離
    private int mClampedDistanceY;
    
    //省略代碼若干...

    public boolean shouldInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getActionMasked();
        //省略代碼若干...

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                //省略代碼若干...
                if (mDragState == STATE_SETTLING || mDragState == STATE_NONE_TOUCH) {
                    trySwipe(pointerId, true, x, y, 0, 0);
                }
                break;
            }
            case MotionEvent.ACTION_POINTER_DOWN: {
                //省略代碼若干...
                if (mDragState == STATE_SETTLING || mDragState == STATE_NONE_TOUCH) {
                    trySwipe(pointerId, true, x, y, 0, 0);
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                //省略代碼若干...
                for (int i = 0; i < ev.getPointerCount(); i++) {
                    //省略代碼若干...
                    if (trySwipe(pointerId, false, downX, downY, dx, dy)) {
                        break;
                    }
                }
                break;
            }
            //省略代碼若干...
        }
        return mDragState == STATE_DRAGGING;
    }

    public void processTouchEvent(MotionEvent ev) {
        final int action = ev.getActionMasked();
        //省略代碼若干...

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                //省略代碼若干...
                trySwipe(pointerId, mDragState == STATE_SETTLING || mDragState == STATE_NONE_TOUCH, x, y, 0, 0);
                break;
            }

            case MotionEvent.ACTION_POINTER_DOWN: {
                //省略代碼若干...
                trySwipe(pointerId, true, x, y, 0, 0);
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                if (mDragState == STATE_DRAGGING) {
                    //省略代碼若干...
                    dragTo(mClampedDistanceX + idx, mClampedDistanceY + idy, idx, idy);
                } else {
                    for (int i = 0; i < pointerCount; i++) {
                        //省略代碼若干...
                        if (trySwipe(pointerId, false, downX, downY, dx, dy)) {
                            break;
                        }
                    }
                    saveLastMotion(ev);
                }
                break;
            }

            case MotionEvent.ACTION_POINTER_UP: {
                final int pointerId = ev.getPointerId(actionIndex);
                if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) {
                    //省略代碼若干...
                    for (int i = 0; i < pointerCount; i++) {
                        //省略代碼若干...
                        if (trySwipe(id, true, mInitialMotionX[id], mInitialMotionX[id], 0, 0)) {
                            newActivePointer = mActivePointerId;
                            break;
                        }
                    }
                    //省略代碼若干...
                }
                break;
            }
            //省略代碼若干...
        }
    }
    
    private boolean trySwipe(int pointerId, boolean settling, float downX, float downY, float dx, float dy) {
        //省略代碼若干...
        boolean swipe;
        if (settling) {
            //釋放後,動畫播放時,手指再次出發側滑,判斷是否捕獲
            swipe = mSwipeConsumer.tryAcceptSettling(pointerId, downX, downY);
        } else {
            //經過滑動位置及距離來決定是否捕獲touch事件做爲側滑
            swipe = mSwipeConsumer.tryAcceptMoving(pointerId, downX, downY, dx, dy);
        }
        if (swipe) {
            //省略代碼若干...
            
            //肯定捕獲成功,後續將由mSwipeConsumer來消費側滑事件
            //能夠在onSwipeAccepted方法中進行一些準備工做
            mSwipeConsumer.onSwipeAccepted(pointerId, settling, initX, initY);
            //初始化側滑距離
            mClampedDistanceX = mSwipeConsumer.clampDistanceHorizontal(0, 0);
            mClampedDistanceY = mSwipeConsumer.clampDistanceVertical(0, 0);
            setDragState(STATE_DRAGGING);
            return true;
        }
        return false;
    }
    //省略代碼若干...
}
複製代碼

SwipeHelper的主要職能就是進行touch事件攔截,經過接管SmartSwipeWrapper的onInterceptTouchEvent和onTouchEvent,在ACTION_DOWNACTION_MOVE等touch事件觸發時,經過trySwipe將touch事件的狀態交給SwipeConsumer來決定是否捕獲本次touch事件。

SwipeConsumer決定捕獲touch事件做爲側滑手勢後,其onSwipeAccepted方法會被調用,在此處能夠爲本次側滑作一些準備工做

3. 由SwipeConsumer來決定是否消費以及如何消費touch事件

public abstract class SwipeConsumer {
    
    protected int mDirection;
    //省略代碼若干...

    /** 在釋放動畫播放狀態或者多指交替滑動狀態判斷是否捕獲(是否消費本次滑動事件) */
    public boolean tryAcceptSettling(int pointerId, float downX, float downY) {
        //省略代碼若干...
        return isDirectionEnable(mDirection) && !isDirectionLocked(mDirection);
    }

    /** 靜止狀態下根據移動距離來判斷是否捕獲(是否消費本次滑動事件) */
    public boolean tryAcceptMoving(int pointerId, float downX, float downY, float dx, float dy) {
        //省略代碼若干...
        int dir = DIRECTION_NONE;
        boolean handle = false;
        if (Math.abs(dx) > Math.abs(dy)) {
            if (dx > 0 && isLeftEnable()) {
                dir = DIRECTION_LEFT;
                handle = true;
            } else if (dx < 0 && isRightEnable()) {
                dir = DIRECTION_RIGHT;
                handle = true;
            }
        } else {
            if (dy > 0 && isTopEnable()) {
                dir = DIRECTION_TOP;
                handle = true;
            } else if (dy < 0 && isBottomEnable()) {
                dir = DIRECTION_BOTTOM;
                handle = true;
            }
        }
        //省略代碼若干...
        if (handle) {
            if (isDirectionLocked(dir)) {
                handle = false;
            } else {
                mDirection = dir;
            }
        }
        return handle;
    }
}
複製代碼

SwipeConsumer根據自身的設置(側滑方向是否啓用、是否被鎖定等設定)及touch事件的座標和方向來決定是否捕獲本次touch事件做爲側滑事件來進行消費。

接下來看看SwipeConsumer是如何消費側滑距離的

public abstract class SwipeConsumer {
    //省略代碼若干...
    
    //消費側滑距離
    public void onSwipeDistanceChanged(int clampedDistanceX, int clampedDistanceY, int dx, int dy) {
        //省略代碼若干...
        
        //側滑距離確實發生了變化
        if (clampedDistanceX != mCurSwipeDistanceX || clampedDistanceY != mCurSwipeDistanceY) {
            //記錄實際側滑的距離
            mCurSwipeDistanceX = clampedDistanceX;
            mCurSwipeDistanceY = clampedDistanceY;
            //省略代碼若干...
            
            //計算側滑進度: 當前側滑距離 / 最大側滑距離
            switch (mDirection) {
                case DIRECTION_LEFT: case DIRECTION_RIGHT:
                    mProgress = Math.abs((float) mCurSwipeDistanceX / mSwipeOpenDistance);
                    break;
                case DIRECTION_TOP: case DIRECTION_BOTTOM:
                    mProgress = Math.abs((float) mCurSwipeDistanceY / mSwipeOpenDistance);
                    break;
                default:
            }
            if ((mDirection & DIRECTION_HORIZONTAL) > 0) { //橫向側滑
                int realDistanceX = clampedDistanceX;
                if (mSwipeDistanceCalculator != null) { //設置了側滑距離計算器
                    //用側滑距離計算器對當前側滑距離進行加工
                    realDistanceX = mSwipeDistanceCalculator.calculateSwipeDistance(clampedDistanceX, mProgress);
                }
                //計算出用於UI展現的側滑距離以及delta值
                dx = realDistanceX - mCurDisplayDistanceX;
                dy = 0;
                mCurDisplayDistanceX = realDistanceX;
            } else if ((mDirection & DIRECTION_VERTICAL) > 0) {
                int realDistanceY = clampedDistanceY;
                if (mSwipeDistanceCalculator != null) {
                    realDistanceY = mSwipeDistanceCalculator.calculateSwipeDistance(clampedDistanceY, mProgress);
                }
                dx = 0;
                dy = realDistanceY - mCurDisplayDistanceY;
                mCurDisplayDistanceY = realDistanceY;
            }
            //將加工後的側滑距離用於UI展現,該方法爲抽象方法,在子類中實現
            onDisplayDistanceChanged(mCurDisplayDistanceX, mCurDisplayDistanceY, dx, dy);
        }
        //省略代碼若干...
    }
    
    /** * 定義了一個抽象方法 * 子類根據加工後的側滑距離來進行UI展現 */
    protected abstract void onDisplayDistanceChanged(int distanceXToDisplay, int distanceYToDisplay, int dx, int dy);
}
複製代碼

子類中實現onDisplayDistanceChanged抽象方法,在這個方法中根據側滑距離來進行UI展現便可。

經過以上分析可知:側滑的定性(可否及是否捕獲)、定向(捕獲的事件所觸發的側滑方向)和定量(事件捕獲以後在側滑方向上移動的距離)的工做都在SwipeConsumer這個基類中完成了,具體的側滑效果只需在子類實現的onDisplayDistanceChanged方法中完成便可。

然而,不少側滑效果須要在控件的layout和draw的層面作些修改

4. SwipeConsumer的onLayout、onDraw和dispatchDraw

SmartSwipeWrapper默認狀態下它只有1個子控件:contentView,若是側滑效果須要添加其它子控件(例如:滑動抽屜),則由具體的側滑策略類(SwipeConsumer的子類)來完成layout;同理,SwipeConsumer也能夠接受到draw相關的回調

public class SmartSwipeWrapper extends ViewGroup {

    protected SwipeConsumer mConsumer;
    protected View mContentView;
    //省略代碼若干...
    
    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mConsumer != null) {
            mConsumer.dispatchDraw(canvas);
        }
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mConsumer != null) {
            mConsumer.onDraw(canvas);
        }
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //省略代碼若干...
        if (mConsumer != null) {
            mConsumer.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
    
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        boolean layoutByConsumer = false;
        if (mConsumer != null) {
            layoutByConsumer = mConsumer.onLayout(changed, left, top, right, bottom);
        }
        if (!layoutByConsumer) {
            //默認的佈局方式:只佈局contentView便可
            if (mContentView != null) {
                mContentView.layout(0, 0, mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
            }
        }
    }
}
複製代碼

你講累了,我也看累了,是時候總結一下了

SmartSwipe是一個側滑處理框架,而非某種具體的側滑效果,雖然它內部提供了十多種常見的側滑效果。

咱們也能夠用它來實現更多更復雜的側滑效果。 也能夠用這些效果來實現一些頗有意思的功能(例如:demo中用不一樣的側滑效果來製做封面、側滑刪除、下拉刷新等)

至此,SmartSwipe框架的介紹、功能演示以及核心工做流程已介紹完畢。因爲篇幅有限,介紹的比較簡略,能夠按照下方的鏈接地址查看源碼及採用gitbook形式精心編寫而成的文檔教程:

源碼: github.com/luckybilly/…

文檔: luckybilly.github.io/SmartSwipe-… (採用gitbook形式精心編寫而成)

Demo下載: github.com/luckybilly/…

最後:從個人github主頁中也許能發現更多你感興趣的項目哦 :) github.com/luckybilly

相關文章
相關標籤/搜索