android ui 學(xué)習(xí)系列 - 安卓自帶Behavior的使用

終于寫到 Behavior 這個(gè)東東了,這個(gè)東西是隨著 5.0 系統(tǒng),是 CoordinatorLayout 的一個(gè)重要特性,重要到現(xiàn)在很多 app 都在用。

什么是 Behavior

上面說(shuō)到 Behavior 是 CoordinatorLayout 的一個(gè)重要特性,我們可以管他叫:聯(lián)動(dòng)。啥,為啥叫這個(gè)名字,因?yàn)?Behavior 是把頁(yè)面中可滾動(dòng)控件的滾動(dòng)事件和其他任何對(duì)這個(gè)滾動(dòng)事件有興趣的控件結(jié)合起來(lái)。在封裝上看就是把滾動(dòng)事件拋出來(lái),由感興趣的控件來(lái)消費(fèi),很符合觀察者模式的思想。

大家試想,之前我們監(jiān)聽(tīng)滾動(dòng)事件都是在滾動(dòng)控件上注冊(cè) listener 對(duì)象,代碼上適合對(duì)象強(qiáng)耦合的,現(xiàn)在呢,google 把這種行為抽象成 Behavior 這個(gè)接口,提供一些列默認(rèn)實(shí)現(xiàn),也可以去自定義,當(dāng)然自定義才是最重要的環(huán)節(jié),并且包含部分 view 的布局方法在內(nèi),讓我們有很大的定制行,最主要的是可以做到代碼的解耦,功能上的代碼分割,更利于我們維護(hù)。


Behavior 的系統(tǒng)默認(rèn)實(shí)現(xiàn)

google 提供了很多的 Behavior 實(shí)現(xiàn),比如AppbarLayout內(nèi)部的Behavior,專門協(xié)調(diào) AppbarLayout 與可滾動(dòng)View(NestedScrollView,RecyclerView )的, FloatActionButton內(nèi)部的Behavior ,協(xié)調(diào)和Snackbar 的關(guān)系,保證Snackbar 彈出的時(shí)候不被FAB 遮擋。還有就是上面說(shuō)的Snackbar內(nèi)部的Behavior 等等。

另外還提供了幾個(gè)封裝好的帶很不錯(cuò)效果的 Behavior 實(shí)現(xiàn):

  • BottomSheetBehavior
  • BottomSheetDialog
  • SwipeDissmissBehavior

這2個(gè) Behavior 的實(shí)現(xiàn)就是這節(jié)我們學(xué)習(xí)的內(nèi)容,學(xué)習(xí)總是先易后難,自定義 Behavior 下一節(jié)說(shuō)。


Behavior 的使用注意項(xiàng)

Behavior 是 CoordinatorLayout 中 LayoutParams 的一個(gè)屬性,我們知道子類的 LayoutParams 類型是父類的 LayoutParams 類型,那么子類的子類的 LayoutParams 類型可就不是父類的 LayoutParams 類型,所以注意一下:

  1. CoordinatorLayout 必須作為項(xiàng)目的跟節(jié)點(diǎn)
  2. 想使用 Behavior 的 view 必須是 CoordinatorLayout 的直接子類

BottomSheetBehavior

什么是 BottomSheetBehavior ,從效果來(lái)描述就是底部彈出的卡片,看下效果圖:


ezgif.com-video-to-gif.gif

ps: 有個(gè)簡(jiǎn)單的例子介紹的比我清楚

看著是不是有些熟悉啊,一些 app 的點(diǎn)擊分享彈出的 view 不就是這個(gè)樣子的嘛!

使用起來(lái)也是很簡(jiǎn)單的:

  1. 我們?cè)?xml 中給目標(biāo) view 添加這個(gè) app:layout_behavior="@string/bottom_sheet_behavior"
  2. 我們使用代碼獲取到這個(gè) BottomSheetBehavior 對(duì)象, BottomSheetBehavior sheetBehavior = BottomSheetBehavior.from(shareView)
  3. 我們使用 mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED) 設(shè)置狀態(tài)的方法就能控制展開(kāi)還是折疊

我們主要關(guān)心3個(gè)參數(shù)

app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="@string/bottom_sheet_behavior"

// peekHeight 是當(dāng) Bottom Sheets 關(guān)閉的時(shí)候,底部我們能看到的高度,默認(rèn)是0不可見(jiàn)。
app:behavior_peekHeight="50dp"

// hideable是當(dāng)我們拖拽下拉的時(shí)候,bottom sheet是否能全部隱藏。
app:behavior_hideable="true"

需要注意的是:

  • BottomSheetBehavior 默認(rèn)是顯示折疊狀態(tài)的
  • app:behavior_peekHeight="0dp" 屬性設(shè)置 view 折疊狀態(tài)的高度
  • 設(shè)置 behavior_peekHeight=0 后才能做到默認(rèn)不顯示底部卡片,或者用代碼設(shè)置狀態(tài)也可以做到默認(rèn)不顯示、

setBottomSheetCallback 可以監(jiān)聽(tīng)回調(diào)狀態(tài), onStateChanged 監(jiān)聽(tīng)狀態(tài)的改變, onSlide 是拖拽的回調(diào), onStateChanged 可以監(jiān)聽(tīng)到的回調(diào)一共有5種:

BottomSheetBehavior 的5種狀態(tài):

  • STATE_EXPANDED
    展開(kāi)狀態(tài),顯示完整布局。
  • STATE_COLLAPSED
    折疊狀態(tài),顯示peekHeigth 的高度,如果peekHeight為0,則全部隱藏,與STATE_HIDDEN效果一樣。
  • STATE_DRAGGING
    拖拽時(shí)的狀態(tài)
  • STATE_HIDDEN
    隱藏時(shí)的狀態(tài)
  • STATE_SETTLING
    釋放時(shí)的狀態(tài)

xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="switchState"
        android:text="展開(kāi)顯示"
        android:textSize="22sp"/>

    <android.support.constraint.ConstraintLayout
        android:id="@+id/view_bottom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        app:behavior_peekHeight="0dp"
        app:layout_behavior="@string/bottom_sheet_behavior">

        <TextView
            android:id="@+id/view1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="switchState"
            android:text="第一行代碼"
            android:textSize="22sp"/>

        <TextView
            android:id="@+id/view2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:onClick="switchState"
            android:text="第二行代碼"
            android:textSize="22sp"
            app:layout_constraintTop_toBottomOf="@id/view1"/>

        <TextView
            android:id="@+id/view3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:onClick="switchState"
            android:text="開(kāi)發(fā)藝術(shù)探索"
            android:textSize="22sp"
            app:layout_constraintTop_toBottomOf="@id/view2"/>

        <TextView
            android:id="@+id/view4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:onClick="switchState"
            android:text="android進(jìn)階之光"
            android:textSize="22sp"
            app:layout_constraintTop_toBottomOf="@id/view3"/>

    </android.support.constraint.ConstraintLayout>
</android.support.design.widget.CoordinatorLayout>

java

public class BottomSheetBehaviorActivity extends AppCompatActivity {

    private View view_bottom;
    private BottomSheetBehavior mBottomSheetBehavior;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bottom_sheet_behavior);
        view_bottom = findViewById(R.id.view_bottom);
        mBottomSheetBehavior = BottomSheetBehavior.from(view_bottom);
    }

    public void switchState(View view) {

        if (!(view instanceof Button) || mBottomSheetBehavior == null) {
            return;
        }

        Button button = (Button) view;
        if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
            mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
            button.setText("展開(kāi)顯示");
            return;
        }

        if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
            mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            button.setText("折疊收起");
            return;
        }
    }

BottomSheetDialog

BottomSheetDialog 是對(duì) BottomSheetBehavior 的封裝,用一個(gè) dialog 作為模板,綁定我們的特定 layout 布局,BottomSheetDialog 的效果比 BottomSheetBehavior 要好玩多了,他可以支持拖拽到頂部,成為一個(gè) activity 的樣子,使用上和 dialog 是一樣的。

大家注意啊,BottomSheetDialog 是系統(tǒng)實(shí)現(xiàn),不是自定義實(shí)現(xiàn)啊,我剛看的時(shí)候誤認(rèn)為是自定義實(shí)現(xiàn)了,還去看源碼呢。。。

先看看效果:


ezgif.com-video-to-gif.gif
 if (dialog == null) {
            dialog = new BottomSheetDialog(this);
            dialog.setContentView(R.layout.layout_book_list2);
            dialog.setCanceledOnTouchOutside(true);
            dialog.setCancelable(true);
        }
        dialog.show();

是不是很簡(jiǎn)單,就是這么任性,就是一個(gè) dialog...

網(wǎng)易云音樂(lè)的播放列表,高德地圖的路線規(guī)劃都是用這個(gè)做的,都是可以拖拽到頂作為一個(gè) activity 的樣子去顯示,然后滑動(dòng)關(guān)閉,說(shuō)實(shí)話這個(gè)效果第一次使用真是驚艷我了,還在想這個(gè)是怎么實(shí)現(xiàn)的呢,肯定很復(fù)雜吧,要讓 view 展示出來(lái),然后監(jiān)聽(tīng)滑動(dòng),拖拽到頂停止,然后監(jiān)聽(tīng)滑動(dòng)做位移,隱藏 view,好復(fù)雜。沒(méi)想到原來(lái)就是這么一個(gè) dialog,真是想感嘆封裝的強(qiáng)大啊,所以學(xué)號(hào)設(shè)計(jì)模式,然后做好封裝才是代碼功底的極大提現(xiàn)啊。


SwipeDissmissBehavior

這個(gè)效果真心覺(jué)得沒(méi)啥用,效果號(hào)僵硬好爛,就是view 隨著手指的左移右移,然后位移隱藏自己,隱藏不能隨著手機(jī)移動(dòng),很僵硬。

老規(guī)矩還是來(lái)看下:


ezgif.com-video-to-gif.gif

還是給我們的目標(biāo) view 添加這個(gè) Behavior 即可,然后代碼 new 一個(gè) SwipeDismissBehavior 出來(lái),設(shè)置參數(shù),然后綁定給 view 的 layoutParams 參數(shù)

        mSwipeLayout = findViewById(R.id.swipe_layout);
        SwipeDismissBehavior swipe = new SwipeDismissBehavior();

        /**
         * //設(shè)置滑動(dòng)的方向,有3個(gè)值
         *
         * 1,SWIPE_DIRECTION_ANY 表示向左像右滑動(dòng)都可以,
         * 2,SWIPE_DIRECTION_START_TO_END,只能從左向右滑
         * 3,SWIPE_DIRECTION_END_TO_START,只能從右向左滑
         */
        swipe.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);

        swipe.setStartAlphaSwipeDistance(0f);

        swipe.setSensitivity(0.2f);

        swipe.setListener(new SwipeDismissBehavior.OnDismissListener() {
            @Override
            public void onDismiss(View view) {
                Log.e(TAG,"------>onDissmiss");
            }

            @Override
            public void onDragStateChanged(int state) {
                Log.e(TAG,"------>onDragStateChanged");
            }
        });

        CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mSwipeLayout.getLayoutParams();
        if(layoutParams!=null){
            layoutParams.setBehavior(swipe);
        }

ps: demo 地址: github


參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容