手记

用CoordinatorLayout做个好看的伸缩头部

github传送门


目录
  • 前言
  • 效果图
  • 快速上手
  • CollapsingToolbarLayout折叠模式
  • AppBarLayout滚动方式
  • CoordinatorLayout配合Snackbar
  • 自定义伸缩头部
  • 最后

前言

垂直滚动这个操作, 水果机一代就有了, 是移动App最常见的内容展示视图了, 之前也是写了RecyclerView的内容, 这次再补充伸缩头部的实现. 港真, 伸缩头部是那种看到第一眼就会爱上的视图效果, 好看又简洁.


效果图

先上案例的效果图, 有兴趣再看下去:


快速上手

先来实操一下, 看看从默认的滚动模板(Scrolling Activity)到效果图要几步.

  • 首先, 在Toolbar上面加入ImageView, 参数之后再说明.
<android.support.design.widget.CollapsingToolbarLayout
    android:id="@+id/toolbar_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    app:contentScrim="?attr/colorPrimary"
    app:layout_scrollFlags="scroll|exitUntilCollapsed"
    app:toolbarId="@+id/toolbar">

    <ImageView
        android:id="@+id/iv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:contentDescription="@string/desc"
        android:scaleType="centerCrop"
        app:layout_collapseMode="pin" />

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:layout_collapseMode="pin" />

</android.support.design.widget.CollapsingToolbarLayout>
  • 然后在java代码中使用Glide加载图片.

导包:

implementation 'com.github.bumptech.glide:glide:3.7.0'
// 加载图片
ImageView ivMain = (ImageView) findViewById(R.id.iv_main);
Glide.with(this).load(R.drawable.p5).into(ivMain);
  • 看下效果:

发现两个问题, 由于背景是白色, 标题栏字体颜色要变成黑色, 默认就是黑色, 所以就是删除xml中的主题设置. 当然, 如果你是深色背景, 这里就无需动它. 然后标题栏需要变成透明的.

将标题栏设置透明色

那由于5.0之前是不能变的, 将styles.xml从5.0区分开, 5.0之前什么都不做, 之后版本设置标题栏为透明色. 现在styles.xml中写入:

<style name="MyTheme" parent="AppTheme" />

然后复制styles.xml:

删除重复部分:

<resources>

    <style name="MyTheme" parent="AppTheme">
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
</resources>
  • 然后在配置文件设置新主题, 顺带改下标题名称, 再次运行看下效果:
// 设置标题
ActionBar supportActionBar = getSupportActionBar();
if (supportActionBar != null) {
    supportActionBar.setTitle(UIUtil.getString(R.string.p5));
}

这样就完成了.


CollapsingToolbarLayout折叠模式
app:layout_collapseMode="parallax"
app:layout_collapseMode="pin"
app:layout_collapseMode="none"

从xml中的参数说吧, 来看CollapsingToolbarLayout的折叠模式. 下面我也是截了一段官方文档内容.

COLLAPSE_MODE_OFF

int COLLAPSE_MODE_OFF
The view will act as normal with no collapsing behavior.

Constant Value: 0 (0x00000000)

COLLAPSE_MODE_PARALLAX

int COLLAPSE_MODE_PARALLAX
The view will scroll in a parallax fashion. See setParallaxMultiplier(float) to change the multiplier used.

Constant Value: 2 (0x00000002)

COLLAPSE_MODE_PIN

int COLLAPSE_MODE_PIN
The view will pin in place until it reaches the bottom of the CollapsingToolbarLayout.

Constant Value: 1 (0x00000001)

列个表再看下:

参数 效果
none 视图将正常运行, 没有折叠行为
pin 视图将固定到位, 直到它到达CollapsingToolbarLayout的底部
parallax 视图将以视差方式滚动

是不是该怎么懵还是怎么懵, 来看效果图:

注意看人物的脚, parallax模式下人物最终滑动到身体部位消失. pin模式下, 人物滑到脚部位消失. 也就是说, pin模式下, 下面的滚动视图和图片是同步滑动的, 但是这样的观感其实不好. parallax则改进了这一点, 看起来很和谐, 尽管两者不再同步, 这就是翻译后说的以视差方式滚动了.


AppBarLayout滚动方式

滚动方式主要依靠参数组合(scroll必须要), 列个表再看下效果图, 官方文档就不截了.

参数 效果
scroll 视图将滚动与滚动事件直接相关. 需要设置此标志才能使任何其他标志生效. 如果在此之前的任何兄弟视图没有此标志, 则此值无效.
exitUntilCollapsed 退出(滚动屏幕)时, 视图将滚动直到“折叠”. 折叠高度由视图的最小高度定义。
snap 在滚动结束时, 如果视图仅部分可见, 则它将被捕捉并滚动到其最近的边缘.
enterAlways 当进入(在屏幕上滚动)时, 无论滚动视图是否也在滚动, 视图都将滚动任何向下滚动事件. 这通常被称为“快速返回”模式.
enterAlwaysCollapsed 'enterAlways'的另一个标志, 它修改返回的视图, 最初只回滚到它的折叠高度. 一旦滚动视图到达其滚动范围的末尾, 该视图的其余部分将滚动到视图中. 折叠高度由视图的最小高度定义.
  • 看看单scroll的情况: app:layout_scrollFlags="scroll"

可以看到整个滚上去了, 没有保留Toolbar.

  • 那我现在用的是app:layout_scrollFlags="scroll|exitUntilCollapsed", 效果大家也见过了.

  • 喜闻乐见的吸附效果, app:layout_scrollFlags="scroll|snap", 例如, 还剩下25%没滚完, 松手就自己滚出去; 如果还有75%没滚完, 松手直接全部显示. 但是我感觉体验不好, 会让人有着操作不灵敏的错觉.

  • 快速返回, 就是把滚出去的部分快速显示出来, 可以对比之前的返回速度来看: app:layout_scrollFlags="scroll|enterAlways"

  • 对比快速返回来看, 这个相对柔和一些, 可以理解为二段式的快速返回, 总之就是返回没有enterAlways那么迅速: app:layout_scrollFlags="scroll|enterAlwaysCollapsed"


CoordinatorLayout配合Snackbar

先来看看自带的点击悬浮按钮的效果:

不让悬浮按钮吸附在Toolbar上, 将它放置到底部, 再看下效果:

android:layout_gravity="end|bottom"

如果不是CoordinatorLayout, 可就没有这种效果了哦.


自定义伸缩头部

再来看一个改动更大, 更自定义的. 先上效果图:

相比于之前的, 最大的变化在于对滚动幅度的监听. 依据滚动幅度变化Toolbar内容.

布局文件

先来看下主布局文件的变化, Toolbar包含了两个布局文件, 相互切换. 然后展开部分由之前的ImageView变成了一个布局文件, 这里要注意app:contentInsetLeft="0dp", app:contentInsetStart="0dp", 这个就像html的默认边距一样, 需要清零. 不写的话左侧有默认的边距.

<android.support.design.widget.CollapsingToolbarLayout
    android:id="@+id/toolbar_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    app:layout_scrollFlags="scroll|exitUntilCollapsed"
    app:toolbarId="@+id/toolbar">

    <include
        layout="@layout/open_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="?attr/actionBarSize"
        app:layout_collapseMode="parallax" />

    <android.support.v7.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:contentInsetLeft="0dp"
        app:contentInsetStart="0dp"
        app:layout_collapseMode="pin">

        <include
            android:id="@+id/toolbar_open"
            layout="@layout/toolbar_open" />

        <include
            android:id="@+id/toolbar_close"
            layout="@layout/toolbar_close" />

    </android.support.v7.widget.Toolbar>

</android.support.design.widget.CollapsingToolbarLayout>

三个小的布局代码我就贴一个做栗子:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="@color/colorPrimary">

    <ImageView
        android:id="@+id/iv_first"
        android:layout_width="@dimen/twenty_four_dp"
        android:layout_height="@dimen/twenty_four_dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_centerVertical="true"
        android:layout_marginLeft="@dimen/twenty_dp"
        android:layout_marginStart="@dimen/twenty_dp"
        android:contentDescription="@string/desc"
        android:src="@android:drawable/btn_star_big_on" />

    <ImageView
        android:id="@+id/iv_second"
        android:layout_width="@dimen/twenty_four_dp"
        android:contentDescription="@string/desc"
        android:layout_height="@dimen/twenty_four_dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="@dimen/twenty_dp"
        android:layout_marginStart="@dimen/twenty_dp"
        android:layout_toEndOf="@+id/iv_first"
        android:layout_toRightOf="@+id/iv_first"
        android:src="@android:drawable/btn_star_big_on" />

    <ImageView
        android:id="@+id/iv_third"
        android:layout_width="@dimen/twenty_four_dp"
        android:layout_height="@dimen/twenty_four_dp"
        android:contentDescription="@string/desc"
        android:layout_centerVertical="true"
        android:layout_marginLeft="@dimen/twenty_dp"
        android:layout_marginStart="@dimen/twenty_dp"
        android:layout_toEndOf="@+id/iv_second"
        android:layout_toRightOf="@+id/iv_second"
        android:src="@android:drawable/btn_star_big_on" />

    <ImageView
        android:id="@+id/iv_forth"
        android:layout_width="@dimen/twenty_four_dp"
        android:contentDescription="@string/desc"
        android:layout_height="@dimen/twenty_four_dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="@dimen/twenty_dp"
        android:layout_marginStart="@dimen/twenty_dp"
        android:layout_toEndOf="@+id/iv_third"
        android:layout_toRightOf="@+id/iv_third"
        android:src="@android:drawable/btn_star_big_on" />

    <ImageView
        android:layout_width="@dimen/twenty_four_dp"
        android:layout_height="@dimen/twenty_four_dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="@dimen/twenty_dp"
        android:contentDescription="@string/desc"
        android:layout_marginStart="@dimen/twenty_dp"
        android:layout_toEndOf="@+id/iv_forth"
        android:layout_toRightOf="@+id/iv_forth"
        android:src="@android:drawable/btn_star_big_on" />

    <View
        android:id="@+id/toolbar_close_mask"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

要点在最后的View, 这是一个遮罩, 依据滚动幅度变化其透明度起到遮罩效果.

AppBarLayout.OnOffsetChangedListener

官方文档写的很简单, 使用起来也不难. 添加implements AppBarLayout.OnOffsetChangedListener. 实现public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset)方法.

@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
    int offset = Math.abs(verticalOffset);
    int scrollRange = appBarLayout.getTotalScrollRange();
    if (offset <= scrollRange / 2) {
        mToolbarOpen.setVisibility(View.VISIBLE);
        mToolbarClose.setVisibility(View.GONE);
        float openPer = (float) offset / (scrollRange / 2);
        int openAlpha = (int) (255 * openPer);
        mToolbarOpenMask.setBackgroundColor(Color.argb(openAlpha, 48, 63, 159));
    } else {
        mToolbarClose.setVisibility(View.VISIBLE);
        mToolbarOpen.setVisibility(View.GONE);
        float closePer = (float) (scrollRange - offset) / (scrollRange / 2);
        int closeAlpha = (int) (255 * closePer);
        mToolbarCloseMask.setBackgroundColor(Color.argb(closeAlpha, 48, 63, 159));
    }
    float per = (float) offset / scrollRange;
    int alpha = (int) (255 * per);
    mContentMask.setBackgroundColor(Color.argb(alpha, 48, 63, 159));
}

前面也说了, 就是变化遮罩透明度, 这个颜色是对应了布局中设置的颜色的, 否则过渡效果就不对了. 可以用下PS将colors.xml中6位颜色变成rgb填入.


最后

看到这里也很不容易啦(手动比心). 喜欢记得点赞, 有意见或者建议评论区见, 暗中关注我也是可以的哦~

github传送门


6人推荐
随时随地看视频
慕课网APP

热门评论

感谢分享 很全面 就是能不能换个平台写  这平台图片都显示不出来?

感谢分享 很全面 就是能不能换个平台写  这平台图片都显示不出来?

推荐一篇不错的文章  CoordinatorLayout + AppBarLayout + NestedScrollView 组合使用实现地图背景,滑动悬停华丽效果。仿饿了么地图界面

https://blog.csdn.net/jason_rui/article/details/100222797


查看全部评论