美文网首页
MaterialDesign使用总结

MaterialDesign使用总结

作者: JCJIE | 来源:发表于2017-06-04 09:59 被阅读0次

    前言

    2015年IO大会上,Google带来了Android M,同时还有Android支持库的新一轮更新,其中更是增加一个全新的支持库Android Design Support Library,包含了数个重要的Material Design组件,如CoordinatorLayout、TabLayout、NavigationLayout等,用于将Material Design适配到Android 2.1(API 7)。
    首先,我们看看通过CoordinatorLayout来实现仿知乎的动画效果,接着我们会分布解析实现的原理:

    这里写图片描述

    CoordinatorLayout

    在Material Design组件钟,CoordinatorLayout十分重要,任何酷炫效果的基础均源于它。首先看看官方对它的介绍。

    CoordinatorLayout is a super-powered FrameLayout
    CoordinatorLayout is intended for two primary use cases:

    1. As a top-level application decor or chrome layout
    2. As a container for a specific interaction with one or more child views
      By specifying Behaviors for child views of a CoordinatorLayout you can provide many different interactions within a single parent and those views can also interact with one another. View classes can specify a default behavior when used as a child of a CoordinatorLayout using the DefaultBehavior annotation.
      Behaviors may be used to implement a variety of interactions and additional layout modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons that stick to other elements as they move and animate.
      Children of a CoordinatorLayout may have an anchor . This view id must correspond to an arbitrary descendant of the CoordinatorLayout, but it may not be the anchored child itself or a descendant of the anchored child. This can be used to place floating views relative to other arbitrary content panes.
      Children can specify insetEdge to describe how the view insets the CoordinatorLayout. Any child views which are set to dodge the same inset edges by dodgeInsetEdges will be moved appropriately so that the views do not overlap.

    从官方文档中,我们可以总结出以下几点

    1. CoordinatorLayout 是一个功能强大的FrameLayout
    2. CoordinatorLayout 的主要作用是协调各个子view之间的相互作用,而这些相互作用的实现主要依赖于它的一个内部类Behavior,下文中将会仔细分析这个类。
      在日常使用中CoordinatorLayout 主要有三种常见的用法
      1. 与FloatingActionButton的结合
      2. 与TabLayout的结合
      3. 与CollapsingLayout的结合

    与FloatingActionButton的结合

    与FloatingActionButton结合使用较为简单,只需要把两个控件在同一个页面布局在一起,不需要做任何特殊处理,这里就不做介绍了。

    与TabLayout的结合

    CoordinatorLayout 能够实现这种效果主要归功于AppbarLayout

    • AppbarLayout
      老规矩,先看源码

    AppBarLayout is a vertical LinearLayout which implements many of the features of material designs app bar concept, namely scrolling gestures.
    Children should provide their desired scrolling behavior through setScrollFlags(int) and the associated layout xml attribute:app:layout_scrollFlags.
    This view depends heavily on being used as a direct child within a CoordinatorLayout
    If you use AppBarLayout within a different ViewGroup, most of it's functionality will not work.
    AppBarLayout also requires a separate scrolling sibling in order to know when to scroll. The binding is done through the AppBarLayout.ScrollingViewBehavior
    behavior class, meaning that you should set your scrolling view's behavior to be an instance of AppBarLayout.ScrollingViewBehavior. A string resource containing the full class name is available.

    通过官方文档的介绍,我们能知道一下几点
    1、AppBarLayout 是一个垂直方向的LinearLayout,用它来实现一种滚动效果
    2、AppBarLayout 的子view要想实现滚动效果就必须要设置app:layout_scrollFlags这个属性
    3、AppBarLayout下面需要设置一个有滚动效果的空间才能实现滚动的效果,如NestedScrollView,然后在这个控件中设置
    app:layout_behavior="@string/appbar_scrolling_view_behavior
    4、需要注意的是AppBarLayout 只能作为CoordinatorLayout的直接子view才能实现效果

    <?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"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.jcj.royalni.jcjzhihudaily.MainActivity">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/index_app_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="@color/colorPrimary"
                app:layout_scrollFlags="scroll|enterAlways">
                <ImageView
                    android:id="@+id/live"
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:layout_marginRight="5dp"
                    android:src="@drawable/live_button" />
    
                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_margin="10dp"
                    android:layout_toLeftOf="@id/live"
                    android:background="@color/searchmenu">
                    <ImageView
                        android:id="@+id/search"
                        android:layout_width="24dp"
                        android:layout_height="24dp"
                        android:layout_centerVertical="true"
                        android:layout_marginLeft="10dp"
                        android:src="@drawable/ic_search" />
    
                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_centerVertical="true"
                        android:layout_marginLeft="10dp"
                        android:layout_toRightOf="@id/search"
                        android:text="搜索话题、问题或人"
                        android:textSize="16sp" />
                </RelativeLayout>
            </RelativeLayout>
        </android.support.design.widget.AppBarLayout>
    
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/floating_action_bar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right|bottom"
            android:layout_marginBottom="76dp"
            android:layout_marginRight="25dp"
            app:layout_behavior="com.jcj.royalni.jcjzhihudaily.MyFloatingActionButtonBehavior"
            />
        <android.support.v4.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:tag="nest"
            android:layout_above="@id/bottom"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="haha "></android.support.v7.widget.RecyclerView>
        </android.support.v4.widget.NestedScrollView>
    
        <LinearLayout
            android:id="@+id/bottom"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:layout_gravity="bottom"
            android:background="@color/white"
            android:orientation="horizontal"
            app:layout_behavior="com.jcj.royalni.jcjzhihudaily.MyBehavior">
    
            <RadioGroup
                android:id="@+id/tabs_rg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="horizontal"
                android:paddingLeft="5dp"
                android:paddingRight="5dp">
    
                <RadioButton
                    android:id="@+id/home_tab"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="#00000000"
                    android:button="@null"
                    android:checked="true"
                    android:drawableTop="@drawable/home_sel"
                    android:gravity="center|bottom"
                    android:paddingTop="5dp" />
    
                <RadioButton
                    android:id="@+id/explore_tab"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="#00000000"
                    android:button="@null"
                    android:drawableTop="@drawable/explore_sel"
                    android:gravity="center|bottom"
                    android:paddingTop="5dp" />
    
                <RadioButton
                    android:id="@+id/notify_tab"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="#00000000"
                    android:button="@null"
                    android:drawableTop="@drawable/notify_sel"
                    android:gravity="center|bottom"
                    android:paddingTop="5dp" />
    
                <RadioButton
                    android:id="@+id/user_tab"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="#00000000"
                    android:button="@null"
                    android:drawableTop="@drawable/user_sel"
                    android:gravity="center|bottom"
                    android:paddingTop="5dp" />
            </RadioGroup>
        </LinearLayout>
    </android.support.design.widget.CoordinatorLayout>
    

    在知乎这个例子中,首先在AppbarLayout的子view RelativeLayout中设置了app:layout_scrollFlag这个属性,接着在NestedScrollView中设置了behavior属性,且NestedScrollView中是用RecyclerView这个可以滚动的控件来实现的,所以才能实现如示例中所示的效果。
    app:layout_scrollFlag中主要的四个设置条件解释如下

    • scroll: 所有想滚动出屏幕的view都需要设置这个flag, 没有设置这个flag的view将被固定在屏幕顶部。例如,TabLayout 没有设置这个值,将会停留在屏幕顶部。
    • enterAlways:设置这个flag时,向下的滚动都会导致该view变为可见,启用快速“返回模式”。
    • enterAlwaysCollapsed: 当你的视图已经设置minHeight属性又使用此标志时,你的视图只能已最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。
    • exitUntilCollapsed: 滚动退出屏幕,最后折叠在顶端

    为了使得知乎的头部有滑动效果,必须做到如下三点:

    1. CoordinatorLayout作为布局的父布局容器。
    2. 给需要滑动的组件(这里是RelativeLayout)设置app:layout_scrollFlags=”scroll|enterAlways” 属性。
    3. 给滑动的组件(这里是ViewPager)设置app:layout_behavior属性

    与CollapsingToolbarLayout结合

    CollapsingToolbarLayout is a wrapper for Toolbar which implements a collapsing app bar. It is designed to be used as a direct child of a AppBarLayout

    • CollapsingToolbarLayou是一个Toolbar的包装者,它只能作为AppBarLayout的直接子类。它的主要功能如下
    1. Collapsingtitle:ToolBar的标题,当CollapsingToolbarLayout全屏没有折叠时,title显示的是大字体,在折叠的过程中,title不断变小到一定大小的效果。你可以调用setTitle(CharSequence)方法设置title。
    2. Contentscrim:ToolBar被折叠到顶部固定时候的背景,你可以调用setContentScrim(Drawable)方法改变背景或者在属性中使用app:contentScrim=?attr/colorPrimary”来改变背景。
    3. Status bar scrim:状态栏的背景,调用方法setStatusBarScrim(Drawable)。
    4. Parallax scrolling children:CollapsingToolbarLayout滑动时,子视图的视觉差,可以通过属性app:layout_collapseParallaxMultiplier=0.6”改变。值的范围[0.0,1.0],值越大视差越大。
    5. CollapseMode:子视图的折叠模式,在子视图设置,有两种“pin”:固定模式,在折叠的时候最后固定在顶端;“parallax”:视差模式,在折叠的时候会有个视差折叠的效果。我们可以在布局中使用属性app:layout_collapseMode=”parallax”来改变。

    这里暂时不用知乎的例子进行介绍,使用下面例子进行分析

    这里写图片描述
    要实现这种动画效果,使用如下布局
    <?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"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:context="com.jcj.royalni.materialdesigndemo.MaterialCollapseActivity">
        <android.support.design.widget.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="256dp"
            android:fitsSystemWindows="true">
            <android.support.design.widget.CollapsingToolbarLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:contentScrim="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">
                <ImageView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:src="@drawable/ic_viewpager_02"
                    android:scaleType="centerCrop"
                    app:layout_collapseMode="parallax"
                    app:layout_collapseParallaxMultiplier="0.6"
                    />
                <android.support.v7.widget.Toolbar
                    android:id="@+id/collapsing_toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                    android:title="纪昌杰"
                    app:layout_collapseMode="pin">
                </android.support.v7.widget.Toolbar>
            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>
            <android.support.v4.widget.NestedScrollView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior"
                >
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:orientation="vertical"
                    android:paddingTop="24dp">
            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:cardCornerRadius="4dp"
                android:elevation="@dimen/cardview_compat_inset_shadow"
                android:layout_margin="10dp"
                >
                <LinearLayout
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="20dp">
                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="CardView"
         android:textAppearance="@style/TextAppearance.AppCompat.Title" />
                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"  android:text="fadffashklaghklafkldjafkljdkfjvklakakljklsadjfkljdfklajgkajglkjfdlkajfkljakl" />
                </LinearLayout>
            </android.support.v7.widget.CardView>
          </LinearLayout>
        </android.support.v4.widget.NestedScrollView>
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/float_action_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_anchor="@id/appbar"
            android:layout_margin="10dp"
            app:layout_anchorGravity="center|bottom|right"
            android:src="@drawable/ic_action_search"
            android:clickable="true"/>
    </android.support.design.widget.CoordinatorLayout>
    

    CoordinatorLayout 还提供了一个 layout_anchor 的属性,连同 layout_anchorGravity 一起,可以用来放置与其他视图关联在一起的悬浮视图(如FloatingActionButton)。本例中使用FloatingActionButton。

    • 通过下面的参数设置了FloatingActionButton的位置,两个属性共同作用使得FAB 浮动按钮也能折叠消失,展现。
      app:layout_anchor="@id/appbar"
      app:layout_anchorGravity="bottom|right|end"

    使用CollapsingToolbarLayout实现折叠效果,需要注意3点

    1. AppBarLayout的高度固定
    2. CollapsingToolbarLayout中设置app:layout_scrollFlags属性
    3. CollapsingToolbarLayout的子视图设置layout_collapseMode属性
    4. 关联悬浮视图设置app:layout_anchor,app:layout_anchorGravity属性

    behavior

    从CoordinatorLayout的源码介绍能实现效果,最重要的就是behavior这个类。看源码

    Interaction behavior plugin for child views of {@link CoordinatorLayout}.
    A Behavior implements one or more interactions that a user can take on a child view.
    These interactions may include drags, swipes, flings, or any other gestures.

    从中可以得出以下几点

    1. 它是CoordinatorLayout子类中实现交互的一个插件
    2. 这些交互能够应用到一个或多个子类中,这些交互包括滑动、拖拽以及其它各种手势的效果


      这里写图片描述

      这张图片介绍了behavior这个抽象类的继承结构,在这里我们可以看到系统实现了几类behavior,在FloatingActionButton中之所以能实现动画效果,就是因为它默认设置了FloatingActionButton.Behavior这个类。同理,我们也可以实现自己的behavior。首先我们研究一下系统默认实现的一个behavior进行分析。在代码中我们看到在nestedScorllView中设置了behavior属性如下

    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    

    我们找到这个类ScrollingViewBehavior,分析代码可知实现这种效果最重要依靠两个方法

            /**
             * Determine whether the supplied child view has another specific sibling view as a
             * layout dependency.
             *
             * <p>This method will be called at least once in response to a layout request. If it
             * returns true for a given child and dependency view pair, the parent CoordinatorLayout
             * will:</p>
             * <ol>
             *     <li>Always lay out this child after the dependent child is laid out, regardless
             *     of child order.</li>
             *     <li>Call {@link #onDependentViewChanged} when the dependency view's layout or
             *     position changes.</li>
             * </ol>
             *
             * @param parent the parent view of the given child
             * @param child the child view to test
             * @param dependency the proposed dependency of child
             * @return true if child's layout depends on the proposed dependency's layout,false otherwise
             */
            @Override
            public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
                // We depend on any AppBarLayouts
                return dependency instanceof AppBarLayout;
            }
    ----------
    /**
             * Respond to a change in a child's dependent view
             *
             * <p>This method is called whenever a dependent view changes in size or position outside
             * of the standard layout flow. A Behavior may use this method to appropriately update
             * the child view in response.</p>
             *
             * <p>A view's dependency is determined by
             * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or
             * if {@code child} has set another view as it's anchor.</p>
             *
             * <p>Note that if a Behavior changes the layout of a child via this method, it should
             * also be able to reconstruct the correct position in
             * {@link #onLayoutChild(CoordinatorLayout, android.view.View, int) onLayoutChild}.
             * <code>onDependentViewChanged</code> will not be called during normal layout since
             * the layout of each child view will always happen in dependency order.</p>
             *
             * <p>If the Behavior changes the child view's size or position, it should return true.
             * The default implementation returns false.</p>
             *
             * @param parent the parent view of the given child
             * @param child the child view to manipulate
             * @param dependency the dependent view that changed
             * @return true if the Behavior changed the child view's size or position, false otherwise
             */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
         offsetChildAsNeeded(parent, child, dependency);
         return false;
    }
    

    通过这两个方法就可以实现知乎demo中那种滚动的效果。

    1. 第一个方法就是确定提供子视图有另一个特定的兄弟视图布局的依赖。返回true就是子布局依赖于相应的dependency布局,返回false就是不依赖。
    2. 第二个方法就是当dependency的大小或者位置发生变动时调用这个方法来动态的更新子view,返回true就是behavior类改变了子view的大小或位置,反之亦然。

    在知乎demo中最底下的radiogroup以及FloatingActionButton都是使用了自定义的behavior。下面就是实现radiogroup的behavior

    public class MyBehavior extends Behavior{
        public MyBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
            return dependency instanceof AppBarLayout;
        }
    
        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
            float translationY = Math.abs(dependency.getTop());
            child.setTranslationY(translationY);
            return true;
        }
    }
    
    

    如代码所示,这里的dependency就是AppBarLayout,child就是RadioGroup的父类LinearLayout,所以这里能实现RadioGroup实现如知乎demo中的效果。

    相关文章

      网友评论

          本文标题:MaterialDesign使用总结

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