注:该文章也同步更新到稀土掘金:链接

前言

CollapsingToolbarLayout是android MaterialDeign提供的一个组件,通过搭配AppBarLayout可实现toolbar的折叠效果。下边就通过仿实现稀土掘金个人中心页来讲解它的具体用法。先上效果图:

实现

我们将toolbar分为两层:

  • 红色圈中的大模块,暂名:A
  • 绿色圈中的模块,暂名:B
    通过分层再分析下效果:
  • 当视图向上滚动时,A会逐渐折叠
  • 当A完全折叠后,B的图标会更新状态,并且用户的信息会向上浮动显示在B中

  • 当A没有完全折叠后,用户信息会消失,并且B中的图标会恢复原来的状态

通过效果分析,我们可以这么设计:

  • 将A内容包含在CollapsingToolbarLayout中,再配合CoordinatorLayout+AppBarLayout,即可实现A内容的折叠
  • 监听A内容是否折叠,改变B的状态

CollapsingToolbarLayout折叠事件的监听

  1. 通过AppBarLayout.OnOffsetChangedListener的回调
Interface definition for a callback to be invoked when an AppBarLayout's vertical offset changes.
// TODO(b/76413401): update this interface after the widget migration
public interface OnOffsetChangedListener extends BaseOnOffsetChangedListener<AppBarLayout> {void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset);
}

查看官方api文档,该回调可以获取到AppBarLayout的垂直偏移量。通过判断偏移量的大小,即可获取到当前toolbar的状态

abstract class AppBarStateChangeListener : AppBarLayout.OnOffsetChangedListener {enum class State {EXPANDED, COLLAPSED, IDLE}private var mCurrentState = State.IDLEoverride fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) {if (verticalOffset == 0) {if (mCurrentState != State.EXPANDED) {onStateChanged(appBarLayout, State.EXPANDED);}mCurrentState = State.EXPANDED} else if (appBarLayout != null) {if (Math.abs(verticalOffset) >= appBarLayout.totalScrollRange) {if (mCurrentState != State.COLLAPSED) {onStateChanged(appBarLayout, State.COLLAPSED);}mCurrentState = State.COLLAPSED} else {if (mCurrentState != State.IDLE) {onStateChanged(appBarLayout, State.IDLE);}mCurrentState = State.IDLE;}}}abstract fun onStateChanged(appBarLayout: AppBarLayout?, state: State)
}
  1. 通过CollapsingToolbarLayout的setScrimsShown函数
Set whether the content scrim and/or status bar scrim should be shown or not. Any change in the vertical scroll may overwrite this value.
Params:
shown – whether the scrims should be shown
animate – whether to animate the visibility change
See Also:getStatusBarScrim(), getContentScrim()public void setScrimsShown(boolean shown, boolean animate) {if (scrimsAreShown != shown) {if (animate) {animateScrim(shown ? 0xFF : 0x0);} else {setScrimAlpha(shown ? 0xFF : 0x0);}scrimsAreShown = shown;}
}

查看官方api文档,可以看到当内容发生变化时,该方法会被自动调用,因此我们可以通过扩展该方法

class NestCollapsingToolbarLayout : CollapsingToolbarLayout {private var mIsScrimsShown: Boolean = falseprivate lateinit var scrimsShowListener: OnScrimsShowListenerconstructor(context: Context) : super(context)constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr)override fun setScrimsShown(shown: Boolean, animate: Boolean) {super.setScrimsShown(shown, animate)if (mIsScrimsShown != shown) {mIsScrimsShown = shownif (scrimsShowListener != null) {scrimsShowListener.onScrimsShowChange(this, mIsScrimsShown)}}}fun setOnScrimesShowListener(listener: OnScrimsShowListener){scrimsShowListener = listener}interface OnScrimsShowListener {fun onScrimsShowChange(nestCollapsingToolbarLayout: NestCollapsingToolbarLayout,isScrimesShow: Boolean)}
}

当然这两个方法会有区别,方法一监听的offset为整个AppBarLayout包含内容的高度,方法二只针对CollapsingToolbarLayout包含内容的高度。这里我使用方法二

布局设计

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"android:background="@android:color/white"android:orientation="vertical"tools:context=".MainActivity"><androidx.coordinatorlayout.widget.CoordinatorLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><com.google.android.material.appbar.AppBarLayoutandroid:id="@+id/appbar"android:layout_width="match_parent"android:layout_height="wrap_content"><com.example.behaviordemo.NestCollapsingToolbarLayoutandroid:id="@+id/toolbarLayout"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@android:color/white"app:layout_scrollFlags="scroll|exitUntilCollapsed"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:layout_width="match_parent"android:layout_height="150dp"android:scaleType="centerCrop"android:src="@drawable/user_profile_header" /><ImageViewandroid:id="@+id/ivHead"android:layout_width="60dp"android:layout_height="60dp"android:layout_marginLeft="10dp"android:layout_marginTop="125dp"android:background="@drawable/empty_avatar_user" /><LinearLayoutandroid:id="@+id/llAuthor"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@+id/ivHead"android:layout_marginLeft="10dp"android:layout_marginTop="10dp"android:gravity="center_vertical"android:orientation="horizontal"><TextViewandroid:id="@+id/author_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="PG_KING"android:textColor="@android:color/black"android:textSize="16sp" /><ImageViewandroid:id="@+id/ivLevel"android:layout_width="27dp"android:layout_height="15dp"android:layout_marginLeft="5dp"android:background="@drawable/ic_user_big_lv1" /></LinearLayout><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@+id/llAuthor"android:layout_marginLeft="8dp"android:text="Android"android:textColor="@android:color/darker_gray"android:textSize="13sp" /><TextViewandroid:layout_width="80dp"android:layout_height="35dp"android:layout_alignTop="@+id/llAuthor"android:layout_alignParentRight="true"android:layout_marginRight="10dp"android:background="@drawable/bg_edit"android:gravity="center"android:text="编辑"android:textColor="@color/theme_blue"android:textSize="15sp" /></RelativeLayout></com.example.behaviordemo.NestCollapsingToolbarLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:background="@android:color/white"android:orientation="horizontal"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center_vertical"android:orientation="vertical"android:paddingLeft="10dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="10000"android:textColor="@android:color/black"android:textSize="14sp" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="关注"android:textColor="@android:color/darker_gray"android:textSize="13sp" /></LinearLayout><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center_vertical"android:orientation="vertical"android:paddingLeft="10dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="10000"android:textColor="@android:color/black"android:textSize="14sp" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="关注"android:textColor="@android:color/darker_gray"android:textSize="13sp" /></LinearLayout><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="match_parent"android:gravity="center_vertical"android:orientation="vertical"android:paddingLeft="10dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="10000"android:textColor="@android:color/black"android:textSize="14sp" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="关注"android:textColor="@android:color/darker_gray"android:textSize="13sp" /></LinearLayout></LinearLayout><Viewandroid:layout_width="match_parent"android:layout_height="10dp"android:background="@android:color/darker_gray" /><com.google.android.material.tabs.TabLayoutandroid:id="@+id/tabLayout"android:layout_width="match_parent"android:layout_height="60dp"app:tabIndicatorColor="@color/theme_blue"app:tabIndicatorFullWidth="false"app:tabSelectedTextColor="@color/theme_blue"app:tabTextColor="@android:color/darker_gray" /></com.google.android.material.appbar.AppBarLayout><androidx.viewpager.widget.ViewPagerandroid:id="@+id/viewPager"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/white"app:layout_behavior="@string/appbar_scrolling_view_behavior" /></androidx.coordinatorlayout.widget.CoordinatorLayout><RelativeLayoutandroid:id="@+id/rlTitle"android:layout_width="match_parent"android:layout_height="60dp"android:paddingLeft="15dp"android:paddingRight="15dp"><ImageViewandroid:id="@+id/ivBack"android:layout_width="25dp"android:layout_height="25dp"android:layout_centerVertical="true"android:src="@drawable/ic_back" /><LinearLayoutandroid:id="@+id/llSmallAuthor"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginLeft="30dp"android:layout_toRightOf="@+id/ivBack"android:gravity="center_vertical"android:orientation="horizontal"android:visibility="invisible"><ImageViewandroid:layout_width="35dp"android:layout_height="35dp"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:src="@drawable/empty_avatar_user" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="10dp"android:text="PG_KING"android:textColor="@android:color/black"android:textSize="16sp" /><ImageViewandroid:layout_width="27dp"android:layout_height="15dp"android:layout_marginLeft="5dp"android:background="@drawable/ic_user_big_lv1" /></LinearLayout><ImageViewandroid:id="@+id/ivShare"android:layout_width="25dp"android:layout_height="25dp"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:src="@drawable/ic_share" /><ImageViewandroid:id="@+id/ivUserData"android:layout_width="25dp"android:layout_height="25dp"android:layout_centerVertical="true"android:layout_marginRight="30dp"android:layout_toLeftOf="@+id/ivShare"android:src="@drawable/ic_userdata" /></RelativeLayout></FrameLayout>
  • AppBarLayout+CollapsingToolbarLayout需要实现折叠效果,必须选择CoordinatorLayout作为根布局
  • CollapsingToolbarLayout需要设置app:layout_scrollFlags=“scroll|exitUntilCollapsed”
  • 定义AppBarLayout与滚动视图之间的联系:在RecyclerView或者任意支持嵌套滚动的view比如NestedScrollView上添加app:layout_behavior属性配置

实现滚动效果

appBarLayout.setOnScrimesShowListener(object :NestCollapsingToolbarLayout.OnScrimsShowListener {override fun onScrimsShowChange(nestCollapsingToolbarLayout: NestCollapsingToolbarLayout,isScrimesShow: Boolean) {if (isScrimesShow) {rlTitle.setBackgroundColor(Color.WHITE)ivBack.setImageResource(R.drawable.ic_back_blue)ivShare.setImageResource(R.drawable.ic_share_blue)ivUserData.setImageDrawable(tintDrawable(resources.getDrawable(R.drawable.ic_userdata),ColorStateList.valueOf(Color.parseColor("#B6B6B6"))))showSmallAuthor()} else {rlTitle.setBackgroundColor(Color.TRANSPARENT)ivBack.setImageResource(R.drawable.ic_back)ivShare.setImageResource(R.drawable.ic_share)ivUserData.setImageDrawable(tintDrawable(ivUserData.drawable,ColorStateList.valueOf(Color.parseColor("#FFFFFF"))))if (objectAnimator.isRunning) {objectAnimator.cancel()}llSmallAuthor.visibility = View.INVISIBLE}}})

补充

1、这里还使用了TabLayout+ViewPager的组合,用于实现主内容区的逻辑。tablayout和viewpager的关联绑定

pagerAdapter = object : ListPagerAdapter(supportFragmentManager, fragments) {override fun getPageTitle(position: Int): CharSequence? {if (tabList.size > position) {return tabList.get(position)}return ""}
}
viewPager.adapter = pagerAdapter
tabLayout.setupWithViewPager(viewPager)

2、动态改变drawable的颜色

看上图,统计柱状图由白色变成灰色~这里没有使用两张图片进行切换,而是使用了系统提供的DrawableCompat.setTintList函数

fun tintDrawable(drawable: Drawable, colors: ColorStateList): Drawable {val wrappedDrawable = DrawableCompat.wrap(drawable)DrawableCompat.setTintList(wrappedDrawable, colors)return wrappedDrawable
}// 使用
ivUserData.setImageDrawable(tintDrawable(resources.getDrawable(R.drawable.ic_userdata),ColorStateList.valueOf(Color.parseColor("#B6B6B6")))
)

拓展

app:layout_scrollFlags属性介绍

<attr name="layout_scrollFlags"><!-- Disable scrolling on the view. This flag should not be combined with any of the otherscroll flags. --><flag name="noScroll" value="0x0"/><!-- The view will be scroll in direct relation to scroll events. This flag needs to beset for any of the other flags to take effect. If any sibling viewsbefore this one do not have this flag, then this value has no effect. --><flag name="scroll" value="0x1"/><!-- When exiting (scrolling off screen) the view will be scrolled until it is'collapsed'. The collapsed height is defined by the view's minimum height. --><flag name="exitUntilCollapsed" value="0x2"/><!-- When entering (scrolling on screen) the view will scroll on any downwardsscroll event, regardless of whether the scrolling view is also scrolling. Thisis commonly referred to as the 'quick return' pattern. --><flag name="enterAlways" value="0x4"/><!-- An additional flag for 'enterAlways' which modifies the returning view toonly initially scroll back to it's collapsed height. Once the scrolling view hasreached the end of it's scroll range, the remainder of this view will be scrolledinto view. --><flag name="enterAlwaysCollapsed" value="0x8"/><!-- Upon a scroll ending, if the view is only partially visible then it will besnapped and scrolled to it's closest edge. --><flag name="snap" value="0x10"/><!-- An additional flag to be used with 'snap'. If set, the view will be snapped to itstop and bottom margins, as opposed to the edges of the view itself. --><flag name="snapMargins" value="0x20"/>
</attr>

查看官方api文档,我们整体梳理下:

  • 其他属性的使用需要配合scroll,否则会没有效果,正确用法举例:app:layout_scrollFlags=“scroll|exitUntilCollapsed”
  • enterAlways:不管向下还是向上滚动,优先滚动的是AppBarLayout中的childView
  • exitUntilCollapsed:向上滚动,优先滚动的是AppBarLayout中的childView;向下滚动,优先滚动的是其他视图,直到其他视图已经滚动到顶部再滚动AppBarLayout
  • snap:可配合其他属性一起使用,可以保证AppBarLayout完全显示或者完全隐藏而不会显示部分

具体的效果,可以通过写例子进行体验。跑过效果理解起来才没那么枯燥~

最后附上demo地址:gitee-demo

使用CollapsingToolbarLayout高仿稀土掘金个人中心页相关推荐

  1. 自定义view高仿稀土掘金loading闪动字体效果

    注:该文章同步发布到稀土掘金:链接 前言 由于通勤时间较长,在路上总会有时间刷刷文章.稀土掘金就是常用的一个app(这里非广告,哈哈哈).前段时间,发表了篇文章:# 使用CollapsingToolb ...

  2. 高仿简书Android,高仿简书个人中心页面

    高仿简书个人中心页面 Demo下载地址: 先贴上效果图 1. 步骤 1.1 把APP的主题改为NoAction 1.2 引入Material Design 包 implementation 'com. ...

  3. 高仿简书个人中心页面

    高仿简书个人中心页面 Demo下载地址: 先贴上效果图 1. 步骤 1.1 把APP的主题改为NoAction <!-- Base application theme. --><st ...

  4. React与Koa一起打造一个仿稀土掘金全栈个人博客(技术篇)

    前言 我的个人博客样式布局是仿的稀土掘金 ,个人博客线上网址为https://www.maomin.club/ ,也可以百度搜索前端历劫之路 .为了浏览体验,可以用PC浏览器浏览. 本篇文章将分为前台 ...

  5. 一款基于Vue2.0高仿微信App的单页应用

    概述 利用Vue2.0模仿微信app,努力做到以假乱真的效果.个人独立开发,源码中有详细的注释,为新手提供许多有用的提示信息.项目的第一期接近尾声,后期会增加 仿3DTouch.登陆.注册.emoji ...

  6. 高仿蓝奏云单页下载页面源码

    介绍: 蓝奏云下载页面,喜欢的可以下载下来! 记事本打开修改内容 网盘下载地址: http://kekewangLuo.net/MSiDnzguCkD0 图片:

  7. 高仿蓝奏云下载页源码

    介绍: 最近刚扒的蓝奏云下载页页面 喜欢的可以下载下来! 网盘下载地址: https://zijiewangpan.com/EGDAQk0pFn8 图片:

  8. android高仿ios控制中心,高仿ios控制中心安卓版

    高仿ios控制中心安卓版是一款非常好用的安卓仿苹果手机控制中心的软件,能够让使用安卓手机的朋友们随时体验苹果手机的系统,操作简单方便,软件也是非常稳定的,大家可放心的下载使用,感兴趣的用户们就前来下载 ...

  9. 伯乐发卡系统高级版源码 高仿淘宝模板 带用户中心

    介绍: 内置高仿淘宝模板,带用户中心  分销代理等等 推荐使用宝塔面板安装,设置运行目录为public,测试环境为php7.0 mysql5.5 伪静态选择为thinkphp 授权已经去了,后台是/h ...

最新文章

  1. insert into 多条数据_最全总结 | 聊聊 Python 数据处理全家桶(Sqlite篇)
  2. 播放视频一会,出错并自动关闭
  3. 自动化运维之SaltStack实践
  4. 团队软件库_新环节!新设备!新软件! ——天津市物流大赛创新大揭秘!
  5. nyoj-括号匹配(二)---动态规划
  6. 大道至简第六章读后感
  7. 项目微管理 - 总结也是新的开始
  8. Java笔记(四)各类容器,set,map,队列实现
  9. WindowsServer2012 DFS配置出错原因
  10. http 请求_HTTP请求方法有哪些?
  11. jquery 获取data-* 属性值
  12. NHOI2019总结
  13. 数据挖掘-贡献度分析
  14. Linux--解决vi报错:E37: No write since last change E162: No write since last change for buffer
  15. Maximal submatrix
  16. 12864oled显示屏专业程序【仅供参考】
  17. 20191223-20191227风险指标的学习总结
  18. matlab动刚度仿真,精彩案例|最好的abaqus结构动刚度分析
  19. matlab improfile用法,MATLAB图像处理基本命令2
  20. 爬取起点中文网字体反爬取

热门文章

  1. MATLAB 画柱状图:更改横轴显示内容,调整横轴显示角度,纵轴加百分号%,调整纵轴显示范围,柱状图顶添加数字
  2. Nginx添加腾讯安全HTTPS证书
  3. 电脑ps计算机磨皮,用PS磨皮详解教程 -电脑资料
  4. mysql sql where or_SQL的WHERE子句中包含多个AND和OR
  5. 3dMax夜晚行车灯光轨迹一键生成插件TrafficTrails使用教程
  6. 3dvary灯光材质为什么不亮_3dmax灯光教程灯光打出来太假?不真实?杂点?曝光?原因都在这儿呢...
  7. [译]Unity3D Shader教程(五)Surface Shader Basics
  8. 攻防世界 MISC新手练习区 刷12道题题所得的思路和方法
  9. 阿里云倒逼亚马逊提高市场竞争?AWS CEO安迪·贾西谈量子计算与AI
  10. Unity Shaders and Effects Cookbook (2-7)实现 Photoshop 色阶效果