BottomSheet效果的兩種實現(xiàn)方式

BottomSheet效果

BottomSheet的效果是指從屏幕底部向上滑的效果,是MaterialDesign風(fēng)格的一種,視覺效果如下:


BottomSheet效果

實現(xiàn)這種效果有幾種不同的方式,如果是在一個固定的頁面上添加這種效果,可以在該頁面布局中添加BoottomSheet相關(guān)的控件。如果是作為通用控件來提供給不同頁面使用,則可以使用BottomSheetDialog實現(xiàn),本文將對兩種方法進行講解,其中會講到一些使用上的細節(jié),處理不好這些細節(jié),會出現(xiàn)非常怪異的效果。

單頁面添加BottomSheet效果

首先引入依賴包:

compile 'com.android.support:design:27.1.1'

頁面布局如下:

<?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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/customActionWebView"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="文本一"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="文本二"/>
    </LinearLayout>

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/nested_scroll_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:behavior_hideable="true"
        app:behavior_peekHeight="150dp"
        android:layout_gravity="bottom"
        app:layout_behavior="@string/bottom_sheet_behavior">

        <WebView
            android:id="@+id/web_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </WebView>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

根布局需要使用CoordinatorLayout,同時在其直接子布局——這里是NestedScrollView——里添加behavior app:layout_behavior = "@string/bottom_sheet_behavior"。很多文章說指定behavior的控件必須是NestedScrollView,這是錯誤的,實際上任何view或viewgroup都可以。該behavior配合CoordinateLayout就可以實現(xiàn)behavior所在控件的上滑效果。如果需要上滑的布局展示的時候先漏出一部分,如上面視頻所示,可以通過設(shè)置app:behavior_peekHeight實現(xiàn),它用來指定漏出的高度。

在代碼部分,首先獲取NestedScrollView的behavior,然后通過behavior控制底部卡片什么時候彈出,同時會有一些狀態(tài)回調(diào)函數(shù)可供調(diào)用。

public class BrowserActivity extends AppCompatActivity {
    private NestedScrollView nestedScrollView;
    private BottomSheetBehavior behavior;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.browser_layout);

        nestedScrollView = (NestedScrollView) findViewById(R.id.nested_scroll_view);
        behavior = BottomSheetBehavior.from(nestedScrollView);
        behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {
                //這里是bottomSheet 狀態(tài)的改變
            }

            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {
                //這里是拖拽中的回調(diào),根據(jù)slideOffset可以做一些動畫
            }
        });
    }

    public void showBottomSheet() {
        behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
    }
}

通過這種方式可以在特定的頁面添加底部上滑的效果。

BottomSheetDialog實現(xiàn)通用效果

BottomSheetDialog是BottomSheet效果實現(xiàn)的一種更加通用的方法,比如我們需要在不同的頁面實現(xiàn)長按文本彈出卡片列表效果,下面給出實現(xiàn)。

我們集成BottomSheetDialog實現(xiàn)自定義的Dialog,其布局如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView 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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <view class="com.example.trio.tensordemo.CustomBottomSheetDialogForWebView$NestViewEmbeddedListView"
            android:id="@+id/card_list_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:divider="@color/transparent"
            android:scrollbars="none"
            android:dividerHeight="15dp"
            android:layout_margin="20dp">
        </view>
    </LinearLayout>

</android.support.v4.widget.NestedScrollView>

注意,這里最外層布局需要是NestedScrollView,而不能是CoordinateLayout,因為BottomSheetDialog本身已經(jīng)有個CoordinateLayout根布局,它會把你的布局文件包裹起來,如果你在自己的布局里把最外層布局寫成CoordinateLayout,會導(dǎo)致底部上滑的卡片,在下滑消失后屏幕依舊變暗的問題,這是因為整個布局變成了兩個CoordinateLayout嵌套,下滑的時候里面的CoordinateLayout滑出屏幕,但外層的CoordinateLayout仍然在展示。通過查閱BottomSheetDialog源碼可以看出,它是這樣包裹你的布局文件的:

public class BottomSheetDialog extends AppCompatDialog {
    ...
    @Override
    public void setContentView(@LayoutRes int layoutResId) {
        super.setContentView(wrapInBottomSheet(layoutResId, null, null));
    }
    
    private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
        final FrameLayout container = (FrameLayout) View.inflate(getContext(),
                R.layout.design_bottom_sheet_dialog, null);
        final CoordinatorLayout coordinator =
                (CoordinatorLayout) container.findViewById(R.id.coordinator);
        if (layoutResId != 0 && view == null) {
            view = getLayoutInflater().inflate(layoutResId, coordinator, false);
        }
        FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
        mBehavior = BottomSheetBehavior.from(bottomSheet);
        mBehavior.setBottomSheetCallback(mBottomSheetCallback);
        mBehavior.setHideable(mCancelable);
        if (params == null) {
            bottomSheet.addView(view);
        } else {
            bottomSheet.addView(view, params);
        }
        // We treat the CoordinatorLayout as outside the dialog though it is technically inside
        coordinator.findViewById(R.id.touch_outside).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mCancelable && isShowing() && shouldWindowCloseOnTouchOutside()) {
                    cancel();
                }
            }
        });
        ...
        return container;
    }
    ...
}

所以,BottomSheetDialog本身的布局實際如下:


BottomSheetDialog頁面布局

我們可以看到最外層是FrameLayout,里面有個CoordinateLayout,CoordinateLayout里面有個直接子布局FrameLayout,該CoordinateLayout指定了Behavior,最里面才是用戶自定義的布局,所以不應(yīng)該在自定義布局里再添加CoordinateLayout,也不應(yīng)該再次指定Behavior,直接擺放你的內(nèi)容就行。

我們的自定義布局如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView 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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <view class="com.example.trio.tensordemo.CustomBottomSheetDialogForWebView$NestViewEmbeddedListView"
            android:id="@+id/card_list_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:divider="@color/transparent"
            android:scrollbars="none"
            android:dividerHeight="15dp"
            android:layout_margin="20dp">
        </view>
    </LinearLayout>

</android.support.v4.widget.NestedScrollView>

布局的核心是一個ListView,注意,由于ListView和behavior都需要處理滑動事件,所以直接使用ListView會導(dǎo)致滑動沖突,解決辦法是采用ScrollView嵌套ListView實現(xiàn),同時使用自定義的ListView將所有列表項展開。

自定義的NestViewEmbeddedListView如下:

public static class NestViewEmbeddedListView extends ListView {
        public NestViewEmbeddedListView(android.content.Context context, android.util.AttributeSet attrs){
            super(context, attrs);
        }
        /**
         * 設(shè)置不滾動
         */
        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
        {
            int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
                    MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, expandSpec);

        }
    }

自定義的BottomSheetDialog代碼如下:

public class CustomBottomSheetDialogForWebView extends BottomSheetDialog {
    private Context context;
    private NestViewEmbeddedListView listView;
    private NerResult nerResult;
    private CardListAdapter cardListAdapter = new CardListAdapter();

    public CustomBottomSheetDialogForWebView(@NonNull Context context, NerResult nerResult) {
        super(context);
        this.context = context;
        this.nerResult = nerResult;
        createView();
    }

    public void createView() {
        View bottomSheetView = getLayoutInflater().inflate(R.layout.webview_bottom_sheet_layout, null);
        setContentView(bottomSheetView);

        // 注意:這里要給layout的parent設(shè)置peekHeight,而不是在layout里給layout本身設(shè)置,下面設(shè)置背景色同理,坑爹?。。?        BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(((View) bottomSheetView.getParent()));
        bottomSheetBehavior.setPeekHeight(730);

        ((View) bottomSheetView.getParent()).setBackgroundColor(context.getResources().getColor(R.color.transparent));
        listView = bottomSheetView.findViewById(R.id.card_list_view);

        cardListAdapter.setNerItems(nerResult);
        listView.setAdapter(cardListAdapter);
    }
}

這里需要注意的就是,設(shè)置背景透明和獲取Behavior都是對自定義布局的父布局,也就是bottomSheetView.getParent()進行。最終的效果就是下面的效果:


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

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

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