滑动 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














