UI之CoordinatorLayout、AppbarLayout、CollapsingToolbarLayout的使用

1.CoordinatorLayout 是什么

CoordinatorLayout 是一种功能更强大的FrameLayout

主要用于:
1.作为window的顶层布局 decor
2.作为父容器调度协调子布局,通过设置子View的 Behavior来调度子View的布局实现手势影响布局的形式产生动画效果
3.Behavior是CoordinatorLayout中的一个抽象类,用来协助CoordinatorLayout的Child Views之间的交互,Library中提供了
AppbarLayout.Behavior\AppBarLayout.ScrollingViewBehavior\FloatingActionButton.Behavior\SwipeDismissBehavior等实现子类,也可以自行定义实现子类。

2.CoordinatorLayout的使用

  • 引入依赖

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

  • CoordinatorLayout与 FloatingActionButton合用

    // 在布局中加入coordinatorlayout和FloatingActionButton的控件
    "1.0" encoding="utf-8"?>
    "@+id/coor_main"
        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.example.wendy.coordinatorlayout.MainActivity">
    
        "@+id/fab_main"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end|bottom"
            android:layout_marginBottom="10dp"
            android:layout_marginRight="10dp"
            android:elevation="4dp"
            android:src="@drawable/ic_action_accept"
            app:fabSize="normal"/>
      
    
    //代码 
    public class MainActivity extends AppCompatActivity {
        private FloatingActionButton fab;
        private CoordinatorLayout coor_layout;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            init();
        }
    
        private void init() {
            fab = findViewById(R.id.fab_main);
            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                //Snackbar 和toast的用法相似,snackbar是从屏幕底滑出,通过setAction()方法可能给snackbar添加一个按钮
                    Snackbar.make(view, "hello, I am smackbar!", Snackbar.LENGTH_LONG).setAction("UNDO", 
                    new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            //点击SnackBar上胡按钮后需要做的操作
                            fab.setImageResource(R.drawable.ic_action_cancel);
                        }
                    }).show();
                }
            });
        } }
    
    

    UI之CoordinatorLayout、AppbarLayout、CollapsingToolbarLayout的使用_第1张图片

    展示效果:弹出SnackBar 时,FloatingActionButton布局会向上移动
    FloatingActionButton是默认使用FloatingActionButton.Behavior


  • Coordinatorlayout与AppbarLayout合用

    AppBarLayout :
    继承自LinearLayout,子控件默认为竖直方向显示,可以用它实现Material Design 的Toolbar;它支持滑动手势;它的子控件可以通过在代码里调用setScrollFlags(int)或者在XML里app:layout_scrollFlags来设置它的滑动手势。当然实现这些的前提是它的根布局必须是 CoordinatorLayout。这里的滑动手势可以理解为:当某个可滚动View的滚动手势发生变化时,AppBarLayout内部的子View实现某种动作。AppBarLayout的子控件不仅仅可以设置为Toolbar,也可以包含其他的View,AppBarLayout必须作为Toolbar的父布局容器。
    app:layout_scrollFlags="[scroll | enterAlways |
    enterAlwaysCollapsed | exitUntilCollapsed]"

    scroll:可以滚动出屏幕的Flag,如没有设置则view会停留在屏幕顶部
    enterAlways:任意向下滚动都会使该view变为可见,启用快速”返回模式”。
    enterAlwaysCollapsed: 当你的视图已经设置minHeight属性又使用此标志时,你的视图只能以最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。
    exitUntilCollapsed: 滚动退出屏幕,最后折叠在顶端

//布局
"1.0" encoding="utf-8"?>

.support.design.widget.CoordinatorLayout
    android:id="@+id/coor_main"
    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.example.wendy.coordinatorlayout.MainActivity">

    .support.design.widget.AppBarLayout
        android:id="@+id/appbar_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        .support.v7.widget.Toolbar
            android:id="@+id/toolbar_main"
            android:layout_width="match_parent"
            android:layout_height="?android:attr/actionBarSize"
            android:background="@color/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:titleTextColor="#ffffff"/>

        <com.astuetz.PagerSlidingTabStrip
            android:id="@+id/title_main"
            android:layout_width="match_parent"
            android:layout_height="48dip"
            app:pstsDividerColor="#0e0e0e"
            app:pstsDividerPadding="15dp"
            app:pstsDividerWidth="1dp"
            app:pstsIndicatorColor="#ffffff"
            app:pstsIndicatorHeight="2dp"
            app:pstsShouldExpand="true"
            app:pstsTabBackground="@color/colorPrimaryLight"/>

    .support.design.widget.AppBarLayout>

  <!--viewpager中必须设置layout_behavior属性-->
    .support.v4.view.ViewPager
        android:id="@+id/viewPager_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

.support.design.widget.CoordinatorLayout>


布局要点:1.CoordinatorLayout必须是整个布局的父布局.
        2.CoordinatorLayout需要有两个直接的子view(AppbarLayout,和一个能滚动的view)
        3.需要滚动出屏幕的view必须在AppbarLayout布局中,且设置属性app:layout_scrollFlag="scroll|enteralways"
        4.CoordinatorLayout主布局中要有一个能够滚动的view,可以是NestedScrollView或者RecyclerView(或
          者是在viewPager里面直接内嵌一个RecyclerView,貌似不能通过fragment来嵌套recyclerView),能滚
          动的view必须设置属性 app:layout_behavior="@string/appbar_scrolling_view_behavior"

UI之CoordinatorLayout、AppbarLayout、CollapsingToolbarLayout的使用_第2张图片



  • CoordinatorLayout与AppBarLayout嵌套CollapsingToolbarLayout

CollapsingToolbarLayout:
————————————继承于FragmentLayout,主要作用是提供一个可以折叠的Toolbar,给它设置layout_scrollFlags,它可以控制包含在CollapsingToolbarLayout中的控件(如:ImageView、Toolbar)在响应layout_behavior事件时作出相应的scrollFlags滚动事件(移除屏幕或固定在屏幕顶端)。
特殊xml属性:
app: titleEnabled true则title会跟着放大折叠,默认为true
app:title 标题,当titleEnabled为true时会跟着缩放
app:toolbarId 用于折叠展开的toolbar的id
app:collapsedTitleGravity 折叠状态时title的位置
app:collapsedTitleTextAppearance折叠状态时标题文字的style
app:contentScrim 折叠状态时toolbar的背景
app:expandedTitleGravity 展开状态时标题的位置
app:expandedTitleMargin展开状态时的标题边距
app:expandedTitleTextAppearance 展开状态时标题文字的style
app:scrimAnimationDuration
app:scrimVisibleHeightTrigger当处于什么高度时出现设置的背景
app:statusbarScrim 折叠状态时设置状态栏的背景
//布局
"1.0" encoding="utf-8"?>
.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.example.wendy.coordinatorlayout.CollapsingActivity">

    .support.design.widget.AppBarLayout
        android:id="@+id/appbar_coll"
        android:layout_width="match_parent"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        android:layout_height="wrap_content">

        .support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_coll"
            android:layout_width="match_parent"
            app:title="@string/app_name"
            app:contentScrim="@color/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            android:layout_height="256dp">

            "@+id/image_coll"
                app:layout_collapseMode="parallax"
                android:layout_width="match_parent"
                android:src="@drawable/grass"
                android:scaleType="fitXY"
                android:layout_height="match_parent"/>

            .support.v7.widget.Toolbar
                app:titleTextColor="@color/colorWhite"
                android:id="@+id/toolbar_coll"
                android:layout_width="match_parent"
                app:layout_collapseMode="pin"
                android:layout_height="?android:attr/actionBarSize"/>


        .support.design.widget.CollapsingToolbarLayout>

    .support.design.widget.AppBarLayout>

    .support.v4.widget.NestedScrollView
        android:id="@+id/scroll_coll"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        "match_parent"
            android:orientation="vertical"
            android:layout_height="wrap_content">
            .support.v7.widget.CardView
                android:layout_margin="10dp"
                app:cardCornerRadius="4dp"
                android:elevation="4dp"
                android:layout_width="match_parent"
                android:layout_height="100dp">
                "vertical"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">
               "CardView"
                   android:layout_margin="10dp"
                   android:textStyle="bold"
                   android:textSize="18sp"
                   android:textColor="#000000"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"/>
                "@string/appbar_scrolling_view_behavior"
                    android:layout_marginLeft="10dp"
                    android:layout_marginRight="10dp"
                    android:textColor="#000000"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>
                
            .support.v7.widget.CardView>
        
    .support.v4.widget.NestedScrollView>
    .support.design.widget.FloatingActionButton
        app:fabSize="normal"
        android:src="@drawable/ic_done_black_24dp"
        android:layout_width="wrap_content"
        app:layout_anchor="@id/appbar_coll"
        app:layout_anchorGravity="end|bottom"
        android:layout_marginRight="10dp"
        android:layout_height="wrap_content"/>
.support.design.widget.CoordinatorLayout>
布局要点:
1.CoordinatorLayout必须是全局父容器,且必须有一个AppLayout和一个可滑动的view作为其直接子view,可滑动的子
   view必须设置app:layout_behavior="@string/appbar_scrolling_view_behavior";来告诉 
   coordinatorLayout什么时候触发滚动以及behavior类型
2.CollapsingToolbarLayout必须是AppLayout的直接子view且必须设置滑动标志
  app:layout_scrollFlags="scroll|exitUntilCollapsed"
3.CollapsingToolbarLayout的子view Toolbar和imageView必须设置折叠的方式app:collapseMode="[pin|parallax]";    
  parallax:视图将会随着滚动一起伸缩;pin:固定在顶部,不随着一起变化大小
4.CoordinatorLayout 还提供了一个 layout_anchor 的属性,连同 layout_anchorGravity 一起,可以用来放置
  与其他视图关联在一起的悬浮视图(如 FloatingActionButton)app:layout_anchor="@id/appbar"
  app:layout_anchorGravity="bottom|right|end"

UI之CoordinatorLayout、AppbarLayout、CollapsingToolbarLayout的使用_第3张图片


3.关于behavior

CoordinatorLayout通过behavior来控制子view。前面写到FloatingActionButton.Behavior,AppBarLayout.Behavior, AppBarLayout.ScrollingViewBehavior。 AppBarLayout中有两个Behavior,一个是拿来给它自己用的,另一个是拿来给同级别的可滑动view用的。

Q:为什么有些CoordinatorLayout的child Views 需要在xml中设置layout_behavior,有些不用设置如(FloatingActionBar)

Behavior有两个构造方法

 public static abstract class Behavior<V extends View> {

        /**
         * Default constructor for instantiating Behaviors.
         */
        public Behavior() {
        }

        /**
         * Default constructor for inflating Behaviors from layout. The Behavior will have
         * the opportunity to parse specially defined layout parameters. These parameters will
         * appear on the child view tag.
         *
         * @param context
         * @param attrs
         */
        public Behavior(Context context, AttributeSet attrs) {
        }

在构造方法的注释可以知道,第二种有参构造法会解析布局的特殊构造属性,调用parseBehavior方法通过反射来获取对应的Behavior

  static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
        if (TextUtils.isEmpty(name)) {
            return null;
        }

        final String fullName;
        if (name.startsWith(".")) {
            // 获得类的全称
            fullName = context.getPackageName() + name;
        } else if (name.indexOf('.') >= 0) {
            // Fully qualified package name.
            fullName = name;
        } else {
            // Assume stock behavior in this package (if we have one)
            fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
                    ? (WIDGET_PACKAGE_NAME + '.' + name)
                    : name;
        }

        try {
            Map> constructors = sConstructors.get();
            if (constructors == null) {
                constructors = new HashMap<>();
                sConstructors.set(constructors);
            }
            Constructor c = constructors.get(fullName);
            if (c == null) {
            //通过反射来得到Behavior
                final Class clazz = (Class) Class.forName(fullName, true,
                        context.getClassLoader());
                c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
                c.setAccessible(true);
                constructors.put(fullName, c);
            }
            return c.newInstance(context, attrs);
        } catch (Exception e) {
            throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
        }
    }

由此可以看出给Child Views设置Behavior有两种方法:

  1. 在xml中通过设置 app:layout_behavior=”.MyBehavior”
  2. 在Child Views控件定义源码中,使用@CoordinatorLayout.DefaultBehavior类注解,通过然后通过反射机制来得到Behaivor,系统的AppBarLayout、FloatingActionButton都采用了这种方式,所以无需在布局中重复设置。(这种方法需要自定义的Behavior有第二种构造方法)
    这里写图片描述

Q:如何自定义实现自己的Behavior

自定义的Behavior可以分为两类:dependent机制和nested机制来对应不同的场景


dependent机制


这种机制描述的是两个Child Views之间的绑定依赖关系,设置Behavior属性的Child View跟随依赖对象Dependency View的大小位置改变而发生变化,对应需要实现的方法常见有两个:

//决定是否产生依赖行为,返回true则child布局依赖于dependency
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
            return false;
        }

 //依赖的控件发生大小或者位置变化时产生回调       
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
            return false;
        }

Demo: 依赖Snackbar的布局

  public class MyCustomBehavior extends CoordinatorLayout.Behavior<View> {

    public MyCustomBehavior() {
    }

    public MyCustomBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof Snackbar.SnackbarLayout;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        float translationY = getViewbTranslationYFromSnackbar(parent, child);
        child.setRotation(90 * translationY / dependency.getHeight());
        child.setTranslationX(child.getWidth() * translationY / dependency.getHeight());
        child.setTranslationY(translationY);
        return false;
    }

    //计算child需要在Y方向上移动的距离
    private float getViewTranslationYFromSnackbar(CoordinatorLayout parent, View child) {
        float minOffset = 0f;
        //通过CoordinatorLayout获得child滚动所依赖的Views
        List list = parent.getDependencies(child);
        for (int i = 0; i < list.size(); i++) {
            View view = list.get(i);
            //coordinatorLayout.doViewOverlap(child,view)判断两个view的位置是否重叠
            if ((view instanceof Snackbar.SnackbarLayout) && parent.doViewsOverlap(child, view)) {
                minOffset = Math.min(minOffset, view.getTranslationY() - view.getHeight());
            }
        }
        return minOffset;
    }
}

//在xml中
xml version="1.0" encoding="utf-8"?>
"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.example.wendy.coordinatorlayout.ThirdActivity">
    

UI之CoordinatorLayout、AppbarLayout、CollapsingToolbarLayout的使用_第4张图片

Demo 2: 依赖Toolbar的AppBarLayout

 public class MySearchBehavior extends CoordinatorLayout.Behavior {


    public MySearchBehavior() {
    }


    public MySearchBehavior(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 dy = dependency.getTop();
        child.setTranslationY(-dy);

        return true;
    }
 }


//在xml布局中


 "1.0" encoding="utf-8"?>
.support.design.widget.CoordinatorLayout
    android:id="@+id/coor_main"
    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.example.wendy.coordinatorlayouttest.MainActivity">

    .support.design.widget.AppBarLayout
        android:id="@+id/abl_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">


        .support.v7.widget.Toolbar
            android:id="@+id/tb_main"
            android:layout_width="match_parent"
            android:layout_height="?android:attr/actionBarSize"
            app:layout_scrollFlags="scroll|enterAlways">
        .support.v7.widget.Toolbar>

    .support.design.widget.AppBarLayout>

    .support.v4.view.ViewPager
        android:id="@+id/vp_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

    .support.v4.view.ViewPager>

    "match_parent"
        android:layout_height="56dp"
        android:layout_gravity="bottom"
        android:background="@color/colorPrimaryDark"
        android:padding="10dp"
        android:weightSum="5"

        app:layout_behavior=".MySearchBehavior">//将自定义的Behavior设置给子布局

        "0dp"
            android:layout_height="match_parent"
            android:layout_weight="4"
            android:background="#ffffff"
            android:hint="Please input zhe Comment"
            android:textSize="16sp"/>

        


nested机制


Nested机制要求CoordinatorLayout包含了一个实现了NestedScrollingChild接口的滚动视图控件(如RecyclerView),设置Behavior属性的Child Views会随着这个控件的滚动而发生布局变化,需要重写以下方法

//返回值为true时才能让自定义的Behavior接受滑动事件
 public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes, @NestedScrollType int type) {

  }

//滚动视图控件滑动时调用,dyConsumed为Y轴上滑动的距离
 public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
                @NonNull View target, int dxConsumed, int dyConsumed,
                int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) {

 }

Demo: nested滚动视图的滚动

public class MyCustomBehavior extends CoordinatorLayout.Behavior {

    public MyCustomBehavior() {
    }


    public MyCustomBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,@NonNull 
    View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL;//当滑动为竖直方向时返回true,接受滑动事件
    }

    @Override
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View 
    child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, 
        dxUnconsumed, dyUnconsumed);

        if (dyConsumed > 0){
            child.animate().translationY(child.getHeight() * 4).setInterpolator(new AccelerateInterpolator(2)).start();//向下滑动时,将child向下移除屏幕

        }else{
            child.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start(); //向上滑动时,将child移动回原来的位置
        }
    }
}


;//在xml布局中

"1.0" encoding="utf-8"?>
.support.design.widget.CoordinatorLayout
    android:id="@+id/coor_main"
    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.example.wendy.coordinatorlayouttest.MainActivity">

    .support.design.widget.AppBarLayout
        android:id="@+id/abl_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">


        .support.v7.widget.Toolbar
            android:id="@+id/tb_main"
            android:layout_width="match_parent"
            android:layout_height="?android:attr/actionBarSize">
        .support.v7.widget.Toolbar>

    .support.design.widget.AppBarLayout>

    .support.v4.view.ViewPager
        android:id="@+id/vp_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

    .support.v4.view.ViewPager>


    .support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:layout_margin="20dp"
        android:src="@android:drawable/ic_media_play"
        app:backgroundTint="@color/colorAccent"
        app:fabSize="mini"
        app:layout_behavior=".MyCustomBehavior"/>  ;//将自定义的Behavior设置给CoordinatorLayout的直接child view

.support.design.widget.CoordinatorLayout>

UI之CoordinatorLayout、AppbarLayout、CollapsingToolbarLayout的使用_第5张图片

你可能感兴趣的:(UI之CoordinatorLayout、AppbarLayout、CollapsingToolbarLayout的使用)