滑动 Scrollview 时动态改变 Toolbar 透明度,模仿京东详情页效果

前段时间逛JD时看到详情页滑动时 Toolbar 改变透明度的效果感觉很不错,想着自己实现一下,于是就有了这篇文章。
效果
由于GIF图过大,效果通过Demo地址查看
思路
由于滑动的是一个 Scrollview,自然而然想到通过监听其滑动,通过计算Y轴滑动的距离与触发透明效果区域的距离,得到其滑动的比例,进而求得此时的透明度
实现
首先定义一个接口,用来回调透明度的变化
interface OnAlphaChangeListener {
/**
* 透明度 [alpha]
*/
fun onAlpha(@IntRange(from = 0, to = 255) alpha: Int)
}
然后自定义 Scrollview,这里继承自 NestedScrollView,使其有更好的兼容性
class DynamicAlphaScrollView @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : NestedScrollView(context, attributeSet, defStyleAttr) {
......
}
既然是监听 ScrollView 的滑动距离,就要重写其 onScrollChanged 方法
override fun onScrollChanged(x: Int, y: Int, oldX: Int, oldY: Int) {
super.onScrollChanged(x, y, oldX, oldY)
}
接下来就是计算不同滑动距离下的透明度,代码如下
/**
* 处理透明度变化
*
* @param [scrollY] Y轴滑动距离
*/
private fun handleAlphaChange(scrollY: Int) {
onAlphaChangeListener?.let {
// 屏幕高度
val heightPixels = resources.displayMetrics.heightPixels
// 取屏幕高度1/3当作顶部高度(模糊值)
val topHeight = heightPixels / 3f
// 根据滑动距离计算透明度
val alpha = when {
// 滑动距离为0,透明度 0
scrollY <= 0 -> 0
// 滑动距离处于顶部高度所在范围,计算透明度
scrollY in 1 until topHeight.toInt() -> calculateAlpha(scrollY, topHeight)
// 滑动距离超过顶部高度,透明度 255
else -> 255
}
// 回调透明度
it.onAlpha(alpha)
}
}
这里我们取屏幕高度的1/3作为触发透明度变化的区域,主要有3种情况
-
当滑动距离为0时,透明度为0,也就是完全透明状态
-
当滑动距离超过触发透明度变化的区域时,透明度为255,也就是完全不透明状态
-
当滑动距离处于触发透明度变化的区域内时,就要根据滑动距离计算出透明度,公式如下
/**
* 计算透明度
*
* @param [scrollY] Y轴滑动距离
* @param [topHeight] 顶部高度
*/
private fun calculateAlpha(scrollY: Int, topHeight: Float): Int {
return (scrollY / topHeight * 255).toInt()
}
代码很简单,就一句话,滑动的距离除以触发透明度变化的区域在乘以255,就是此时的透明度了
比如,滑动了200px,触发透明度变化的区域高度为300px,那么此时的透明度为:
200 / 300 * 255 = 170
通过上面的计算,我们知道了不同滑动距离下透明度的变化值,那么现在就要将它们应用起来
首先看一下布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<top.i97.scrollviewtoolbartransparentdemo.DynamicAlphaScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop"
android:src="@drawable/intellijidea" />
......
</LinearLayout>
</top.i97.scrollviewtoolbartransparentdemo.DynamicAlphaScrollView>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@color/colorPrimary"
app:title="Gallery"
app:titleTextColor="@android:color/white" />
</FrameLayout>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/colorAccent"
android:elevation="5dp"
android:text="Action"
android:textAllCaps="false" />
</LinearLayout>
布局非常简单,上面一个 Toolbar,下面一个 Scrollview 包裹了一些图片
下面就是 Activity 中的实现
首先实现 OnAlphaChangeListener 接口,重写 onAlpha 方法来接收透明度变化的值
class MainActivity : AppCompatActivity(), OnAlphaChangeListener {
......
override fun onAlpha(alpha: Int) {
}
}
然后给刚才自定义的 Scrollview 设置监听
scrollView.onAlphaChangeListener = this
接着在 onAlpha 方法中改变 Toolbar 背景的透明度
override fun onAlpha(alpha: Int) {
// 动态改变 Toolbar 背景透明度
toolbar.background.mutate().alpha = alpha
}
最后就是沉浸式状态栏,由于不是本文章的重点,这里借助第三方库 Immersionbar 实现
private fun init() {
scrollView.onAlphaChangeListener = this
toolbar.apply {
// 初始透明度为0
background.mutate().alpha = 0
// 沉浸状态栏
ImmersionBar.with(this@MainActivity).titleBar(this).init()
}
}
❤️ done