Android側滑原來可以這么優雅

前言

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

目前,我們需要為每種場景引入不同的側滑框架,由于App中的側滑場景很多,我們項目中也就需要引入多個側滑框架,而每個框架的使用方式各有不同,需要單獨學習,團隊的學習成本較高。

那么問題來了,有沒有一種框架能解決所有側滑需求呢?

一個框架解決所有側滑需求?你確定不是在開玩笑?

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

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

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

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

于是,側滑的概念就這樣被清晰地抽象出來了。

從這個抽象概念可以看出:側滑手勢同一時間只處理上下左右4個方向中的一個方向

如果我們將這個抽象概念封裝出來,將手勢事件的識別、攔截及數據加工在框架內部處理好,并向外實時地輸出側滑方向距離及相關的回調, 理論上我們就可以實現所有的側滑需求了。

至于具體的側滑效果,學過策略模式的都知道:
每一種具體的側滑效果實現都可以看做是一種側滑策略。

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

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

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

看起來是蠻diao的,可是我要的側滑效果你這里沒有啊

這就需要自定義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側滑更簡單!

最后,奉上相關鏈接地址:

源碼: https://github.com/luckybilly/SmartSwipe

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

Demo下載: https://github.com/luckybilly/SmartSwipe/raw/master/app-release.apk

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容