Android 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的控件
    
    
    
        
      
    
    //代码 
    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();
                }
            });
        } }
    

    这里写图片描述

    展示效果:弹出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: 滚动退出屏幕,最后折叠在顶端

//布局




    

        

        

    

  <!--viewpager中必须设置layout_behavior属性-->
    




布局要点: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"

 

 这里写图片描述


 

  •  
  • 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.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"

 这里写图片描述


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 {

        /**
         * 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 {

    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中


    

这里写图片描述

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布局中


 


    


        
        

    

    

    

    //将自定义的Behavior设置给子布局

        

        

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布局中




    


        
        

    

    

    


      ;//将自定义的Behavior设置给CoordinatorLayout的直接child view

这里写图片描述

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