Android仿小米时钟嵌套滑动(NestedScroll, 自定义behavior)

最近无聊刷手机的时候, 发现小米时钟的嵌套滑动很有意思, 就试着做了下
先上对比图:

分析下小米时钟的滑动
  • 闹钟列表 向上滑动时, 时钟面 透明度上升, 快到最大滑动时逐渐显示数字时钟.

在闹钟列表向上滑动的过程中,时钟面和数字时钟的位置看起来并没有发生变化, 而且时钟面能分发到滑动信息
如果尝试用 AppBarLayout + appbar_scrolling_view_behavior , 会出现滑动信息被消耗的情况, 尝试滑动 AppBarLayout 的子view会使整体滑动,
像这样:

自定义behavior

如果用 AppBarLayout 不方便, 那我们就自己来, 先上布局:

<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"
    tools:context="com.jimzjy.demo.MainActivity">

    <com.jimzjy.networkspeedview.NetworkSpeedView
        android:id="@+id/main_network_speed_view"
        android:layout_width="match_parent"
        android:layout_height="@dimen/top_size"
        android:background="@color/colorPrimary"
        app:speed_unit="Auto_same"/>

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <View
            android:id="@+id/main_scrim_view"
            android:layout_width="match_parent"
            android:layout_height="@dimen/top_size"
            android:background="@color/colorPrimary"
            android:alpha="0"
            app:layout_behavior=".DeviceRecyclerScrollerBehavior"/>
        <android.support.v7.widget.RecyclerView
            android:id="@+id/main_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/white"
            android:layout_marginTop="@dimen/top_size"
            app:layout_behavior=".MeasureRecyclerBehavior"
            tools:listitem="@layout/rv_item"
            tools:itemCount="7"/>
    </android.support.design.widget.CoordinatorLayout>
</FrameLayout>
复制代码

Framelayout中分了两个层次, 底层 NetworkSpeedView ,再 CoordinatorLayout . CoordinatorLayout 中用一个 View 遮住
底层的 NetworkSpeedView , 一开始让 View 的 alpha 值为0, 在 CoordinatorLayout 向上滑动的过程中, 提高 alpha 值, 这样就达到了近似 NetworkSpeedView 透明度改变的效果.
为了方便看, 顶部的 text 现在还不添加

关于嵌套滑动和自定义Behavior可以看
一点见解: Android嵌套滑动和NestedScrollView
CoordinatorLayout高级用法-自定义Behavior

Behavior :

class DeviceRecyclerScrollerBehavior(ctx: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior<View>(ctx, attrs) {
    //顶部文字的高度
    private val mTopText = ctx.resources.getDimension(R.dimen.top_text_size)
    //总共需要上移的高度
    private val mTotalOffsetY = ctx.resources.getDimension(R.dimen.top_size) - mTopText

    override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: View, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
        return (axes and ViewCompat.SCROLL_AXIS_VERTICAL) != 0
    }

    override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: View, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
        /*
        判断是否要消耗 dy
        dy > 0 上移, dy < 0 下移
        向上,如果已经达到向上最大滑动/向下,内部的 RecyclerView 还可以下滑 . 则不消耗
         */
        val toCost = (dy > 0 && coordinatorLayout.scrollY < mTotalOffsetY)
                || (dy < 0 && coordinatorLayout.scrollY > 0 && !target.canScrollVertically(-1))

        if (toCost) {
            coordinatorLayout.scrollBy(0, dy)
            //改变透明度
            child.alpha = coordinatorLayout.scrollY / (mTotalOffsetY - mTopText)
            consumed[1] = dy
        }
    }
}

class MeasureRecyclerBehavior(ctx: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior<RecyclerView>(ctx, attrs) {
    private val mTopText = ctx.resources.getDimension(R.dimen.top_text_size)

    override fun onMeasureChild(parent: CoordinatorLayout?, child: RecyclerView?, parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int, heightUsed: Int): Boolean {
        //测量大小, 让 RecyclerView 的高度 = CoordinatorLayout的高度 - 顶部text的高度
        child?.measure(parentWidthMeasureSpec, View.MeasureSpec.makeMeasureSpec(
                View.MeasureSpec.getSize(parentHeightMeasureSpec) - mTopText.toInt(), View.MeasureSpec.EXACTLY))
        return true
    }
}
复制代码

通过 MeasureRecyclerBehavior 测量 RecyclerView 的高度(设定准确值), 而不是根据父布局, 就不会出现向上滑动时感觉底下少一块的情况,像这样:

DeviceRecyclerScrollerBehavior 会设置消耗滑动距离, 以及修改透明度

处理顶部的 TextView

因为顶部的 TextView 不随着 CoordinatorLayout 的滑动而滑动, 所以把顶部的 TextView 放到 FrameLayout(直接在 Coordinator上面) :

<FrameLayout xmlns:android>
    <com.jimzjy.networkspeedview.NetworkSpeedView/>
    <CoordinatorLayout/>
    <TextView
        android:id="@+id/main_top_text"
        android:layout_width="match_parent"
        android:layout_height="@dimen/top_text_size"
        android:gravity="center_vertical|center_horizontal"
        android:text="100 | 100"
        android:textSize="22sp"
        android:textColor="@android:color/white"
        android:alpha="0"/>
</FrameLayout>
复制代码

因为 TextView 不是 CoordinatorLayout 的子 View , 所以就不能通过 behavior 来设置它的 alpha 了.
那就通过设置监听, 每次滑动都会调用 CoordinatorLayout 的 scrollTo() ( scrollBy() 内部调用的也是scrollTo() ), 所以继承 CoordinatorLayout , 覆写 scrollTo() , 写个回调监听就好了.

class ReCoordinatorLayout(ctx: Context, attrs: AttributeSet?) : CoordinatorLayout(ctx, attrs) {
    private val mTopText = ctx.resources.getDimension(R.dimen.top_text_size)
    private val mTop = ctx.resources.getDimension(R.dimen.top_size)
    private val mTopOffsetY = mTop - mTopText
    private val mAlphaChangeStart = mTopOffsetY - mTopText
    private var mShowText: ((alpha: Float) -> Unit)? = null

    constructor(ctx: Context) : this(ctx, null)

    override fun scrollTo(x: Int, y: Int) {
        var sY = y
        //防止滑动过多
        when {
            sY < 0 -> sY = 0
            sY > mTopOffsetY -> sY = mTopOffsetY.toInt()
        }
        if (sY != scrollY) super.scrollTo(x, sY)
        when {
            sY > mAlphaChangeStart -> mShowText?.invoke((sY - mAlphaChangeStart) / mTopText)
            sY <= mAlphaChangeStart -> mShowText?.invoke(0f)
        }
    }

    //设置回调函数
    fun setShowtext(showText: (alpha: Float) -> Unit) {
        this.mShowText = showText
    }
}
复制代码

在 scrollTo 中调用回调函数 mShowText() 来设置 alpha 值.

设置回调函数
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ... ...
        main_coordinator_layout.setShowtext { main_top_text.alpha = it }
    }
复制代码

总结

GitHub地址
总的来说实现的还是比较粗糙的, 但是这样非常简单

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值