Material Design 系列之 CoordinatorLayout 开发详解

前言

CoordinatorLayout 遵循 Material 风格,结合 AppbarLayout,CollapsingToolbarLayout 等可产生各种炫酷的效果,本篇博客就将介绍 CoordinatorLayout 的各种酷炫效果。

image

一、View 介绍

1、CoordinatorLayout

又名协调者布局,它是 support.design 包中的控件。简单来说,CoordinatorLayout 是用来协调其子 view 并以触摸影响布局的形式产生动画效果的一个 super-powered FrameLayout,其典型的子 View 包括:FloatingActionButtonSnackBar。注意:CoordinatorLayout 是一个顶级父 View

2、AppBarLayout

AppBarLayoutLinearLayout 的子类,必须在它的子 view 上设置 app:layout_scrollFlags 属性或者是在代码中调用 setScrollFlags()设置这个属性。

AppBarLayout 的子布局有 5 种滚动标识(上面代码 CollapsingToolbarLayout 中配置的app:layout_scrollFlags 属性):

  • scroll:所有想滚动出屏幕的 view 都需要设置这个 flag, 没有设置这个 flag 的 view 将被固定在屏幕顶部。
  • enterAlways:这个 flag 让任意向下的滚动都会导致该 view 变为可见,启用快速“返回模式”。
  • enterAlwaysCollapsed:假设你定义了一个最小高度(minHeight)同时 enterAlways 也定义了,那么 view 将在到达这个最小高度的时候开始显示,并且从这个时候开始慢慢展开,当滚动到顶部的时候展开完。
  • exitUntilCollapsed:当你定义了一个 minHeight,此布局将在滚动到达这个最小高度的时候折叠。
  • snap:当一个滚动事件结束,如果视图是部分可见的,那么它将被滚动到收缩或展开。例如,如果视图只有底部 25%显示,它将折叠。相反,如果它的底部 75%可见,那么它将完全展开。

3、CollapsingToolbarLayout

CollapsingToolbarLayout 作用是提供了一个可以折叠的 Toolbar,它继承自 FrameLayout,给它设置 layout_scrollFlags,它可以控制包含在 CollapsingToolbarLayout 中的控件(如:ImageView、Toolbar)在响应 layout_behavior 事件时作出相应的 scrollFlags 滚动事件(移除屏幕或固定在屏幕顶端)。

CollapsingToolbarLayout 可以通过 app:contentScrim 设置折叠时工具栏布局的颜色,通过 app:statusBarScrim 设置折叠时状态栏的颜色。默认 contentScrimcolorPrimary 的色值,statusBarScrimcolorPrimaryDark 的色值。

CollapsingToolbarLayout 的子布局有 3 种折叠模式(Toolbar 中设置的 app:layout_collapseMode

  • off:默认属性,布局将正常显示,无折叠行为。
  • pin:CollapsingToolbarLayout 折叠后,此布局将固定在顶部。
  • parallax:CollapsingToolbarLayout 折叠时,此布局也会有视差折叠效果。当 CollapsingToolbarLayout 的子布局设置了 parallax 模式时,我们还可以通过 app:layout_collapseParallaxMultiplier 设置视差滚动因子,值为:0~1。

常用属性:

  • app:contentScrim:当 Toolbar 收缩到一定程度时的所展现的主体颜色。即 Toolbar 的颜色。
  • app:title:当 titleEnable 设置为 true 的时候,在 toolbar 展开的时候,显示大标题,toolbar 收缩时,显示为 toolbar 上面的小标题。
  • app:scrimAnimationDuration:该属性控制 toolbar 收缩时,颜色变化的动画持续时间。即颜色变为 contentScrim 所指定的颜色进行的动画所需要的时间
  • app:collapsedTitleTextAppearance:指定 toolbar 收缩时,标题字体的样式。
  • app:collapsedTitleGravity : 指定 toolbar 收缩时的标题文字对齐方式。
  • app:expandedTitleTextAppearance : 指定 toolbar 展开后的标题文字字体。
  • app:expandedTitleGravity:指定 toolbar 展开时,title 所在的位置。类似的还有 expandedTitleMargin、collapsedTitleGravity 这些属性。
  • app:expandedTitleMargin : 指定展开后的标题四周间距。

4、NestedScrollView

在新版的 support-v4 兼容包里面有一个 NestedScrollView 控件,这个控件其实和普通的 ScrollView 并没有多大的区别,这个控件其实是 Meterial Design 中设计的一个控件,目的是跟 MD 中的其他控件兼容。应该说在 MD 中,RecyclerView 代替了ListView,而 NestedScrollView 代替了 ScrollView,他们两个都可以用来跟ToolBar 交互,实现上拉下滑中 ToolBar 的变化。在 NestedScrollView 的名字中其实就可以看出他的作用了,Nested 是嵌套的意思,而 ToolBar 基本需要嵌套使用。

5、FloatingActionButton

FloatingActionButton 就是一个漂亮的按钮,其本质是一个 ImageVeiw。有一点要注意,Meterial Design 引入了 Z 轴的概念,就是所有的 view 都有了高度,他们一层一层贴在手机屏幕上,而 FloatingActionButton 的 Z 轴高度最高,它贴在所有 view 的最上面,没有 view 能覆盖它。

6、Behavior

Behavior 只有是 CoordinatorLayout 的直接子 View 才有意义。只要将 Behavior 绑定到 CoordinatorLayout 的直接子元素上,就能对触摸事件(touch events)、window insets、measurement、layout 以及嵌套滚动(nested scrolling)等动作进行拦截。Design Library 的大多功能都是借助 Behavior 的大量运用来实现的。当然,Behavior 无法独立完成工作,必须与实际调用的 CoordinatorLayout 子视图相绑定。具体有三种方式:通过代码绑定、在 XML 中绑定或者通过注释实现自动绑定

二、应用实战

1、CoordinatorLayout + FloatingActionButton 使用

市面上众多 APP 首页都会使用 CoordinatorLayout 结合 FloatingActionButton 的效果,当列表垂直方向滑动时,FloatingActionButton 随之显示与隐藏。

  • 布局文件

    
    
    
    
    
    
    
    
    

可以看到布局文件最外层使用 CoordinatorLayout,里面包含了 RecyclerViewToolbarFloatingActionButton三个子 View,使用android:layout_gravity="end|bottom"属性设置 FloatingActionButton 显示在右下角位置。关于 ToolBarFloatingActionButton 使用还不熟悉的朋友请自行补课。

FloatingActionButton 设置app:layout_behavior属性,这里的".coordinatorLayout.behavior.FabBehavior"是自定义 Behavior 实现。

  • 自定义 Behavior

       public class FabBehavior extends FloatingActionButton.Behavior {
    
        private boolean visible = true;
    
        public FabBehavior(Context context, AttributeSet attrs) {
              super(context, attrs);
      }
    
        @Override
        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                   @NonNull FloatingActionButton child,
                                   @NonNull View directTargetChild,
                                   @NonNull View target,
                                   int axes, int type) {
    //判断如果是垂直滚动则返回true
    return axes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type);
      }
    
        @Override
        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                           @NonNull FloatingActionButton child,
                           @NonNull View target,
                           int dxConsumed, int dyConsumed,
                           int dxUnconsumed, int dyUnconsumed, int type) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed  , type);
    
    if (dyConsumed > 0 && visible) {
        visible = false;
        child.animate().scaleX(0).scaleY(0).setInterpolator(new AccelerateInterpolator(3));
    } else if (dyConsumed < 0 && !visible) {
        visible = true;
        child.animate().scaleX(1).scaleY(1).setInterpolator(new DecelerateInterpolator(3));
    }
      }
    }
    
  • onStartNestedScroll:一定要按照自己的需求返回 true,该方法决定了当前控件是否能接收到其内部 View(非并非是直接子 View)滑动时的参数;假设你只涉及到纵向滑动,这里可以根据 nestedScrollAxes 这个参数,进行纵向判断。

  • onNestedScroll:在内层 view 将剩下的滚动消耗完之后调用,可以在这里处理最后剩下的滚动

image.gif

2、CoordinatorLayout + FloatingActionButton + Snackbar 使用

日常开发过程中,使用 FloatingActionButton + Snackbar 会出现一个 BUG,当 Snackbar 显示时会覆盖 FloatingActionButton 显示,这种展示效果很不友好,如下图所示。

image

解决问题最简单的方式就是最外层布局更换成 CoordinatorLayout,即可轻松完成

  • Activity 类:(提醒:布局文件与上面一致)

    public class SnackBarBehaviorActivity extends BaseActivity {
    
      @BindView(R.id.recyclerView)
      RecyclerView recyclerView;
      RecyclerViewAdapter recyclerViewAdapter;
      private List stringList = new ArrayList<>();
    
      @Override
      protected void initView() {
          setToolbarTitle("SnackBar Behavior");
          setToolBarCallBack();
          for (int i = 1; i <= 100; i++) {
            stringList.add("ITEM " + i);
        }
    recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
    recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
    recyclerViewAdapter = new RecyclerViewAdapter(R.layout.recyclerview_item, stringList);
    recyclerViewAdapter.bindToRecyclerView(recyclerView);
    }
    
    @Override
    protected int getLayoutResID() {
    return R.layout.activity_fabbehavior_layout;
    }
    
      public void fabClick(View view) {
          Snackbar.make(view, "谁让你点击的?", Snackbar.LENGTH_SHORT)
            .setAction("关闭", v -> {
                Toast.makeText(SnackBarBehaviorActivity.this, "Sorry", Toast.LENGTH_SHORT).show();
            }).addCallback(new BaseTransientBottomBar.BaseCallback() {
        @Override
        public void onDismissed(Snackbar transientBottomBar, int event) {
            super.onDismissed(transientBottomBar, event);
        }
    
        @Override
        public void onShown(Snackbar transientBottomBar) {
            super.onShown(transientBottomBar);
        }
    }).show();
    }
    }
    

其实代码很简单,按照正常逻辑处理就完事了,主要在于布局文件的合理搭配使用上。

image

3、CoordinatorLayout + AppBarLayout 使用

CoordinatorLayout + AppBarLayout 应该是现在 APP 主流的布局方式,配合 ToolbarTabLayout 可以实现炫酷的列表滑动折叠效果,提升应用整体档次,增加用户体验。

  • 布局文件

    
    
    
    
    
    
    
    
    
      
    
    
      ` 

使用 AppBarLayoutToolbarTabLayout 包裹,底部使用 ViewPager 布局,ViewPagerapp:layout_behavior=”@string/appbar_scrolling_view_behavior”Behavior 是系统默认的,我们也可以根据自己的需求来自定义 Behavior

注意 Toolbar 设置了 app:layout_scrollFlags="scroll|enterAlways"属性,这是一个非常重要的属性,子布局通过此确定是否可滑动。 其中有五种属性,具体含义可阅读上文 AppBarLayout 介绍。

  • Activity 类

      public class CoordinatorCardViewActivity extends BaseActivity {
        private List fragments = new ArrayList<>();
        @BindView(R.id.tabLayout)
        TabLayout tabLayout;
        @BindView(R.id.viewPage)
        ViewPager viewPage;
        private String mTitles[] = {"TAB 0", "TAB 1", "TAB 2"};
    
        @Override
        protected void initView() {
          setToolBarCallBack();
          setToolbarTitle("Behavior");
    // 设置文本字体颜色[未选中颜色、选中颜色]
    tabLayout.setTabTextColors(getResources().getColor(R.color.gray),
            getResources().getColor(R.color.white));
    // 设置下划线跟文本宽度一致
    tabLayout.setTabIndicatorFullWidth(true);
    // 设置TabLayout和ViewPager绑定
    tabLayout.setupWithViewPager(viewPage, false);
    // 添加TAB标签
    for (String mTitle : mTitles) {
        tabLayout.addTab(tabLayout.newTab().setText(mTitle));
    }
    fragments.add(CardImageFragment.newInstance());
    fragments.add(CardTextFragment.newInstance());
    fragments.add(CardBelleFragment.newInstance());
    
    viewPage.setAdapter(new FragmentAdapter(getSupportFragmentManager(), tabLayout.getTabCount()));
    // 设置ViewPager默认显示index
    viewPage.setCurrentItem(0);
      }
    
      @Override
        protected int getLayoutResID() {
          return R.layout.activity_coordinator_cardviewlayout;
      }
    
      class FragmentAdapter extends FragmentPagerAdapter {
    
    public FragmentAdapter(@NonNull FragmentManager fm, int behavior) {
        super(fm, behavior);
    }
    
    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return mTitles[position];
    }
    
    @NonNull
    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }
    
    @Override
    public int getCount() {
        return fragments.size();
    }
      }
    
    } 
    

Activity 类主要设置了 TabLayoutViewPager 相关属性,如果对 TabLayout + ViewPager 还不熟悉的朋友可以阅读之前文章。实现具体效果如下:

image

4、CoordinatorLayout + AppBarLayout + CollapsingToolbarLayout 使用

  • 布局文件

    
    
    
    
    
    
    
        
    
        
    
    
      
    
      
    
    
    
      
    
      
    
    ` 

FloatingActionButton 这个控件通过 app:layout_anchor 这个设置锚定在了 AppBarLayout 下方。FloatingActionButton 源码中有一个 Behavior 方法,当 AppBarLayout 收缩时,FloatingActionButton 就会跟着做出相应变化。

  • Activity 类

    public class AppBarBehaviorActivity extends BaseActivity {
    
      @Override
      protected void initView() {
          StatusBarUtils.setTransparent(this);
          setToolBarCallBack();
          setSupportActionBar(toolbar);
          getSupportActionBar().setDisplayHomeAsUpEnabled(true);
      }
    
      @Override
        protected int getLayoutResID() {
          return R.layout.activity_coordinator_collapsinglayout;
      }
    } 
    

注意:在背景图片沉浸式的时候,只靠上面的 XML 布局是无法实现的,还需要 2 行代码:

      setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
image

三、总结

本文主要介绍 CoordinatorLayout 的基本使用,还是非常简单的,CoordinatorLayout作为协调者,肯定是非常重要的。CoordinatorLayout 是一个“加强版”的 FrameLayout,我们只需要知道他两个作用

(1) 用作应用的顶层布局管理器

(2) 通过为子 View 指定 behavior 实现自定义的交互行为。在我们做 Material Design 风格的 app 时通常都使用 CoordinatorLayout 作为布局的根节点,以便实现特定的 UI 交互行为。

你可能感兴趣的:(Material Design 系列之 CoordinatorLayout 开发详解)