android 部分布局滚动,[Android] 写了个视差滚动布局 ParallaxLayout

新项目用到了大量的视差滚动效果,今天写了控件做支持,并非成熟但适用大多数简单场景,把大致思路在这里写下。

什么是视差滚动, 简单说就是

不同组件按不同速度滚动

像这种效果:6f4a0cb30ab0d894636cf9e78144415d.gif

我们今天介绍的主要是以纵向的滚动效果为主,当然如果你要实现横向思路也一样。

1. 思路

简单实现这个效果思路很简单,只需要监听滚动控件的滚动行为,再根据不同组件的速度要求调整偏移量就🉑️了。

要做的通用点,我的思路是实现一个视差滚动的父控件,子view添加一个layout_parallax_speed的属性,这样无需多写java代码,就可以得到所需要的参数,在布局代码中就可以完成定义。

最后的调用类似这种效果:

复制代码

其实这里要注意,我们的布局是介于 ScrollView 和视差组件之间的一个布局,因此它可能是 LinearLayout 可能是 RelativeLayout 也可能是 ConstraintLayout, 可能是任意一个ViewGroup 的子类,因为我也不知道开发者需要一个什么样的内部结构。

再一点,这个布局本身不具备 Scroll 能力,仍需要嵌套在外部的可滚动组件。

可能你有疑问,为什么不直接写一个ParallaxScrollView之类的呢。

这样就不需要担心内部结构的问题,不需要写多种布局的子类,自身也能控制滚动行为。

也就是类似下面这种结构的:

复制代码

原因也很简单,

Android的ViewGroup没有跨越父子关系设置LayoutParam的能力(只能父子间,不能爷孙间),也就是说,这种结构上,最外侧的ParallaxScrollView不能以正常方式获取到最内层的 layout_parallax_speed 属性。

2. 实现

需要解决两个问题

定义并获取各子控件的速度属性 layout_parallax_speed

检测滚动,对子控件做相对移动

2.2 定义并获取子控件布局属性

这里先介绍一下自定义ViewGroup,定义和获取子View布局参数的流程。

我们知道xml中定义的这些属性都会转换成AttributeSet, 然后在代码中保存在 LayoutParam 里。

然后会在addView方法时, 然后把它和view联系在一起。

ViewGroup 添加 View 流程图:99baab91ceef767548997bab147e19bc.png

因此我们要做的三件事是:

定义一个layout_parallax_speed属性

定义一个LayoutParam,添加一个成员变量parallaxSpeed用来保存 1 中定义的值

默认为子View 生成我们定义的LayoutParams

相对的,我们的流程图应该是:1

定义一个layout_parallax_speed属性

复制代码定义一个LayoutParam,解析 layout_parallax_speed 值并保存

class LayoutParams : RelativeLayout.LayoutParams {

var parallaxSpeed: Float = 1f

constructor(context: Context, attrs: AttributeSet?): super(context, attrs) {

val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.ParallaxLayout)

parallaxSpeed = a.getFloat(R.styleable.ParallaxLayout_layout_parallax_speed, 1f)

a.recycle()

}

constructor(width: Int, height: Int): super(width, height)

constructor(layoutParams: MarginLayoutParams): super(layoutParams)

constructor(layoutParams: LayoutParams): super(layoutParams)

}

复制代码重写generateLayoutParams, 默认为子 View 生成我们定义的LayoutParams

override fun generateLayoutParams(attrs: AttributeSet?): RelativeLayout.LayoutParams {

return LayoutParams(context, attrs)

}

复制代码

2.2 检测滚动

在添加到窗口时,对可滚动的父布局添加addOnScrollChangedListener方法

// TODO 添加关键代码

override fun onAttachedToWindow(){

super.onAttachedToWindow()

if (parent != null && parent is ScrollView) {

(parent as ViewGroup).viewTreeObserver.addOnScrollChangedListener(this)

}

}

override fun onDetachedFromWindow(){

super.onDetachedFromWindow()

if (parent != null && parent is ScrollView) {

(parent as ScrollView).viewTreeObserver.removeOnScrollChangedListener(this)

}

}

复制代码

如果你的最低API在24之上,那你就不需要用 viewTreeObserver 这么暴力的东西,可以直接parent.addOnScrollChangedListener

在onScrollChanged事件中获取偏移量,计算各子View的相对偏移值

/**

* parent 的 onScrollChanged 事件

* 父亲滚动改变时,根据每个元素的滚动速度进行调整 view.translationY

*/

private var parentLastScrollY = 0

override fun onScrollChanged() {

val currentScrollY = (parent as ViewGroup).scrollY

val delta = currentScrollY - parentLastScrollY

for (child in parallaxChildren) {

val translationDelta = -delta * (child.speed - 1f)

child.view.translationY += translationDelta.toInt()

}

parentLastScrollY = currentScrollY

}

复制代码

3. 总结

最后再贴一遍整体流程图:

** 开头的部分是我们要做的工作

1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值