Android材料设计动画之触摸反馈

    xiaoxiao2021-03-25  91

    Android材料设计动画之触摸反馈

    定制触摸反馈

    前言

    在Android 5.0版本发布时,所公布的Android材料设计之动画更新幅度很大,有各种各样,各种场景下的动画。动画对于增强用户体验的感受有着超级重要的贡献,为APP适量添加合适的动画,会让APP“生龙活虎”,焕发APP的活力。不仅能给用户带来超棒的视觉享受,也能增强人机交互的能力,最终带给用户一流的用户体验。

    在Android 5.0发布了一个触摸反馈的动画,即当用户触摸(点击、长按)应用的控件的时候,会有一个持续一小段时间的动画,表示设备已经接收到这一用户操作便以实时动画反馈用户。

    本文的demo继续使用文章《 Android材料设计》一文中的demo。

    如何使用触摸反馈

    触摸反馈在Android 5.0之前的老版本就有,只不过这是一种静态的反馈,那就是State list,我们把一个控件的状态以seletor为标签定义在xml文件中,如下:

    <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true" android:drawable="@android:color/holo_green_dark"/> <item android:state_pressed="true" android:drawable="@android:color/holo_green_dark"/> <item android:drawable="@android:color/holo_blue_bright"/> </selector>

    正常状态时显示的颜色为holo_blue_bright,按下状态显示的颜色为holo_green_dark。在控件不同的状态下,显示不同的背景颜色。

    在Android 5.0推出一种新的触摸反馈,波纹动画,较之前的State list,不仅有颜色的变化,还有一个波纹样式的动画持续一小段时间。

    在Android 5.0后,不需要任何特殊代码,控件默认采用这种波纹动画作为控件的触摸反馈,以点击的地方为中心,有水波波纹向四周扩散。如下图是一个Button控件默认情况下的点击效果:

    定制触摸反馈动画

    Android提供的默认的样式往往不能满足我们的需求,这时就需要对样式等进行定制。这里说的定制,不是定制动画的样式,即不能改变波纹动画,而是对动画开始前的颜色,以及波纹的范围和颜色等进行定制。

    定制波纹颜色

    修改默认的触摸反馈波纹的颜色,重写主题的android:colorControlHighlight 属性即可,如下:

    <resources> <!-- Base application theme. --> <style name="AppTheme" parent="@style/AppThemeBase"> <!-- Customize your theme here. --> <!-- 修改波纹颜色,使用主色调colorPrimary #3F51B5 深蓝色 --> <item name="android:colorControlHighlight">@color/colorPrimary</item> <item name="android:navigationBarColor">@color/navigationBarColor</item> </style> </resources>

    效果如下:

    定制波纹边界

    把控件的background属性的值设置成如下:

    ?android:attr/selectableItemBackground 指定波纹有边界,即波纹只在控件的范围内,默认值。?android:attr/selectableItemBackgroundBorderless 指定越过视图边界的波纹。 它将由一个非空背景的视图的最近父项所绘制和设定边界。API 级别 21 中推出的新属性

    有边界的效果就是上一章节中的的图示,越过控件边界的代码如下:

    <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:textAllCaps="false" android:background="?android:attr/selectableItemBackgroundBorderless" android:textColor="@color/colorPrimary" android:text="@string/button_text"/>

    效果如下:

    长按的效果如下:

    用xml代替

    上两章节中,定义波纹颜色要在主题中,定义波纹范围在控件属性中,这样在修改和维护都不方便,可以把它们都统一写在一个xml文件中。和State list类似的selector类似,波纹动画也可以在drawable目录下创建一个ripple文件,如下:

    <ripple android:color="@color/colorPrimary" xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@android:id/mask" android:drawable="@android:color/holo_green_light" /> </ripple>

    android:color=”@color/colorPrimary表示波纹的颜色,作用和android:colorControlHighlight一样。

    item表示正常状态的颜色,即没有press/focus的状态。

    android:id=”@android:id/mask”表示是否显示item的颜色,有android:id=”@android:id/mask”表示默认不显示item的颜色,相反显示item的颜色

    如果没有item,表示波纹边界越过控件,类似?android:attr/selectableItemBackgroundBorderless。如下:

    <ripple android:color="@color/colorPrimary" xmlns:android="http://schemas.android.com/apk/res/android"> </ripple>

    注意:触摸反馈波纹动画是Android 5.0材料设计的组成部分,所以如果APP需要兼容Android 5.0之前的版本,需要考虑兼容性。需要把相关代码放置value-v21中。

    波纹动画的原理

    我们知道,一个view有图像需要绘制时,会回调draw()方法,draw()方法再调用Drawable的draw()方法,把图像绘制出来。波纹动画有RippleDrawable负责绘制,RippleDrawable继承LayerDrawable,继承Drawable,当点击一个控件时,调用RippleDrawable的draw()方法绘制波纹,如下:

    public void draw(@NonNull Canvas canvas) { pruneRipples(); ...... drawContent(canvas); //绘制波纹 drawBackgroundAndRipples(canvas); canvas.restoreToCount(saveCount); }

    这个方法定义在文件frameworks/base/graphics/java/android/graphics/drawable/RippleDrawable.java中。

    接着调用drawBackgroundAndRipples()方法,接着看:

    private void drawBackgroundAndRipples(Canvas canvas) { final RippleForeground active = mRipple; final RippleBackground background = mBackground; final int count = mExitingRipplesCount; ...... final int halfAlpha = (Color.alpha(color) / 2) << 24; final Paint p = getRipplePaint(); ...... if (background != null && background.isVisible()) { background.draw(canvas, p); } if (count > 0) { final RippleForeground[] ripples = mExitingRipples; for (int i = 0; i < count; i++) { ripples[i].draw(canvas, p); } } ...... }

    这个方法定义在文件frameworks/base/graphics/java/android/graphics/drawable/RippleDrawable.java中。

    上面的代码,有一个RippleBackground和一个RippleForeground,RippleBackground是用于绘制Ripple的背景的,单击控件时,我们看到的波纹动画由RippleForeground绘制。调用getRipplePaint()获取到画波纹的画笔,如果count > 0,就调用ripples[i].draw()方法。count表示波纹的计数,1表示一次波纹,2表示2次波纹,最多10次波纹,这里count = 1。继续往下看RippleForeground的draw()方法:

    public boolean draw(Canvas c, Paint p) { ...... if (hasDisplayListCanvas) { final DisplayListCanvas hw = (DisplayListCanvas) c; startPendingAnimation(hw, p); if (mHardwareAnimator != null) { return drawHardware(hw); } } return drawSoftware(c, p); }

    这个方法定义在文件frameworks/base/graphics/java/android/graphics/drawable/RippleComponent.java中。

    上面这个方法,会判断是否用硬件加速,如果是调用drawHardware(hw),如果不是调用drawSoftware(c, p),对于上层,它们没有区别,这两个方法,最终都是调用Canvas的drawCircle()方法,即画一个圆,这个圆表示波纹最先开始的第一道波纹,然而它是静止的,通过调用startPendingAnimation(hw, p)方法,让这个波纹从第一道波纹逐渐向四周扩散,看起来就和现实生活中的波纹的效果一样。startPendingAnimation(hw, p)的实现如下:

    private void startPendingAnimation(DisplayListCanvas hw, Paint p) { if (mHasPendingHardwareAnimator) { mHasPendingHardwareAnimator = false; mHardwareAnimator = createHardwareExit(new Paint(p)); mHardwareAnimator.start(hw); ...... } }

    这个方法定义在文件frameworks/base/graphics/java/android/graphics/drawable/RippleComponent.java中。

    这里直接调用了createHardwareExit()方法创建一个RenderNodeAnimatorSet对象,然后调用start()方法启动动画,createHardwareExit()的实现如下:

    protected RenderNodeAnimatorSet createHardwareExit(Paint p) { //波纹的幅度,即一个圆 final int radiusDuration; //波纹持续的时间 final int originDuration; //波纹的模糊度 final int opacityDuration; if (mIsBounded) { computeBoundedTargetValues(); radiusDuration = BOUNDED_RADIUS_EXIT_DURATION; originDuration = BOUNDED_ORIGIN_EXIT_DURATION; opacityDuration = BOUNDED_OPACITY_EXIT_DURATION; } else { radiusDuration = getRadiusExitDuration(); originDuration = radiusDuration; opacityDuration = getOpacityExitDuration(); } //波纹开始的坐标,即点击的地方 final float startX = getCurrentX(); final float startY = getCurrentY(); final float startRadius = getCurrentRadius(); //设置波纹的透明度 p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f)); mPropPaint = CanvasProperty.createPaint(p); mPropRadius = CanvasProperty.createFloat(startRadius); mPropX = CanvasProperty.createFloat(startX); mPropY = CanvasProperty.createFloat(startY); final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius); radius.setDuration(radiusDuration); radius.setInterpolator(DECELERATE_INTERPOLATOR); final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX); x.setDuration(originDuration); x.setInterpolator(DECELERATE_INTERPOLATOR); final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY); y.setDuration(originDuration); y.setInterpolator(DECELERATE_INTERPOLATOR); final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0); opacity.setDuration(opacityDuration); opacity.setInterpolator(LINEAR_INTERPOLATOR); opacity.addListener(mAnimationListener); final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet(); set.add(radius); set.add(opacity); set.add(x); set.add(y); return set; }

    这个方法定义在文件frameworks/base/graphics/java/android/graphics/drawable/RippleForeground.java中。

    上面的代码对应radius,x,y,opacity实例化一个RenderNodeAnimator对象,表示是一个动画,然后把每个动画add()到动画的集合中,这个和Android其它常用的动画使用是基本类似的。再往下就是动画的原理了,不在本文阐述的范围,读者可以查阅相关的Android动画的显示过程的资料。

    转载请注明原文地址: https://ju.6miu.com/read-22510.html

    最新回复(0)