内容简介:最近去小米之家体验了下小米9,发现MIUI有一个挺特别的列表动画效果,在系统上的各种应用上都能见到它的身影。方法,计算手指滑动距离来缩放内部控件。这种方式适合对View触摸分发机制比较熟悉的同学,代码比较复杂,看了下现有的库也都没能实现MIUI中Fling状态的弹性效果。正好最近看了下
最近去小米之家体验了下小米9,发现MIUI有一个挺特别的列表动画效果,在系统上的各种应用上都能见到它的身影。
网上查了下,小米早在几个系统版本前就有这个,网上也有了实现这个效果的控件库。实现方法大同小异,大多都是通过继承ScrollView
,然后重写
onInterceptTouchEvent
方法和
OnTouchEvent
方法,计算手指滑动距离来缩放内部控件。
这种方式适合对View触摸分发机制比较熟悉的同学,代码比较复杂,看了下现有的库也都没能实现MIUI中Fling状态的弹性效果。正好最近看了下 NestedScrolling
的相关知识,发现能很好地实现这些效果,所以就让我们来看看吧。
预备知识
需要先了解下 NestedScrollChild
和 NestedScrollParent
,所谓的NestedScrolling机制是这样的:内部NestedScrollingChild在滚动的时候,预先将dx,dy通过 NestedScrollingChildHelper
传递给 NestedScrollingParent
, NestedScrollingParent
可先对其进行部分消耗,Parent处理完后,再将剩余的部分还给内部 NestedScrollingChild
处理,最后再把剩下的dx,dy再给Parent做最后处理,这样一次触摸滑动事件将可以由多个控件共同消耗处理,这样就可以很方便解决之前一次触摸滑动事件只能被一个控件响应而产生的嵌套滑动问题。
先看下 NestedScrollParent :
public interface NestedScrollingParent { public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes); public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes); public void onStopNestedScroll(View target); public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed); public void onNestedPreScroll(View target, int dx, int dy, int[] consumed); public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed); public boolean onNestedPreFling(View target, float velocityX, float velocityY); public int getNestedScrollAxes(); } 复制代码
先看下 NestedScrollingChild
public interface NestedScrollingChild { void setNestedScrollingEnabled(boolean enabled); boolean isNestedScrollingEnabled(); boolean startNestedScroll(int axes); void stopNestedScroll(); boolean hasNestedScrollingParent(); boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow); boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow); boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed); boolean dispatchNestedPreFling(float velocityX, float velocityY); } 复制代码
可以看到parent和child的api命名很类似,是成对出现的,确实,它们之前存在发起方和接收方的事件调用关系,都是由child先响应滑动触摸实现,通过 NestedScrollingChildHelper
分发给parent。
弹性列表实现
为方便解析,我们先只实现下滑的弹性动画:
//子view,需事先NestedScrollingChild private var childView: View? = null private val mNestedScrollingParentHelper: NestedScrollingParentHelper private var offsetScale = 0f private var flingScale = 0f private var consumedDy = 0 set(value) { field = if (value > 0) { 0 } else { value } } //是否是Fling滑动 private var filing = false //判定滑动的最小距离 private var touchSlop: Int = 0 private var animator: ValueAnimator? = null init { mNestedScrollingParentHelper = NestedScrollingParentHelper(this) touchSlop = ViewConfiguration.get(context).scaledTouchSlop } override fun onFinishInflate() { super.onFinishInflate() childView = getChildAt(0) } /** * 滚动开始 */ override fun onStartNestedScroll(child: View, target: View, nestedScrollAxes: Int): Boolean { filing = false consumedDy = 0 return child === childView && ViewCompat.SCROLL_AXIS_VERTICAL == nestedScrollAxes } /** * 先于child滚动 */ override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) { if (childView!!.scrollY == 0 && (dy < 0 || consumedDy < 0)) { consumedDy += dy if (Math.abs(consumedDy) > touchSlop) { //计算缩放值,最大放大1.3倍 offsetScale = (1.3 - 600f / (2000 + Math.pow(Math.abs(consumedDy).toDouble(), 2.0))).toFloat() startBouncingTop() //存放消耗的距离,child会接收 consumed[1] = dy } } } override fun onNestedScrollAccepted(child: View, target: View, nestedScrollAxes: Int) { mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes) } /** * 先于child处理Fling */ override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean { if (velocityY < 0 && childView!!.scrollY == 0) { filing = true consumedDy = (consumedDy + velocityY).toInt() flingScale = (0.3 - 600f / (2000 + Math.pow(Math.abs(consumedDy).toDouble(), 2.0))).toFloat() return true } return false } override fun onNestedFling(target: View, velocityX: Float, velocityY: Float, consumed: Boolean): Boolean { return filing } override fun onStopNestedScroll(target: View) { mNestedScrollingParentHelper.onStopNestedScroll(target) backBouncing(filing) } override fun getNestedScrollAxes(): Int { return mNestedScrollingParentHelper.nestedScrollAxes } /** * 进行回弹 */ private fun backBouncing(filing: Boolean) { //初始化 if (animator != null && animator!!.isRunning) { animator!!.cancel() animator = null } if (filing) { animator = ValueAnimator.ofFloat(offsetScale, flingScale, 0f) animator!!.duration = 400 } else { animator = ValueAnimator.ofFloat(offsetScale, 0f) animator!!.duration = 250 } animator!!.interpolator = OvershootInterpolator() animator!!.addUpdateListener { offsetScale = it.animatedValue as Float startBouncingTop() } animator!!.start() } /** * 从顶部开始滑动 */ private fun startBouncingTop() { childView!!.pivotY = 0f childView!!.pivotX = 0f childView!!.scaleY = offsetScale } 复制代码
弹性效果
Fling弹性效果以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。