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

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动画的显示过程的资料。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值