Android之ArcSlidingHelper制作圆弧滑动效果
作者:陈小缘 时间:2021-07-23 03:10:24
前言
我们平时在开发中,难免会遇到一些比较特殊的需求,就比如我们这篇文章的主题,一个关于圆弧滑动的,一般是比较少见的。其实在遇到这些东西时,不要怕,一步步分析他实现原理,问题便能迎刃而解。
前几天一位群友发了一张图,问类似这种要怎么实现:
要支持手势旋转
旋转后惯性滚动
滚动后自动选中
哈哈, 来一张自己实现的效果图:
初步分析
首先我们看下设计图,Item绕着一个半圆旋转,如果我们是自定义ViewGroup的话,那么在onLayout之后,就要把这些Item按一定的角度旋转了。如果直接继承View,这个比较方便,可以直接用Canvas的rotate方法。不过如果继承View的话,做起来是简单,也能满足上面的需求,但局限性就比较大了: 只能draw,而且Item内容不宜过多。所以这次我们打算自定义ViewGroup,它的好处呢就是:什么都能放,我不管你Item里面是什么,反正我就负责显示。惯性滚动的话,这个很容易,我们可以用Scroller配合VelocityTracker来完成。旋转手势,无非就是计算手指滑动的角度。
选择旋转方案
说起View的动画播放,大家肯定都是轻车熟路了,如果一个View,它有监听点击事件,那么在播放位移动画后,监听的位置按道理,也应该在它最新的位置上(即位移后的位置),在这种情况下我们用View的startAnimation就不奏效了:
TranslateAnimation translateAnimation = new TranslateAnimation(0, 150, 0, 300);
translateAnimation.setDuration(500);
translateAnimation.setFillAfter(true);
mView.startAnimation(translateAnimation);
可以看到,在View位移之后,监听点击事件的区域还是在原来的地方。我们再看下用属性动画的:
mView.animate().translationX(150).translationY(300).setDuration(500).start();
监听点击事件的区域随着View的移动而更新了。嘻嘻,我们通过实践来验证了这个说法。
那么我们做的这个是要支持触摸事件的,肯定是使用第二种方法。 ViewPropertyAnimator的源码分析相信大家之前也都已经看过其他大佬们的文章了,这里就只讲讲关键代码: ViewPropertyAnimator它不是ValueAnimator的子类,哈哈,这个有点意外吧,我们直接看startAnimation方法(这个方法是start()里面调用的):
private void startAnimation() {
...
//可以看到这里创建了ValueAnimator对象
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
...
animator.addUpdateListener(mAnimatorEventListener);
...
animator.start();
}
中间那里addUpdateListener(mAnimatorEventListener),我们来看看这个listener里面做了什么:
@Override
public void onAnimationUpdate(ValueAnimator animation) {
...
...
ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
if (valueList != null) {
int count = valueList.size();
for (int i = 0; i < count; ++i) {
NameValuesHolder values = valueList.get(i);
float value = values.mFromValue + fraction * values.mDeltaValue;
if (values.mNameConstant == ALPHA) {
alphaHandled = mView.setAlphaNoInvalidation(value);
} else {
setValue(values.mNameConstant, value);
}
}
}
...
...
}
else里面调用了setValue方法,我们再继续跟下去 (哈哈,感觉好像捉贼一样):
private void setValue(int propertyConstant, float value) {
final View.TransformationInfo info = mView.mTransformationInfo;
final RenderNode renderNode = mView.mRenderNode;
switch (propertyConstant) {
case TRANSLATION_X:
renderNode.setTranslationX(value);
break;
case TRANSLATION_Y:
renderNode.setTranslationY(value);
break;
case TRANSLATION_Z:
renderNode.setTranslationZ(value);
break;
case ROTATION:
renderNode.setRotation(value);
break;
case ROTATION_X:
renderNode.setRotationX(value);
break;
case ROTATION_Y:
renderNode.setRotationY(value);
break;
case SCALE_X:
renderNode.setScaleX(value);
break;
case SCALE_Y:
renderNode.setScaleY(value);
break;
case X:
renderNode.setTranslationX(value - mView.mLeft);
break;
case Y:
renderNode.setTranslationY(value - mView.mTop);
break;
case Z:
renderNode.setTranslationZ(value - renderNode.getElevation());
break;
case ALPHA:
info.mAlpha = value;
renderNode.setAlpha(value);
break;
}
}
我们可以看到,它就调用了View的mRenderNode里面的setXXX方法,最关键就是这些方法啦,其实这几个setXXX方法在View里面也有公开的,我们也是可以直接调用的,所以我们在处理ACTION_MOVE的时候,就直接调用它而不用播放动画啦。我们现在验证一下这个方案可不可行:先试试setTranslationY:
将setTranslationY方法换成setRotation看看:
来源:https://blog.csdn.net/u011387817/article/details/80313184