美文网首页
Meterial Design常见控件的使用(六):Navigo

Meterial Design常见控件的使用(六):Navigo

作者: bug音音 | 来源:发表于2021-01-11 21:39 被阅读0次

    今天的效果在支付宝、淘宝、京东等电商App中很常见。比如支付宝输入密码弹窗、商城下单时选择商品属性时,从下面浮动上来一个PopupWindow,那么今天就带大家用Behavior来实现这两个效果,结果你会发现简直只需要一行代码。

    总结下现在用的APP:

    1. 仿支付宝弹出的输入支付密码窗口。
    2. 仿淘宝/天猫弹出商品属性选择框。
    3. 知乎首页上下滑动隐藏ToolBar和NavigationBar。

    效果预览

    效果预览

    源码下载:http://download.csdn.net/detail/yanzhenjie1003/9578770,推荐先阅读博客理解原理。

    引文

    在我的技术群里有小伙伴们讨论Behavior,我也去玩了玩,我也对Behavior写了系列博客。选中Behavior然后ctrol + t后发现Behavior的一个实现类:BottomSheetBehavior,我就到Android官网上翻了下资料,一翻就发现了惊喜啊,下面就把这些惊喜介绍给大家。

    更多文章请Google/百度搜索我名字:严振杰,排名第一的就是我。

    BottomSheetBehavior怎么玩(知乎Bottom隐藏和显示)

    玩这个东西,首先Behavior作为CoordinatorLayout的子View的LayoutParams(原因看后文解释),所以CoordinatorLayout是万万不能少的,先来亮出整个布局:

    <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">
    
        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:popupTheme="@style/AppTheme.PopupOverlay" />
        </android.support.design.widget.AppBarLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@+id/tab_layout"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            <Button
                android:id="@+id/btn_bottom_sheet_control"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="sheet 显示/隐藏" />
    
        </LinearLayout>
    
        <LinearLayout
            android:id="@+id/tab_layout"
            android:layout_width="match_parent"
            android:layout_height="?actionBarSize"
            android:layout_alignParentBottom="true"
            android:background="@android:color/holo_purple"
            app:layout_behavior="@string/bottom_sheet_behavior">
    
            <Button
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="第一" />
    
            <Button
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="第二" />
    
            <Button
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="第三" />
    
            <Button
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="第四" />
        </LinearLayout>
    
    </android.support.design.widget.CoordinatorLayout>12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
    

    大概介绍下,页面上只能看到Toolbar和一个Buttonsheet 显示/隐藏,然后android:id="@+id/tab_layout"这个布局是横向的,给它设置了Behaviorapp:layout_behavior="@string/bottom_sheet_behavior",经过测试发现,如果不给tab_layout设置BottomSheetBehavior,它会浮动在整个页面的顶部,并在Toolbar的下面。设置了BottomSheetBehavior它会被BottomSheetBehavior自动移动到页面底部外边,所以在页面上是看不到android:id="@+id/tab_layout"这个布局的。

    页面画好了,难道它会自动开关吗,怎么去控制它的打开和关闭呢?那么我们就来看看这货的真实面貌,经过我看Android的官方api发现,BottomSheetBehavior这个货有一个静态方法BottomSheetBehavior.from(View),会返回这个View引用的BottomSheetBehavior

    public static <V extends View> BottomSheetBehavior<V> from(V view) {
        ViewGroup.LayoutParams params = view.getLayoutParams();
        if (!(params instanceof CoordinatorLayout.LayoutParams)) {
            throw new Exception("The view is not a child of CoordinatorLayout");
        }
        CoordinatorLayout.Behavior behavior = params.getBehavior();
        if (!(behavior instanceof BottomSheetBehavior)) {
            throw new IllegalArgumentException("...");
        }
        return (BottomSheetBehavior<V>) behavior;
    }1234567891011
    

    这个方法会检查这个View是否是CoordinatorLayout的子View,如果是才会去拿到这个View的Behavior,所以诸位也应该明白为什么我开头说Behavior作为CoordinatorLayout子View的LayoutParams了。

    接下来我们看看拿到这个货后怎么用:

    private BottomSheetBehavior mBottomSheetBehavior;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.bsbehavior_activity);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    
        findViewById(R.id.btn_bottom_sheet_control).setOnClickListener(onClickListener);
        // 拿到这个tab_layout对应的BottomSheetBehavior
        mBottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.tab_layout));
    }1234567891011121314
    

    findViewById(R.id.btn_bottom_sheet_control).setOnClickListener(onClickListener);是给刚才说的页面中的Button设置了监听,我们用这个按钮来控制tab_layout的显示和隐藏。

    我发现有一个方法可以获取到它所依附的View此时的状态:mBottomSheetBehavior.getState(),翻阅了源码后发现它的返回值有以下几种:

    /**
     * The bottom sheet is dragging.
     */
    public static final int STATE_DRAGGING = 1;
    
    /**
     * The bottom sheet is settling.
     */
    public static final int STATE_SETTLING = 2;
    
    /**
     * The bottom sheet is expanded.
     */
    public static final int STATE_EXPANDED = 3;
    
    /**
     * The bottom sheet is collapsed.
     */
    public static final int STATE_COLLAPSED = 4;
    
    /**
     * The bottom sheet is hidden.
     */
    public static final int STATE_HIDDEN = 5;123456789101112131415161718192021222324
    

    当我看到STATE_EXPANDEDSTATE_COLLAPSED就明白了它的用法了,不就是展开和隐藏起来了麽?所以我们判断这个状态,如果是隐藏就显示,如果是显示就隐藏:

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_bottom_sheet_control) {
            if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
                mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
            } else if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
                mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        }
    }12345678910
    

    到这里,知乎首页的Bottom的隐藏和显示也就讲玩了,接下来我们来看看支付宝淘宝的下方弹窗如何实现。

    BottomSheetDialog怎么玩(商城下单商品属性选择弹窗)

    这个类的发现也是在Android官网搜索BottomSheetBehavior时发现的,一看到BottomSheetDialog后心中狂喜,后来经过我验证,它显示的效果和我猜想的一模一样啊,既然是个Dialog,那么用法应该和普通Dialog没啥去区别了吧。

    然后我就顺势new了一个BottomSheetDialog

    private BottomSheetDialog mBottomSheetDialog;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
    
        createBottomSheetDialog();
    }
    
    private void createBottomSheetDialog() {
        mBottomSheetDialog = new BottomSheetDialog(this);
        View view = LayoutInflater.from(this).inflate(R.layout.dialog_bottom_sheet, null, false);
        mBottomSheetDialog.setContentView(view);
    
        RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
        ...
        recyclerView.setAdapter(adapter);
    }12345678910111213141516171819
    

    View里面是一个RecyclerView

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />12345
    

    然后用下面的代码去控制它的显示和隐藏。

    if (mBottomSheetDialog.isShowing()) {
        mBottomSheetDialog.dismiss();
    } else {
        mBottomSheetDialog.show();
    }12345
    

    当这个Dialog Show出来的时候发现它显示了一半,嗯这个效果确实不错,这样就达到了我们最初说的支付宝密码弹窗和淘宝/天猫商品属性选择。我们滑动的时候如果下面有内容它就会EXPANDED,如果是一个普通的View(非RecyclerView、NestedScrollView)将不会继续往上滑动,下面的内容会继续跟着出来,但是同样可以向下滑动隐藏,也可以调用dismissclose关闭。

    BottomSheetDialog的神坑

    作为一个有情怀的程序员,这里把我踩过的坑和解决方案跟大家分享一下。

    我发现当这个Dilaog打开再关闭后,无法用Dialog.show()再次打开,为什么呢?

    我去阅读了一下BottomSheetDialog源代码,发现了如下代码:

     @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        super.setContentView(wrapInBottomSheet(0, view, params));
    }
    
    private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
        final CoordinatorLayout coordinator = View.inflate(getContext(),R.layout...., null);
        FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
        BottomSheetBehavior.from(bottomSheet).setBottomSheetCallback(mBottomSheetCallback);
        ...
        return coordinator;
    }
    
    private BottomSheetCallback mBottomSheetCallback = new BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet, int newState) {
            if (newState == BottomSheetBehavior.STATE_HIDDEN) {
                dismiss();
            }
        }
    
        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
        }
    };12345678910111213141516171819202122232425
    

    也就是说,系统的BottomSheetDialog是基于BottomSheetBehavior封装的,这里判断了当我们滑动隐藏了BottomSheetBehavior中的View后,内部是设置了BottomSheetBehavior的状态为STATE_HIDDEN,接着它替我们关闭了Dialog,所以我们再次调用dialog.show()的时候Dialog没法再此打开状态为HIDE的dialog了。

    这里就有个疑问了:
    Google为啥没有提供我们自己设置BottomSheetCallback的接口呢?

    没有关系,看了源码发现很简单,我们自己来实现,并且在监听到用户滑动关闭BottomSheetDialog后,我们把BottomSheetBehavior的状态设置为BottomSheetBehavior.STATE_COLLAPSED,也就是半个打开状态(BottomSheetBehavior.STATE_EXPANDED为全打开),根据源码我把设置的方法提供下:

    private void setBehaviorCallback() {
        View view = mBottomSheetDialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);
        final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(view);
        bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {
                if (newState == BottomSheetBehavior.STATE_HIDDEN) {
                    mBottomSheetDialog.dismiss();
                    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                }
            }
            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {
            }
        });
    }12345678910111213141516
    

    这样就解决了BottomSheetDialog关闭后不能再次打开的问题了。

    源码下载:http://download.csdn.net/detail/yanzhenjie1003/9578770

    相关文章

      网友评论

          本文标题:Meterial Design常见控件的使用(六):Navigo

          本文链接:https://www.haomeiwen.com/subject/jtxyaktx.html