一、简介

CoordinatorLayout翻译为协调者布局,是在 Google IO/15 大会发布的,是用来协调其子View们之间动作的一个容器,遵循Material Design风格,包含在 com.android.support:design中。CoordinatorLayout是一个超级强大的FrameLayout,结合AppBarLayoutCollapsingToolbarLayout等可产生各种炫酷的效果。

二、使用

在项目的build.gradle引入material design
老版本(项目未迁移至AndroidX):

implementation 'com.android.support:design:28.0.0'

新版本(项目已迁移至AndroidX),本文使用:

 implementation 'com.google.android.material:material:1.1.0'

2.1、CoordinatorLayout结合AppBarLayout 使用

效果图:

布局文件使用:

<?xml version="1.0" encoding="utf-8"?><androidx.coordinatorlayout.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">    <com.google.android.material.appbar.AppBarLayout        android:layout_width="match_parent"        android:layout_height="wrap_content">        <TextView            android:layout_width="match_parent"            android:layout_height="100dp"            android:background="#222222"            android:gravity="center"            android:text="该区域可折叠"            android:textColor="@android:color/white"            android:textSize="30sp"            app:layout_scrollFlags="scroll" />        <TextView            android:layout_width="match_parent"            android:layout_height="50dp"            android:background="#DD012D"            android:gravity="center"            android:text="该区域为上滑至头部固定区域"            android:textColor="@android:color/white"            android:textSize="20sp" />    com.google.android.material.appbar.AppBarLayout>    <androidx.core.widget.NestedScrollView        android:id="@+id/rv_demo1_content"        android:layout_width="match_parent"        android:layout_height="wrap_content"        app:layout_behavior="@string/appbar_scrolling_view_behavior">        <TextView            android:layout_width="match_parent"            android:text="这是一个滚动布局"            android:textSize="200sp"            android:background="#00ff00"            android:layout_height="wrap_content"/>    androidx.core.widget.NestedScrollView>androidx.coordinatorlayout.widget.CoordinatorLayout>

说明:
CoordinatorLayout须要作为顶层父View,子View想要与CoordinatorLayout实现"联动性"效果的首要条件是这个View必须实现了NestedScrollingChild接口(例如:NestedScrollViewRecyclerView等控件)。CoordinatorLayout子控件如果需要联动,需要设置app:layout_behavior属性,上面AppBarLayout没有设置是因为本身有个默认的app:layout_behavior查看源码如下:

@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)public class AppBarLayout extends LinearLayout {

AppBarLayout中ScrollFlags值

XML使用app:layout_scrollFlags设置,代码中获取该控件AppBarLayout.LayoutParams再使用setScrollFlags(int)设置
XML设置方法:

app:layout_scrollFlags="scroll|enterAlways" 

代码中设置方法:

TextView text= ... //确保该View是被AppBarLayout包裹的AppBarLayout.LayoutParams params =     (AppBarLayout.LayoutParams) text.getLayoutParams();params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL    | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);

几种设置效果如下:

  • scroll (SCROLL_FLAG_SCROLL)
    视图将与滚动事件直接相关。需要设置此标志才能使任何其他标志生效。如果此视图之前的任何同级视图没有此标志,则此值无效。
app:layout_scrollFlags="scroll"

  • enterAlways (SCROLL_FLAG_ENTER_ALWAYS)
    当进入(在屏幕上滚动)时,视图将在任何向下滚动事件上滚动,无论滚动视图是否也在滚动。这通常被称为“快速推出”模式。
app:layout_scrollFlags="scroll|enterAlways"

  • enterAlwaysCollapsed (SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED)
    “enterAlways”的另一个标志,它将返回的视图修改为仅在最初滚动回其折叠高度。一旦滚动视图到达其滚动范围的末尾,此视图的其余部分将滚动到视图中。折叠高度由视图的最小高度定义。
android:minHeight="30dip"app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed" 

  • exitUntilCollapsed (SCROLL_FLAG_EXIT_UNTIL_COLLAPSED)
    退出时(从屏幕上滚动),视图将滚动到“折叠”为止。折叠高度由视图的最小高度定义。
android:minHeight="30dip"app:layout_scrollFlags="scroll|exitUntilCollapsed" />

  • snap (SCROLL_FLAG_SNAP)
    在滚动结束时,如果视图仅部分可见,则它将被捕捉并滚动到最近的边。例如,如果视图只显示其底部的25%,则它将完全从屏幕上滚下。相反,如果它的底部75%是可见的,那么它将完全滚动到视图中。
app:layout_scrollFlags="scroll|snap"

结合 CollapsingToolbarLayout 使用

效果图:

布局文件:

<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:id="@+id/main_content"    android:layout_width="match_parent"    android:fitsSystemWindows="true"    android:layout_height="match_parent"    >    <com.google.android.material.appbar.AppBarLayout        android:id="@+id/appbar"        android:layout_width="match_parent"        android:fitsSystemWindows="true"        android:layout_height="wrap_content"        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">        <com.google.android.material.appbar.CollapsingToolbarLayout            android:id="@+id/toolbar_layout"            android:layout_width="match_parent"            android:layout_height="wrap_content"            app:contentScrim="#ff0000"            app:collapsedTitleGravity="center"            app:expandedTitleGravity="left|bottom"            app:layout_scrollFlags="scroll|exitUntilCollapsed"            app:title="杨幂"            app:toolbarId="@+id/toolbar">            <ImageView                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:scaleType="centerCrop"                app:layout_collapseMode="parallax"                app:srcCompat="@mipmap/ym" />            <androidx.appcompat.widget.Toolbar                android:id="@+id/toolbar"                android:layout_width="match_parent"                android:layout_height="50dp"                app:layout_collapseMode="pin"                />           com.google.android.material.appbar.CollapsingToolbarLayout>    com.google.android.material.appbar.AppBarLayout>    <androidx.core.widget.NestedScrollView        android:id="@+id/rv_demo1_content"        android:layout_width="match_parent"        android:layout_height="wrap_content"        app:layout_behavior="@string/appbar_scrolling_view_behavior">            <TextView                android:layout_width="match_parent"                android:text="这是一个滚动布局"                android:textSize="200sp"                android:background="#00ff00"                android:layout_height="wrap_content"/>    androidx.core.widget.NestedScrollView>androidx.coordinatorlayout.widget.CoordinatorLayout>

CollapsingToolbarLayout部分属性

  • app:contentScrim CollapsingToolbarLayout完全折叠后的背景颜色
  • app:titleEnabled 是否显示标题 app:title 标题 app:toolbarId toolbar 对应的view id
  • app:statusBarScrim 折叠后状态栏的背景
  • app:scrimVisibleHeightTrigger 设置收起多少高度时,显示ContentScrim的内容
  • app:scrimAnimationDuration 展开状态和折叠状态之间,内容转换的动画时间
  • app:expandedTitleTextAppearance 布局张开的时候title的样式
  • app:expandedTitleMarginTop 布局张开的时候title的margin top
  • app:expandedTitleMarginStart 布局张开的时候title的margin start
  • app:expandedTitleMarginEnd 布局张开的时候title的margin end
  • app:expandedTitleMarginBottom 布局张开的时候title的margin bottom
  • app:expandedTitleMargin 布局张开的时候title的margin
  • app:expandedTitleGravity 布局张开的时候title的位置
  • app:collapsedTitleTextAppearance 布局折叠的时候title的样式
  • app:collapsedTitleGravity 布局折叠的时候title的gravity

2.2、CoordinatorLayout 中的 Behavior

Behavior行为控制器:实现了用户可以在子视图上进行的一个或多个交互。这些交互可能包括拖动,滑动,甩动或任何其他手势。
Behavior中常用的重写的方法:

 /**  * 确定使用Behavior的View要依赖的View的类型  * 只要是CoordinatorLayout内的View的状态发送了变化,该方法就会执行  * @param parent     顶层父控件CoordinatorLayout  * @param child      我们设置这个Behavior的View  * @param dependency 值会不断的变化,他会轮询CoordinatorLayout下所有所属的子View  * @return 这里判断dependency所属的View是哪一个, 返回true,onDependentViewChanged才执行,否则不执行  */@Overridepublic boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency)/**  * 当被依赖的View状态改变时回调  * @param parent     顶层父控件CoordinatorLayout  * @param child      我们设置这个Behavior的View  * @param dependency 值会不断的变化,他会轮询CoordinatorLayout下所有所属的子View  * @return 当我们改变了child的大小或者位置的时候我们需要返回true  */@Overridepublic boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency)/**  * 当被依赖的View移除时回调  * @param parent  顶层父控件CoordinatorLayout  * @param child 我们设置这个Behavior的View  * @param dependency 值会不断的变化,他会轮询CoordinatorLayout下所有所属的子View  */@Overridepublic void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency)     

简单案例(向上滑动时底部控件渐渐隐藏,向下滑动时底部控件渐渐显示):

  1. 效果

  2. 自定义一个Behavior

public class Demo1Behavior extends CoordinatorLayout.Behavior<View> {    public Demo1Behavior() {        super();    }    public Demo1Behavior(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {        //这里判断dependency所属的View是哪一个,返回true,onDependentViewChanged才执行,否则不执行        return dependency instanceof AppBarLayout;    }    @Override    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {        /*         *这里获取dependency的top值,也就是AppBarLayout的top,因为AppBarLayout         *在是向上滚出界面的,我们的因为是和AppBarLayout相反,所以取绝对值.         */        float translationY = Math.abs(dependency.getTop());        child.setTranslationY(translationY);        return true;    }}
  1. 布局文件
<?xml version="1.0" encoding="utf-8"?><androidx.coordinatorlayout.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"> <com.google.android.material.appbar.AppBarLayout        android:layout_width="match_parent"        android:layout_height="wrap_content">        <TextView            android:layout_width="match_parent"            android:layout_height="100dp"            android:background="#222222"            android:gravity="center"            android:text="该区域可折叠"            android:textColor="@android:color/white"            android:textSize="30sp"            app:layout_scrollFlags="scroll" />        <TextView            android:layout_width="match_parent"            android:layout_height="50dp"            android:background="#DD012D"            android:gravity="center"            android:text="该区域为上滑至头部固定区域"            android:textColor="@android:color/white"            android:textSize="20sp" />    com.google.android.material.appbar.AppBarLayout>    <androidx.core.widget.NestedScrollView        android:id="@+id/rv_demo1_content"        android:layout_width="match_parent"        android:layout_height="wrap_content"        app:layout_behavior="@string/appbar_scrolling_view_behavior">        <TextView            android:layout_width="match_parent"            android:text="这是一个滚动布局"            android:textSize="200sp"            android:background="#00ff00"            android:layout_height="wrap_content"/>    androidx.core.widget.NestedScrollView>//行为控制器引用 app:layout_behavior=".demo1.Demo1Behavior"    <TextView        android:layout_gravity="bottom"        app:layout_behavior=".demo1.Demo1Behavior"        android:layout_width="match_parent"        android:background="#ff00ff"        android:layout_height="50dip"/>androidx.coordinatorlayout.widget.CoordinatorLayout>

三、案例

防招商银行8.1全部菜单布局,上滑顶部区域隐藏,导航条悬浮,点击导航条可快速定位。

1、效果图

2、布局文件

<?xml version="1.0" encoding="utf-8"?><androidx.coordinatorlayout.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=".demo5.Demo5Activity">    <com.google.android.material.appbar.AppBarLayout        android:id="@+id/abl_demo5_content"        android:layout_width="match_parent"        android:layout_height="wrap_content">        <TextView            android:layout_width="match_parent"            android:layout_height="100dp"            android:background="#222222"            android:gravity="center"            android:text="该区域可折叠"            android:textColor="@android:color/white"            android:textSize="30sp"            app:layout_scrollFlags="scroll|enterAlwaysCollapsed" />        <com.google.android.material.tabs.TabLayout            android:id="@+id/tb_demo5_content"            android:layout_width="match_parent"            android:layout_height="50dip"            android:background="#ffffff">        com.google.android.material.tabs.TabLayout>    com.google.android.material.appbar.AppBarLayout>    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/rv_demo5_content"        android:layout_width="match_parent"        android:layout_height="match_parent"        app:layout_behavior="@string/appbar_scrolling_view_behavior" />androidx.coordinatorlayout.widget.CoordinatorLayout>

3、页面代码及数据适配器代码

3.1、页面代码

public class Demo5Activity extends AppCompatActivity {    private RecyclerView rv_demo5_content;    private List<Demo5Bean> data;    private TabLayout tb_demo5_content;    private AppBarLayout abl_demo5_content;    private GridLayoutManager gridLayoutManager;    private RecyclerView.SmoothScroller smoothScroller;    private List<Integer> titlePosition;    //是否正在滚动    private boolean isScroll;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_demo5);        tb_demo5_content = findViewById(R.id.tb_demo5_content);        rv_demo5_content = findViewById(R.id.rv_demo5_content);        abl_demo5_content = findViewById(R.id.abl_demo5_content);        initData();        initTabLayout();        initRecyclerView();    }    private void initData() {        data = new ArrayList<Demo5Bean>();        titlePosition = new ArrayList<Integer>();        Demo5Bean bean = null;        for (int i = 0; i < 5; i++) {            bean = new Demo5Bean("标题" + i, Demo5Adapter.VIEW_TYPE_TITLE);            data.add(bean);            titlePosition.add(i + (i * 10));            for (int i1 = 0; i1 < 10; i1++) {                bean = new Demo5Bean(i + "_内容" + i1, Demo5Adapter.VIEW_TYPE_MENU);                data.add(bean);            }        }    }    private void initTabLayout() {        for (Demo5Bean datum : data) {            if (datum.getItemType() == Demo5Adapter.VIEW_TYPE_TITLE) {                  tb_demo5_content.addTab(tb_demo5_content.newTab().setText(datum.getName()));            }        }        tb_demo5_content.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {            @Override            public void onTabSelected(TabLayout.Tab tab) {                if(!isScroll) {                    //收缩折叠区                    abl_demo5_content.setExpanded(false);                    int tabPosition = tab.getPosition();                    int titlePosition = getTitlePosition(tabPosition);                    smoothScroller.setTargetPosition(titlePosition);                    gridLayoutManager.startSmoothScroll(smoothScroller);                }            }            @Override            public void onTabUnselected(TabLayout.Tab tab) {            }            @Override            public void onTabReselected(TabLayout.Tab tab) {            }        });    }    private void initRecyclerView() {        gridLayoutManager = new GridLayoutManager(this, 4);        gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {            @Override            public int getSpanSize(int position) {                if (position < data.size()) {                    Demo5Bean bean = data.get(position);                    if (bean.getItemType() == Demo5Adapter.VIEW_TYPE_TITLE) {                        //是标题则占4个item                        return 4;                    } else if (bean.getItemType() == Demo5Adapter.VIEW_TYPE_MENU) {                        //是menu则正常占用一个item                        return 1;                    } else {                        return 0;                    }                } else {                    //FooterView 占4个item                    return 4;                }            }        });        //计算最后填充FooterView填充高度,全屏高度-状态栏高度-tablayout的高度(这里固定高度50dp)-title标题高度(40)-最后分组高度(3排menu每个70),用于recyclerView的最后一个item FooterView填充高度        int screenH = getScreenHeight();        int statusBarH = getStatusBarHeight(this);        int tabH = dip2px(this,50);        int titleH = dip2px(this,40);        int lastMenusH= dip2px(this,70)*3;        int lastH = screenH - statusBarH - tabH -titleH-lastMenusH;        if(lastH<=0){            lastH=0;        }        Demo5Adapter mAdapter = new Demo5Adapter(data, this,lastH);        rv_demo5_content.setLayoutManager(gridLayoutManager);        rv_demo5_content.setAdapter(mAdapter);        //RecyclerView平滑Scroller        smoothScroller = new LinearSmoothScroller(this) {                    @Override                    protected int getVerticalSnapPreference() {                        return LinearSmoothScroller.SNAP_TO_START;                    }                    @Nullable                    @Override                    public PointF computeScrollVectorForPosition(int targetPosition) {                        return gridLayoutManager.computeScrollVectorForPosition(targetPosition);                    }                };        rv_demo5_content.addOnScrollListener(new RecyclerView.OnScrollListener() {            @Override            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {                super.onScrollStateChanged(recyclerView, newState);                if(newState==RecyclerView.SCROLL_STATE_IDLE){                    isScroll=false;                }else{                    isScroll=true;                }            }            @Override            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {                super.onScrolled(recyclerView, dx, dy);                TabLayout.Tab tabAt = tb_demo5_content.getTabAt(getTabPosition(gridLayoutManager.findFirstVisibleItemPosition()));                if (tabAt != null && !tabAt.isSelected()) {                    tabAt.select();                }            }        });    }    private int getTitlePosition(int tabPosition) {        //根据tabPosition找出TitlePosition        return titlePosition.get(tabPosition);    }    private int getTabPosition(int menuPosition) {       return titlePosition.indexOf(menuPosition);    }    private int getScreenHeight() {        return getResources().getDisplayMetrics().heightPixels;    }    public int getStatusBarHeight(Context context) {        int result = 0;        int resourceId = context.getResources()                .getIdentifier("status_bar_height", "dimen", "android");        if (resourceId > 0) {            result = context.getResources().getDimensionPixelSize(resourceId);        }        return result;    }    public static int dip2px(Context context, float dpValue) {        final float scale = context.getResources().getDisplayMetrics().density;        return (int) (dpValue * scale + 0.5f);    }}

3.2、数据适配器

public class Demo5Adapter extends RecyclerView.Adapter {    public static final int VIEW_TYPE_TITLE = 0;    public static final int VIEW_TYPE_MENU = 1;    public static final int VIEW_TYPE_FOOTER = 2;    private final LayoutInflater inflater;    private List<Demo5Bean> data;    private Context context;    private int lastH;    public Demo5Adapter(List<Demo5Bean> data, Context context,int lastH) {        this.data = data;        this.context = context;        this.lastH = lastH;        inflater = LayoutInflater.from(context);    }    @Override    public int getItemViewType(int position) {        if (position == data.size()) {            return VIEW_TYPE_FOOTER;        } else {            Demo5Bean demo5Bean = data.get(position);            return demo5Bean.getItemType();        }    }    @NonNull    @Override    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {        RecyclerView.ViewHolder viewHolder = null;        switch (viewType) {            case VIEW_TYPE_TITLE:                viewHolder = new Demo5TitleViewHolder(inflater.inflate(R.layout.itme_demo5_title, parent, false));                break;            case VIEW_TYPE_MENU:                viewHolder = new Demo5MenuViewHolder(inflater.inflate(R.layout.itme_demo5_menu, parent, false));                break;            case VIEW_TYPE_FOOTER:                View view = new View(parent.getContext());                view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, lastH));                viewHolder = new Demo5FooterViewHolder(view);                break;        }        return viewHolder;    }    @Override    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {        switch (getItemViewType(position)) {            case VIEW_TYPE_TITLE:                ((Demo5TitleViewHolder) holder).tv_item_demo5_title.setText(data.get(position).getName());                Log.i("TTT",holder.itemView.getMeasuredHeight()+"VIEW_TYPE_TITLE");                break;            case VIEW_TYPE_MENU:                ((Demo5MenuViewHolder) holder).tv_item_demo5_menu.setText(data.get(position).getName());                Log.i("TTT",holder.itemView.getMeasuredHeight()+"VIEW_TYPE_MENU");                break;        }    }    @Override    public int getItemCount() {        return data.size() + 1;    }    public static class Demo5TitleViewHolder extends RecyclerView.ViewHolder {        private final TextView tv_item_demo5_title;        public Demo5TitleViewHolder(@NonNull View itemView) {            super(itemView);            tv_item_demo5_title = itemView.findViewById(R.id.tv_item_demo5_title);        }    }    public static class Demo5MenuViewHolder extends RecyclerView.ViewHolder {        private final TextView tv_item_demo5_menu;        public Demo5MenuViewHolder(@NonNull View itemView) {            super(itemView);            tv_item_demo5_menu = itemView.findViewById(R.id.tv_item_demo5_menu);        }    }    public static class Demo5FooterViewHolder extends RecyclerView.ViewHolder {        public Demo5FooterViewHolder(@NonNull View itemView) {            super(itemView);        }    }}

4、实体Bean

public class Demo5Bean {    private String name;    private int itemType;    public Demo5Bean(String name, int itemType) {        this.name = name;        this.itemType = itemType;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getItemType() {        return itemType;    }    public void setItemType(int itemType) {        this.itemType = itemType;    }}

5、其他说明

AppBarLayout代码主动折叠与打开

//打开AppBarLayoutappBarLayout.setExpanded(true);//关闭AppBarLayoutappBarLayout.setExpanded(false);

源码下载地址:Demo全部代码

更多相关文章

  1. Android(安卓)Fragment和FragmentActivity区别和用法
  2. Android(安卓)应用界面开发笔记
  3. Android(安卓)OOM ,回收布局文件中ImageView占用的内存.Bitmap O
  4. android自定义视图属性学习
  5. android仿苹果弹性布局
  6. Android(安卓)5.0 CardView+ListView 卡片布局应用
  7. Android(安卓)自定义 attr
  8. Android(安卓)UI(CheckBox)详解
  9. 初学Android的一些注意事项

随机推荐

  1. PHP基础概念:继承、扩展和trait方法集
  2. jupyter和pycharm区别是什么?Python学习
  3. grid项目对齐示例并用grid模拟bootstrap/
  4. 0129-数组的排序, 数组的合并, 数组成员
  5. vue学习之路(路由)
  6. 0201-常用字符串函数
  7. Linux发行版的系统目录名称命名规则及用
  8. Centos7最小安装完成后,ifconfig命令用不
  9. 【最新】PMP、ACP换审续费流程
  10. 基于Docker部署Ldap环境