本文由 imurluck 授权投稿
原文链接:https://blog.csdn.net/z1289042324/article/details/90447669

HeaderLayout


  网易云音乐App给用户的体验效果一直都非常好,尤其是流畅的动画和滑动的联动效果,都给人一种如丝滑般的感受,这一点在其歌手详情页面体现得尤为突出。那么我们就来实现这样的效果,但是我们不能只局限在实现当中,否则当需求变化就需要改动大量的代码,同时也不能保证它的复用性,放到其他界面则需要写许多重复代码。因此我们需要跳出实现的限制,将其中的元素抽取出来,制作成一个通用的库,并且保证其可拓展性和充分的用户自定义性。经过研究,最终实现了此控件,并取名为HeaderLayout,那么我们先来看看实现效果以便直观的感受一下。

效果图

如何使用


  效果图中所有的头部控件滑动联动效果都只需要在xml中配置几行代码即可完成,由于HeaderLayout是根据CoordinatorLayout的机制来实现的,所以HeaderLayout需要包裹在CoordinatorLayout中才会有效果。

  • 引入依赖

implementation "com.imurluck:headerlayout:$lastVersion"
  • 编写布局
      HeaderLayout继承自FrameLayout,且并没有改写FrameLayout的测量和布局逻辑,所以子控件的布局方式和FrameLayout相同即可,我们只需要关注HeaderLayout新增的几个属性。这里以效果图为例。

  <androidx.coordinatorlayout.widget.CoordinatorLayout          ...>

      <com.zzx.headerlayout_kotlin.HeaderLayout              android:layout_width="match_parent"              android:layout_height="wrap_content"              //新增属性              app:extend_height="30%">

          <androidx.appcompat.widget.AppCompatImageView                  android:layout_width="match_parent"                  android:layout_height="300dp"                  android:src="@drawable/singer"                  android:scaleType="centerCrop"                  //新增属性                  app:transformation="scroll|extend_scale"                  />

          ...

      </com.zzx.headerlayout_kotlin.HeaderLayout>

      <androidx.viewpager.widget.ViewPager              android:id="@+id/viewPager"              android:layout_width="match_parent"              android:layout_height="match_parent"              //配置依赖布局的layout_behavior              app:layout_behavior="@string/header_layout_scrolling_view_behavior"/>

  </androidx.coordinatorlayout.widget.CoordinatorLayout>

  如上所示,HeaderLayout工作在CoordinatorLayout中并且是其直接子View。ViewPager由于需要根据HeaderLayout的滑动做出界面的调整,所以需要配置layout_behavior,并且其值为@string/header_layout_scrolling_view_behavior,这里和AppBarLayout的使用方式一致。我们的工作重点是头部控件的联动效果,因此咱们聚焦于HeaderLayout和其子View。我们看AppCompatImageView,它用来展示效果图中的歌手。仔细分析效果图中AppCompatImageView的变换方式,可以发现它是根据父控件HeaderLayout的滑动而做出的相应的变化效果,HeaderLayout向上滑动,其跟随向上,HeaderLayout向下滑动,则跟着向下。并且,在HeaderLayout滑动到底部继续向下拓展时,AppCompatImageView做了一个收缩的变换。这一切的一切都需要归功于app:transformation属性,可以在代码中看见其值为"scroll|extend_scale",那么其含义是什么呢?对此,我们引出了一个概念----Transformation,它是一个接口,其意在为根据HeaderLayout的滑动及状态而做出相应的变化行为。在介绍Transformation之前,有必要介绍一下HeaderLayout滑动中的几种状态。

HeadeerLayout状态图

  HeaderLayout的滑动实际上是HeaderLayout高度的动态变化,所以需要了解图中三种高度的含义。maxHeight是HeaderLayout第一次加载测量后的高度,minHeight是设置了app:sticky_until_exit="true"属性的子View的高度之和,此属性表示子View不随着HeaderLayout而滑出屏幕,形成一种粘连在屏幕顶部的效果,且子View是按照顺序排列的。extendHeight则是拓展的高度,展示在效果图中就是图片收缩scale时下滑的高度,extendHeight可以在xml中为HeaderLayout设置,其值可以为dimension,百分数,或者float比例,百分数和float比例是按照maxHeight而计算的。
  而图中五种状态用来表示HeaderLayout高度变化过程中用来表示滑动状态的,Transformation就是根据应这五种状态而生,Transformation作用于HeaderLayout的直接子View或者间接子View(间接子View需要自己进行处理,可以参考CommonToolbarTransformation),一个子View可以同时拥有多个Transformation,HeaderLayout在其状态变化时,则会遍历子View的所有Transformation,通知其做出改变。
  XML中作用于AppCompatImageView的app:transformation="scroll|extend_scale"属性,scroll 和 extend_scale则是内置的两种Transformation,如下表所示。

属性 说明 作用对象
transformation
scroll 随着HeaderLayout滑动而滑动 HeaderLayout直接子View
alpha STATE_MIN_HEIGHT到STATE_MAX_HEIGHT对应alpha为0->1 HeaderLayout直接子View
alpha_contray 与alpha相反,STATE_HEIGHT到STATE_MAX_HEIGHT对应alpha为1->0 HeaderLayout直接子View
extendScale 在STATE_MAX_HEIGHT到STATE_EXTEND_MAX_END之间做scale变换 HeaderLayout直接子View
common_toolbar 专为Toolbar设计,在STATE_MIN_HEIGHT时显示Title和Subtitle,否则隐藏,此属性必须设置给HeaderLayout的直接子View,但是Toolbar不需要为其直接子View HeaderLayout直接子View
sticky_until_exit true|false 子View不随HeaderLayout而滑出屏幕,粘连在顶部 HeaderLayout直接子View
custom_transformation @string 自定义Transformation的全路径 HeaderLayout直接子View
extend_height n(dp)|n%|0.n 设置HeaderLayout的extendHeight,可以是dimension、百分比数或者小数比例 HeaderLayout

transformation表示内置的几中Transformation,但是想要自定义Transformation应该如何做呢?

自定义Transformation


  custom_transformation属性则是专为自定义Transformation而服务,其值为自己实现的Transformation类的全路径。自定义Transformation有两种方式,其一是实现Transformation接口,另一种方式是继承TransformationAdapter类,TransformationAdapter是Transformation是Transformation接口的空实现,继承于此则不需要实现所有的方法。

interface Transformation<in V: View> {    /**     * @see [HeaderLayout.scrollState]为STATE_MIN_HEIGHT, 这个方法回调表示[HeaderLayout]的Bottom已经收缩到了最小高度     * @param child 当前需要做变换的view     * @param parent [HeaderLayout]     * @param unConsumedDy 由其他状态到此状态未消耗完的dy     */    fun onStateMinHeight(child: V, parent: HeaderLayout, unConsumedDy: Int)

    /**     * @see [HeaderLayout.scrollState]为STATE_NORMAL_PROCESS, 在STATE_MIN_HEIGHT和STATE_MAX_HEIGHT之间     * 这个方法回调表示[HeaderLayout]的Bottom正在最小高度与最大高度之间     * @param child 当前需要做变换的view     * @param parent [HeaderLayout]     * @param percent 0<percent<1, 值为([HeaderLayout.getBottom] - [HeaderLayout.minHeight]) / ([HeaderLayout.maxHeight] - [HeaderLayout.minHeight]])     * 且值不会为0或者1, 为0相当于是回调了[onStateMinHeight], 为1相当于回调了[onStateMaxHeight], 由于值不会为0或1,     * 所以在回调[onStateMinHeight]和[onStateMaxHeight]时会有一个未消耗的dy     * @param dy 滑动的距离     */    fun onStateNormalProcess(child: V, parent: HeaderLayout, percent: Float, dy: Int)

    /**     * @see [HeaderLayout.scrollState]为STATE_MAX_HEIGHT     * 这个方法回调表示[HeaderLayout]的Bottom正处于[HeaderLayout.maxHeight]     * @param child 当前需要做变换的view     * @param parent [HeaderLayout]     * @param unConsumedDy 由其他状态到此状态未消耗完的dy     */    fun onStateMaxHeight(child: V, parent: HeaderLayout, unConsumedDy: Int)

    /**     * @see [HeaderLayout.scrollState]为STATE_EXTEND_PROCESS, 在STATE_MAX_HEIGHT和STATE_EXTEND_MAX_END之间     * 这个方法回调表示[HeaderLayout]的Bottom正处于[HeaderLayout.maxHeight] 和 [HeaderLayout.maxHeight] + [HeaderLayout.extendHeight]之间     * @param child 当前需要做变换的view     * @param parent [HeaderLayout]     * @param percent 0<percent<1, 值为([HeaderLayout.getBottom] - [HeaderLayout.maxHeight]) / [HeaderLayout.extendHeight]     * 且值不会为0或者1, 为0相当于是回调了[onStateMaxHeight], 为1相当于回调了[onStateExtendMaxEnd], 由于值不会为0或1,     * 所以在回调[onStateMaxHeight]和[onStateExtendMaxEnd]时会有一个未消耗的dy     */    fun onStateExtendProcess(child: V, parent: HeaderLayout, percent: Float, dy: Int)

    /**     * @see [HeaderLayout.scrollState]为STATE_EXTEND_MAX_END,     * 这个方法回调表示[HeaderLayout]的Bottom正处于[HeaderLayout.maxHeight] + [HeaderLayout.extendHeight]     * @param child 当前需要做变换的view     * @param parent [HeaderLayout]     * @param unConsumedDy 由其他状态到此状态未消耗完的dy     *     */    fun onStateExtendMaxEnd(child: V, parent: HeaderLayout, unConsumedDy: Int)}

  HeaderLayout在状态变化的时候会遍历子View的所有Transformation,也即是会回调Transformation接口中的这几个方法,使用者可以根据这几个方法的含义来变换子View。

Tips


  
  开发者在使用app:transformation和app:sticky_untila_exit等属性时,最好用AppCompatImageView代替ImageView,AppCompatTextView代替TextView,这样在XML文件中则不会因为系统控件无法使用自定义属性而报红线,即使报红线也不会影响程序正常的执行,只是看着别扭。

总结


  HeaderLayout是根据参照网易云音乐的效果而实现的,但又跳出了“实现”的限制,提取出来了一个公共而又与业务无关的控件,其思想则是学习了CoordinatorLayout的behavior和ViewGroup事件的分发思想,将HeaderLayout的滑动状态分发给其子View,从而产生联动效果。
  最后放上Github的地址把:https://github.com/imurluck/HeaderLayout

推荐阅读
身在大厂,心在小厂
Android多线程选型指南
ConstraintLayout,看完一篇就够了吗?

编程·思维·职场
欢迎扫码关注

XML中配置网易云歌手详情滑动效果相关推荐

  1. 利用python爬取网易云歌手top50歌曲歌词

    python近年来,发展迅速,成为了最炙手可热的语言. 那么如何来进行网易云歌手top50的歌曲歌词爬取呢 1. 首先进行网易云并进行喜欢的歌手搜索如下: 在这里需要注意的是http://music. ...

  2. python3 爬取网易云歌曲详情

    上一篇介绍了爬网易云歌手id, 在这里我们可以用获取的id数据来构造歌手详情页的url.在这里呢我还是比较习惯使用selenium来爬. 简单介绍一下selenium: 它是浏览器的一个自动化测试框架 ...

  3. Tomcat在server.xml中配置虚拟目录

    首先,在D盘中新建文件chapter02,然后,在chapter02目录下,新建文件welcome.xml 此时,直接访问是访问不到的 启动Tomcat服务器,在浏览器地址栏中输入 http://lo ...

  4. web.xml中配置web监听器

    web.xml中配置web监听器 在web.xml配置监听器,格式如下: <listener><listener-class>类全名</listener-class> ...

  5. 深入理解web.xml中配置/和/*的区别

    在用SpringMVC进行web开发的时候,如果将DispathcerServlet对外访问的虚拟路径配置成/时,需要在Spring的配置文件中配置<mvc:default-servlet-ha ...

  6. 在配置文件web.xml中配置Struts2的启动信息

    在配置文件web.xml中配置Struts2的启动信息: <?xml version="1.0" encoding="UTF-8"?> <we ...

  7. CentOS7中安装网易云音乐

    CentOS 7中安装网易云音乐 中一直没有一个像样的音乐播放器,网易云音乐与深度科技团队在半年前就启动了"网易云音乐7版",但是只提供了Ubuntu(14.04&16.0 ...

  8. 如何在网页中嵌入网易云音乐

    1.用 iframe 标签实现 第一种:无播放列表 1 <iframeframeborder="0"border="1" 2 marginwidth=&q ...

  9. SpringMVC在web.xml中配置DispatcherServlet拦截了静态资源访问

    如图 在web.xml中配置DispatcherServlet时对于url-pattern的配置方式有以下几种情况: 1.配置为: *.do 或者是 *.action 时,拦截以.do或者.actio ...

最新文章

  1. 马斯克矩阵模拟错了?这个试验证明人类不是「缸中之脑」
  2. 关于sigma pix的理解
  3. react-native侧滑
  4. POJ1466 最大点权独立集
  5. 请求接口返回的是一个html_搜狗美图 API 接口请求调用
  6. Spark 简介与安装部署
  7. config kubectl_Kubernetes(k8s)中文文档 kubectl config set-context_Kubernetes中文社区
  8. 高性能 高可用 可弹性伸缩_性能,可伸缩性和活力
  9. linux 改目录前缀,Linux修改终端显示前缀及环境变量
  10. pycharm中python版本_在 Pycharm(2019,.3)里配置 Anaconda3 的 Python 版本
  11. 给你1分钟,回答下RabbitMQ如何保证消息不丢?
  12. 森海塞尔Momentum 2无线蓝牙耳机发布:首次加入主动降噪
  13. android spi串口调试,PIC入门3,SPI通信和串口调试实验
  14. java 注释工具栏_eclipse/intellij idea 查看java源码和注释方法
  15. OpenJDK8 JAVA应用窗口在不同缩放比例下的表现(Linux)
  16. IIC,RS485,RS232各种协议手册更新中
  17. 小镇青年程序员的逆袭人生:从差点回老家到荔枝技术骨干
  18. 使用kali系统中legion工具包进行漏扫时闪退,原因是legion工具包版本问题,使用命令sudo apt-get install legion更新一下就解决了
  19. Barsetto百胜图TripressoES意式便携咖啡机测评,咖啡随行玩味无穷
  20. 如何让木马克星能在win2003上免费使用

热门文章

  1. 4412开发板UT-Exynos4412三星A9四核4412开发平台调试android4.0GPS功能信号超强
  2. Android保存的图像在Windows照片查看器打开提示“可能内存不足”
  3. 通过Cloudreve+云服务器快速搭建一个私人云盘
  4. PHP基础篇 php接口interface到底有什么用?
  5. Android组件化实战五: APT的高级用法JavaPoet
  6. 强制重启计算机快捷键,强制重启电脑快捷键
  7. linux启动和关闭防火墙命令
  8. 基于jsp(java)超市管理系统的设计和开发(含源文件)
  9. 汽车之家的后台怎么统计对比记录的,怎么做数据分析?
  10. 如何确定IP在同一网段