微信側滑返回的聯動效果,但開門效果、百葉窗效果 [復制鏈接]

2019-7-24 09:02
一碼到底 閱讀:414 評論:0 贊:0
Tag:  

SmartSwipe(https://github.com/luckybilly/SmartSwipe)是一個Android側滑處理框架,它封裝了對控件側滑事件(上/下/左/右4個方向滑動的手勢事件)的捕獲、分發及多點交替滑動的處理,基于SmartSwipe我們可以為控件添加各種你想要的側滑效果。

先來看看它能做些什么吧!

  1. 如果已經了解SmartSwipe功能,只是想了解他的實現原理

  2. 可跳過第一節,直接看第二節的原理介紹

一、 用法及演示

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

  1. //仿手機QQ的手勢滑動返回

  2. SmartSwipeBack.activityStayBack(application, null);

  3. //仿微信帶聯動效果的透明側滑返回

  4. SmartSwipeBack.activitySlidingBack(application, null);

  5. //側滑開門樣式關閉activity

  6. SmartSwipeBack.activityDoorBack(application, null);

  7. //側滑百葉窗樣式關閉activity

  8. SmartSwipeBack.activityShuttersBack(application, null);

  9. //仿小米MIUI系統的貝塞爾曲線返回效果

  10. SmartSwipeBack.activityBezierBack(application, null);

側滑返回的更多用法請進入 https://github.com/luckybilly/SmartSwipe

效果圖:

1.2 一行代碼讓頁面動起來

  1. //為控件添加仿iOS的彈性留白效果:

  2. //當縱向不能滾動(或滾動到頂/底)時,若繼續拖動,則UI呈現彈性留白效果,釋放后平滑恢復

  3. SmartSwipe.wrap(view)

  4. .addConsumer(new SpaceConsumer())

  5. .enableVertical();

效果圖:

1.3 一行代碼讓頁面具有彈性

  1. //為控件添加仿MIUI的彈性拉伸效果:

  2. //當縱向不能滾動(或滾動到頂/底)時,若繼續拖動,則UI呈現彈性拉伸效果,釋放后平滑恢復

  3. SmartSwipe.wrap(view)

  4. .addConsumer(new StretchConsumer())

  5. .enableVertical();

效果圖:

1.4 一行代添加下拉刷新

  1. //xxxMode第二個參數為false,表示工作方向為縱向:下拉刷新&上拉加載更多

  2. //如果第二個參數設置為true,則表示工作方向為橫向:右拉刷新&左拉加載更多

  3. SmartSwipeRefresh.drawerMode(view, false).setDataLoader(loader);

  4. SmartSwipeRefresh.behindMode(view, false).setDataLoader(loader);

  5. SmartSwipeRefresh.scaleMode(view, false).setDataLoader(loader);

  6. SmartSwipeRefresh.translateMode(view, false).setDataLoader(loader);

下拉刷新的更多用法請戳 這里

樣式效果圖
drawerMode

behindMode

scaleMode

translateMode

1.5 一行代碼添加滑動菜單

  1. SmartSwipe.wrap(view)

  2. //添加抽屜效果,其效果與DrawerLayout相似

  3. // DrawerLayout只支持左右2個方向,而DrawerConsumer支持上下左右4個方向

  4. .addConsumer(new DrawerConsumer())

  5. //設置橫向(左右兩側)的抽屜為同一個view(常見的側滑顯示刪除按鈕的功能)

  6. .setHorizontalDrawerView(buttonsViewGroup)

  7. .setScrimColor(0x2F000000) //設置遮罩的顏色

  8. .setShadowColor(0x80000000) //設置邊緣的陰影顏色

  9. ;

效果圖:

1.6 一行代碼添加具有聯動效果的滑動菜單

  1. SmartSwipe.wrap(view)

  2. .addConsumer(new SlidingConsumer())

  3. .setRelativeMoveFactor(0.3F) //聯動系數

  4. .setHorizontalDrawerView(buttonsView)

  5. .setScrimColor(0x2F000000)

  6. ;

效果圖如下:

1.7 炫酷的封面

  1. SmartSwipe.wrap(coverView)

  2. .addConsumer(new ShuttersConsumer()) //百葉窗效果

  3. .setScrimColor(0xAF000000)

  4. .enableAllDirections()

  5. .addListener(new SimpleSwipeListener() {

  6. @Override

  7. public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {

  8. //封面打開后自動隱藏或移除

  9. wrapper.setVisibility(View.GONE);

  10. }

  11. });

效果圖:

  1. SmartSwipe.wrap(coverView)

  2. .addConsumer(new DoorConsumer()) //開門效果

  3. .setScrimColor(0xAF000000)

  4. .enableAllDirections()

  5. .addListener(new SimpleSwipeListener() {

  6. @Override

  7. public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {

  8. //封面打開后自動隱藏或移除

  9. wrapper.setVisibility(View.GONE);

  10. }

  11. });

效果圖:

關于封面的更多設置請參考:Demo

二、實現原理

2.1 先介紹一下ViewDragHelper

ViewDragHelper是Android官方支持庫中有一個工具類。它可以幫助我們處理控件的拖拽:先創建一個自定義ViewGroup,將被拖動的控件添加到這個自定義ViewGroup中,并用ViewDragHelper來處理控件的拖拽。

ViewDragHelper的主要作用是:攔截父容器的touch事件,捕獲一個子控件來進行拖拽,通過改變這個子控件的left和top來將其在父容器中重新定位,從而達到拖拽的效果。

在官方支持庫中,滑動抽屜相關的SlidingPaneLayout和DrawerLayout,以及CoordinatorLayout布局相關的BottomSheetBehavior和SwipeDismissBehavior,都能看到ViewDragHelper的身影。

但是,ViewDragHelper的名稱也表明它就是用來處理拖拽的,拖拽的對象必須是一個子View,在拖拽的過程中需要改變子控件的left和top,對于一些沒有子View被拖拽的側滑效果(例如:MIUI系統的貝塞爾曲線側滑返回效果、手機QQ的側滑返回效果及MIUI官方app中的普遍使用了的彈性拉伸效果等等),卻有點力有不逮。

2.2 借鑒ViewDragHelper實現側滑處理

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

  • 狹義側滑:從屏幕的某個邊緣開始向著遠離該邊緣的方向滑動

  • 廣義側滑:手指在屏幕上按下之后向著某個方向滑動

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

既然側滑手勢能被明確地抽象出來,那么我們是否可以借鑒ViewDragHelper的事件攔截思路將它做這樣的封裝?

  1. 對被側滑控件的touch事件進行攔截分析,確認是否將其捕獲作為側滑手勢

  2. 然后計算好側滑的實時位移(手指滑動的位移,而不是不依賴于Viewlefttop)

  3. 再通過策略模式(Strategy Pattern)使用不同的策略不斷消費側滑的位移來進行側滑效果的UI呈現。

答案是肯定的!

2.3 SmartSwipe的實現原理

SmartSwipe在ViewDragHelper的基礎上,將它對子View的捕獲及移動處理改造成對父View自身觸摸事件的定性(能否及是否捕獲)、定向(捕獲的事件所觸發的側滑方向)及定位(事件捕獲之后在側滑方向上移動的距離),并將側滑距離交由SwipeConsumer來消費,SwipeConsumer根據側滑距離的變化對控件布局進行相應的改變。

SmartSwipe的封裝思路如下:

  • 用一個ViewGroup將需要處理側滑事件的控件View包裹起來(被包裹起來的控件作為它的 contentView

  • 可以為這個ViewGroup添加一些附屬控件(如:滑動抽屜

  • 攔截這個ViewGroup的touch事件,并將touch事件轉換為側滑距離交給SwipeConsumer進行消費

  • SwipeConsumer根據側滑距離的變化對控件布局進行相應的改變

  • 通過繼承SwipeConsumer,用不同的方式來改變控件布局(例如:對 contentView及附屬控件的位置、縮放、透明等進行改變),從而實現各種側滑的效果。

于是,側滑的手勢事件識別及滑動距離計算的工作在框架內部就統一完成了,至于根據側滑距離來實現各種不同的UI呈現效果,就可以很方便地通過繼承SwipeConsumer來實現了。

2.4 如何創建自定義SwipeConsumer?

以框架內置的仿MIUI系統應用中彈性拉伸效果的實現為例

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

代碼如下:

  1. public class StretchConsumer extends SwipeConsumer {

  2. @Override

  3. public void onDetachFromWrapper() {

  4. super.onDetachFromWrapper();

  5. View contentView = mWrapper.getContentView();

  6. if (contentView != null) {

  7. contentView.setScaleX(1);

  8. contentView.setScaleY(1);

  9. contentView.setTranslationX(0);

  10. contentView.setTranslationY(0);

  11. }

  12. }


  13. @Override

  14. public void onDisplayDistanceChanged(int distanceXToDisplay, int distanceYToDisplay, int dx, int dy) {

  15. View contentView = mWrapper.getContentView();

  16. if (contentView != null) {

  17. if (distanceXToDisplay >= 0 && isLeftEnable() || distanceXToDisplay <= 0 && isRightEnable()) {

  18. contentView.setScaleX(1 + Math.abs((float) distanceXToDisplay) / mWidth);

  19. contentView.setTranslationX(distanceXToDisplay / 2F);

  20. }

  21. if (distanceYToDisplay >= 0 && isTopEnable() || distanceYToDisplay <= 0 && isBottomEnable()) {

  22. contentView.setScaleY(1 + Math.abs((float) distanceYToDisplay) / mHeight);

  23. contentView.setTranslationY(distanceYToDisplay / 2F);

  24. }

  25. }

  26. }

  27. }

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

它的使用方式同樣簡單:

  1. SmartSwipe.wrap(view) //指定目標控件

  2. .addConsumer(new StretchConsumer()) //添加彈性拉伸效果

  3. .enableVertical(); //指定工作方向為:上、下2個方向

再來看看仿手機QQ側滑返回的效果如何實現

  1. 手機QQ側滑時UI沒有任何變化

  2. 在手指釋放時,根據滑動的方向和速率來決定是否finish當前Activity

代碼如下:

  1. public class StayConsumer extends SwipeConsumer {

  2. private int mMinVelocity = 1000;


  3. public StayConsumer() {

  4. //不能通過滑動距離判斷是否需要打開

  5. setOpenDistance(Integer.MAX_VALUE)

  6. .setMaxSettleDuration(0); //打開時無需動畫,時間置為0

  7. }


  8. @Override

  9. protected void onDisplayDistanceChanged(int distanceXToDisplay, int distanceYToDisplay, int dx, int dy) {

  10. //滑動時不需要對contentView做任何改變

  11. }


  12. @Override

  13. public void onSwipeReleased(float xVelocity, float yVelocity) {

  14. //在釋放時,根據速率和方向來決定是否打開

  15. if (Math.abs(xVelocity) > Math.abs(yVelocity)) {

  16. if (mDirection == DIRECTION_LEFT && xVelocity >= mMinVelocity || (mDirection == DIRECTION_RIGHT && xVelocity <= -mMinVelocity)) {

  17. //置為打開狀態

  18. mCurSwipeDistanceX = getSwipeOpenDistance();

  19. mProgress = 1;

  20. }

  21. } else {

  22. if (mDirection == DIRECTION_TOP && yVelocity >= mMinVelocity || (mDirection == DIRECTION_BOTTOM && yVelocity <= -mMinVelocity)) {

  23. //置為打開狀態

  24. mCurSwipeDistanceY = getSwipeOpenDistance();

  25. mProgress = 1;

  26. }

  27. }

  28. super.onSwipeReleased(xVelocity, yVelocity);

  29. }


  30. public int getMinVelocity() {

  31. return mMinVelocity;

  32. }


  33. //支持使用者設置最低速率的閾值

  34. public StayConsumer setMinVelocity(int minVelocity) {

  35. if (minVelocity > 0) {

  36. this.mMinVelocity = minVelocity;

  37. }

  38. return this;

  39. }

  40. }


我來說兩句
您需要登錄后才可以評論 登錄 | 立即注冊
facelist
所有評論(0)
領先的中文移動開發者社區
18620764416
7*24全天服務
意見反饋:[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

两码中特期期