界面无小事(九): 做个好看的伸缩头部

github传送门

前言

之前也是写了RecyclerView的内容, 这次再补充伸缩头部的实现. 港真, 伸缩头部是那种看到第一眼就会爱上的视图效果, 好看又简洁.

  • 本文内容较多, 需要10分钟以上阅读时间, 请合理安排, 收藏也是可以的哦~
  • 多图预警, 转载请说明出处, 感谢~

效果图

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

case1

case2


快速上手

先来实操一下, 看看从默认的滚动模板(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:

复制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模式

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


AppBarLayout滚动方式

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

参数

效果

scroll

视图将滚动与滚动事件直接相关. 需要设置此标志才能使任何其他标志生效. 如果在此之前的任何兄弟视图没有此标志, 则此值无效.

exitUntilCollapsed

退出(滚动屏幕)时, 视图将滚动直到“折叠”. 折叠高度由视图的最小高度定义。

snap

在滚动结束时, 如果视图仅部分可见, 则它将被捕捉并滚动到其最近的边缘.

enterAlways

当进入(在屏幕上滚动)时, 无论滚动视图是否也在滚动, 视图都将滚动任何向下滚动事件. 这通常被称为“快速返回”模式.

enterAlwaysCollapsed

'enterAlways'的另一个标志, 它修改返回的视图, 最初只回滚到它的折叠高度. 一旦滚动视图到达其滚动范围的末尾, 该视图的其余部分将滚动到视图中. 折叠高度由视图的最小高度定义.

  • 看看单scroll的情况: app:layout_scrollFlags="scroll"

scroll

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

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

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

snap

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

enterAlways

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

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填入.


最后

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

顺带一提, 腾讯云+社区也将同步我的文章了, 目前还在审核中: 我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=cji0h4jns3lw

github传送门


本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大数据钻研

HTML 入门笔记 - 初识HTML

基础框架 <!DOCTYPE HTML><html><head><meta http-equiv="Content-Type" content="text/ht...

48350
来自专栏james大数据架构

android 中resources管理

主要存在于res/value文件夹中 定义: dimen.xml:主要用于设置像素默认值 <resources> res/values/dimens.xml...

22780
来自专栏Android机动车

Kotlin超简单实现StepView

支持时间轴和StepView,三种布局,支持水平布局,垂直布局和自定义布局,截图如下

16120
来自专栏林德熙的博客

win10 UWP ListView

如果发现 UWP ListView 横向没有滚动条,可以使用 ScrollViewer 添加

9820
来自专栏姬小光

小鸡君の前端小姜汤【第015期】- 绝对定位

前面我们学过了“表格布局”(回复 007 或 7)的简单栗子,如果大家用心尝试的话,应该已经可以做出一些粗糙的页面了。

8710
来自专栏葡萄城控件技术团队

Spread for Windows Forms高级主题(3)---单元格的编辑模式

理解单元格的编辑模式 通常情况下,当终端用户双击单元格时,编辑控件将允许用户在该单元格中输入内容。在一个单元格中编辑的能力被称为编辑模式。一些属性和方法可以用来...

23960
来自专栏飞扬的花生

一步一步学习Bootstrap系列--表单布局

前言:Bootstrap 属于前端 ui 库,通过现成的ui组件能够迅速搭建前端页面,简直是我们后端开发的福音,通过几个项目的锻炼有必要总结些常用的知识,本篇把...

353100
来自专栏Nian糕的私人厨房

CSS 鼠标悬停图片,显示隐藏文本

我在 JavaScript 鼠标悬停图片,显示隐藏文本 这篇博文当中实现了同样的效果,只不过是通过 JS 来实现的,但其实,通过 CSS 动画也能实现同样的效果...

17740
来自专栏余生开发

markdown基本语法

markdown是一种纯文本格式的标记语言。通过简单的标记语法,它可以使普通文本内容具有一定的格式。

11640
来自专栏Google Dart

AngularDart Material Design 弹出框 顶

该组件将自己发布为DropdownHandle,因此其子级可以通过注入来控制其可见性:

20330

扫码关注云+社区

领取腾讯云代金券