V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
OneAPM
V2EX  ›  Android

探索安卓中有意义的动画!

  •  1
     
  •   OneAPM · 2015-12-22 18:09:23 +08:00 · 4964 次点击
    这是一个创建于 3020 天前的主题,其中的信息可能已经有所发展或是发生改变。

    ribot 致力于打造美好且充满意义的用户体验,在这一过程中,动画不可或缺

    google

    在 Droidcon London 听完一场 激励人心的演讲之后, 笔者决定深入研究安卓动画。本文集中展示了其研究结果,希望使开发者和设计者们意识到,为 Android 应用添加漂亮的动画并不复杂。

    animate

    动画!

    如果你想尝试这些动画效果,本文所有实例都能在 Github 上的这款 Android 应用 中找到。

    笔者非常喜欢动画效果,因为它不仅提高用户参与度,还能迅速夺人眼球。想想那些以动画设计著称的应用,它们使用起来是多么可心、流畅、*自然*。

    falcon pro

    Falcon Pro :即使细微的动画效果也可以对用户体验产生巨大影响。

    现在,与那些你很喜欢但没有动画的应用做一番比较。

    medium

    Medium : 尽管笔者很喜爱 medium APP ,但它的确缺少恰当的动画。

    小动作也能造就大不同

    我们可以从多个方面利用动画,从而:
    * 通过导航上下文传输用户;
    * 强化元素的层级结构;
    * 展示屏幕显示的组件变化。

    本文旨在说明,在应用中实现有意义的动画十分简单可行——那么,即刻开始吧。

    触觉反馈

    在用户触摸屏幕时提供反馈,有助于视觉交流,形成互动。这些动画不应分散用户的注意力,但又使他们享受其中,获得清晰的视感,从而鼓励进一步操作。

    安卓框架为此类反馈提供了波纹效果,通过设定视图背景,即可使用:

    ?android:attr/selectableItemBackground-在视图范围内展示波纹效果;

    ripple

    波纹在接触点开始,之后填充整个视图背景。

    ?android:attr/selectableItemBackgroundBorderless –将波纹效果延伸至视图之外。

    ripple2

    圆形波纹效果在接触点开始,并沿半径延伸至视图之外。

    View Property Animator

    ViewPropertyAnimator 在 API 12 首次引入,允许我们只使用一个Animator实例,就可以简单高效地使多个视图属性(并行地)执行动画操作。

    viewproperty

    此处将绘制下文提到的所有动画属性。

    注意: 如果已在视图中设置了侦听器,并打算在相同视图下,实现其他动画且不使用回调函数,则需要将侦听器设为 null

    用程序实现时,简单又整洁:

    mButton.animate()
            .alpha(1f)
            .scaleX(1f)
            .scaleY(1f)
            .translationZ(10f)
            .setInterpolator(new FastOutSlowInInterpolator())
            .setStartDelay(200)
            .setListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) { }
    
                @Override
                public void onAnimationEnd(Animator animation) { }
    
                @Override
                public void onAnimationCancel(Animator animation) { }
    
                @Override
                public void onAnimationRepeat(Animator animation) { }
            })
            .start();
    

    注意: 其实我们不需要在动画生成器中调用 start( ) 方法,因为在停止声明的同时,动画就会自动启动。在这种情况下,只有在 UI toolkit 事件队列开始下一次更新时,动画才会再开始。

    alpha

    制作 FAB 的 alpha 动画值

    x,y

    FAB 的( X 和 Y 轴)坐标动画

    z

    FAB 的 Z 坐标动画

    注意: 考虑到向后兼容性,你可以使用ViewCompat 类,来实现在安卓 API 4 以及以上版本的 ViewPropertyAnimator 类。

    Object Animator

    ViewPropertyAnimator 类似,ObjectAnimator 允许我们在目标视图(代码和 XML 源文件中)的不同属性中执行动画。然而,它们还是有些差异的:

    • 在每个实例中, ObjectAnimator 只允许对单一属性执行动画。例如,坐标 Y坐标 X 变化;
    • 但是,它允许自定义属性的动画,例如视图的前景色

    使用自定义属性给视图做缩放动画,并改变其前景色,可以达成下图的效果:

    custom

    使用自定义属性时,可以通过调用ObjectAnimator.ofInt(),创建一个 ObjectAnimator 实例,此处我们声明:

    • view – 应用动画的视图;
    • property – 设定动画的属性;
    • start color – 动画视图的初始颜色;
    • target color – 动画视图的目标颜色。

    接下来,设置评估器(此处使用ArgbEvaluator 设置颜色动画),设置延迟并执行 start( )

    private void animateForegroundColor(@ColorInt final int targetColor) {
        ObjectAnimator animator = 
            ObjectAnimator.ofInt(YOUR_VIEW, FOREGROUND_COLOR, Color.TRANSPARENT, targetColor);
        animator.setEvaluator(new ArgbEvaluator());
        animator.setStartDelay(DELAY_COLOR_CHANGE);
        animator.start();
    }
    

    接下来,使用相似的方法做视图缩放的动画,主要区别在于:

    • 使用ObjectAnimator.ofFloat() 创建 ObjectAnimator 实例,因为在调整视图大小时,并没有改动整型值;
    • 使用 View.SCALE_X 和 View.SCALE_Y 视图属性,而非自定义属性。
    private void resizeView() {
            final float widthHeightRatio = (float)     getHeight() / (float) getWidth();
            resizeViewProperty(View.SCALE_X, .5f, 200);
            resizeViewProperty(View.SCALE_Y, .5f / widthHeightRatio, 250);
        }
    
        private void resizeViewProperty(Property<View, Float> property,
                                        float targetScale, 
                                        int durationOffset) {
            ObjectAnimator animator = ObjectAnimator.ofFloat(this, property, 1f, targetScale);
            animator.setInterpolator(new LinearOutSlowInInterpolator());
            animator.setStartDelay(DELAY_COLOR_CHANGE + durationOffset);
            animator.start();
        }
    

    最后,将调整完大小的视图移开屏幕。在这种情况下,使用AdapterViewFlipper 容纳离屏视图,可以对 ViewFlipper 实例调用showNext()方法,后者会使用(定义好的动画处理该过程。接着,下一个视图也会使用定义好的入场动画,自动出现在屏幕上。

    Interpolators

    Interpolator 可用于定义动画的变化率,意味着动画的速度、加速度、行为都可以改变。可用的 interpolator 有数种,且相互之间的差别微乎其微,建议读者在设备上一探究竟。

    fast1

    该视图以线型动作开始和结束动画。

    fast2

    该视图开始动作很快,逐渐降速直至结束。

    linear

    该视图以线型动作开始,逐渐降速直至结束。

    accelarate

    该视图在动画开始时加速,并在接近结束时逐渐减速。

    Circular Reveal

    CircularReveal 使用剪切的圆形显示或隐藏一组 UI 元素。该动画除了带来视觉上的连续性,还十分赏心悦目,有助于提高用户参与度。

    circular

    如上图所示,在视图的动画效果显示之前,使用 ViewPropertyAnimator 隐藏浮动操作图标。只需定义如下属性就可以配置 circular reveal :

    • startView – CircularReveal 的开始视图(即压缩视图);
    • centerX –点击视图的 X 轴中心;
    • centerY -点击视图的 Y 轴中心;
    • targetView –要显示的视图;
    • finalRadius –剪切圆的半径,大小等于以 X 中心和 Y 中心为直角边的三角形的斜边的值。
    int centerX = (startView.getLeft() + startView.getRight()) / 2;
    int centerY = (startView.getTop() + startView.getBottom()) / 2;
    float finalRadius = (float) Math.hypot((double) centerX, (double) centerY);
    Animator mCircularReveal = ViewAnimationUtils.createCircularReveal(
      targetView, centerX, centerY, 0, finalRadius);
    

    窗口转换

    定制用于活动间导航的转换,可使用户对应用状态产生更为强烈的视觉联系。默认情况可定制如下转换:

    • enter –决定活动视图如何进入场景;
    • exit -决定活动视图如何退出场景;
    • reenter –决定活动视图退出后如何再度进入;
    • shared elements –决定活动间如何共享视图转换。

    API 21起,还有如下几种新的转换方式:

    爆炸

    Explode 转换允许视图从屏幕各个方位退出,会使压缩视图产生爆炸效果。

    explode

    在网格布局中爆炸效果尤其好。

    这种效果易于实现——首先,需要在 res/transition 目录中创建如下转换:

    <explode xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="300"/>
    

    具体做法如下:

    • 声明 explode 转换;
    • 设置持续时间为 300 毫秒。

    接下来,需要将此设置为活动的转换。既可以将其添加到活动主题:

    <style name="AppTheme.Explode" parent="AppTheme.NoActionBar">
      <item name="android:windowExitTransition">@transition/slide_explode</item>
      <item name="android:windowReenterTransition">@android:transition/slide_top</item>
    </style>
    

    也可以编程的方式解决:

    Transition explode = TransitionInflater.from(this).inflateTransition(R.transition.explode);
    getWindow().setEnterTransition(explode);
    

    滑动

    滑动切换可以使活动从屏幕右侧或底部滑入 /出。可能你以前有过*类似的*效果,但是这个新切换更加灵活。

    slide

    滑动切换使我们依次滑动子视图

    这种转换在切换活动时尤为常见,笔者对向右侧滑的流畅感觉情有独钟,当然这也很容易创建:

    <slide xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:interpolator/decelerate_cubic"
        android:slideEdge="end"/>
    

    在这里:
    - 声明了 slide 转换;
    - 设置切换的slideEdgeend(右侧),从而实现从右侧开始滑动——若想要底部滑动将设置为 bottom

    渐变

    渐变切换使活动转换出现淡入或淡出的效果。

    fade

    在视图中使用渐变动画操作简单,且效果宜人。

    创建此切换的操作比之前的切换更加简单:

    <fade xmlns:android="http://schemas.android.com/apk/res/android"
            android:duration="300"/>
    

    在这里:
    - 声明了 fade 转换;
    - 设置持续时间为 300 毫秒。

    优化转换

    实验的同时,笔者发现了一些可以改善上述转换效果的方法。

    允许窗口页面转换——需要在主题中启用下列属性,主题都来源于一个资料主题:

    <item name="android:windowContentTransitions">true</item>

    启用 /禁用转换重叠——上一转换过程结束,新的页面动画才会开始,这样就会形成时延。在不同的案例中,若启用如下属性,转换过程都会更加流畅自然:

    <item name="android:windowAllowEnterTransitionOverlap">true</item>
    <item name="android:windowAllowReturnTransitionOverlap">true</item>
    

    排除特定视图转换—有时我们并不想让活动中的所有视图参与动画,而且大多数情况下,工具栏和状态栏是造成转换故障主因。所幸,可以排除特定的视图,使之无法转换:

    <explode xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200">
    <targets>
    <target android:excludeId="@android:id/navigationBarBackground"/>
    <target android:excludeId="@android:id/statusBarBackground"/>
    </targets>
    </explode>

    工具栏和操作栏——当使用操作栏的活动向使用工具栏的活动转换时(反之亦然),转换过程总是磕磕绊绊。为此,应当确保转换中的两个活动都使用相同的组件。

    转换持续时间——既不能让用户等太久,也不能让动画转换过快。这取决于转换持续时间,最好通过试验敲定恰当的时间。笔者发现,多数情况下 200-500 微秒最为合适。

    共享元素转换

    共享元素转换方便我们为页面间的共享视图制作动画,使动画更为人性化,并给用户带来更好的视觉感受。

    shared

    这里,第一个页面中的视图缩小并平移至第二个页面的标题图片位置。

    在布局中,必须使用 transitionName 属性将所有共享视图联系起来——这表明了视图间的转换关系。下图展示了之前动画中的共享视图:

    link

    这些都是共享视图,意味着它们会在每次页面转换过程中形成动画。

    为了完成如上转换,我们首先要声明共享转换名称,可以通过使用 XML 布局中的 transitionName 属性来完成。

    屏幕 1)

    <RelativeLayout>
        <LinearLayout>
    
            <View 
                android:id="@+id/view_shared_transition"
                android:transitionName="@string/transition_view"/>
    
            <!-- Your other views -->
    
        </LinearLayout>
    </RelativeLayout>
    

    屏幕2)

    <LinearLayout>
    
        <View
            android:id="@+id/view_shared_transition"
            android:transitionName="@string/transition_view"/>
    
        <View
            android:id="@+id/view_separator"/>
    
        <TextView
            android:id="@+id/text_detail"/>
    
        <TextView
            android:id="@+id/text_close"/>
    
    </LinearLayout>
    

    之后,在页面 1 中创建 Pair 对象,使之包含转换视图与其 transitionName。然后将其传给页面选择实例(ActivityOptionsCompat),由此两个页面都得知了共享组件,就可以开始动画了。

    Pair participants = new Pair<>(mSquareView, ViewCompat.getTransitionName(mSquareView));
    
    ActivityOptionsCompat transitionActivityOptions = 
            ActivityOptionsCompat.makeSceneTransitionAnimation(
                    SharedTransitionsActivity.this, participants);
    
    ActivityCompat.startActivity(SharedTransitionsActivity.this, 
                          intent, transitionActivityOptions.toBundle());
    

    sliding

    转换的同时滑动这些视图,有助于完成转换。

    以上就是两个视图间的转换,那么在第二个页面中从底部滑入的视图怎么办呢?

    (它们就是左边的那些视图)

    其实这个实现过程也很简单,如下:

    Slide slide = new Slide(Gravity.BOTTOM);
    slide.addTarget(R.id.view_separator);
    slide.addTarget(R.id.text_detail);
    slide.addTarget(R.id.text_close);
    getWindow().setEnterTransition(slide);
    

    如你所见,创建一个新的Slide 转换:将目标视图添加到转换中,并将滑动动作设为入场动画。

    自定义转换

    我们也可以使用之前介绍过的 API 动画创建自己的自定义转换。例如,将共享元素转换衍伸,成为转换视图变体——当我们需要显示对话框(或者类似的弹框视图)时,自定义转换就会非常有用。具体如下所示:

    custom2

    该动画可以在组件状态间引导用户的注意力。

    先来简单了解一下上图发生了什么:

    • 首先创建一个 SharedTransition ,传入压缩视图与转换名称以引用共享组件。
    • 然后创建ArcMotion 实例,使两个视图转换时形成曲线动画效果。
    • 接下来扩展 ChangeBounds 以创建自定义转换,改变( morph )两个形状(对于 button 和 FAB ,有两个不同的类)。此处重写了类中的多个方法,以便为所需属性做动画。最后,使用 ViewPropertyAnimator 调整对话框的透明度,使用 ObjectAnimator 调整两个视图间的色彩,使用 AnimatorSet 实例将两种动画效果整合在一起。

    结语

    虽然只是浅谈,文本旨在围绕创建*有意义的*动画提供有益的视角,使读者受益。今后,笔者会继续努力,以求进一步改善应用的外观与用户体验。

    原文地址: https://medium.com/ribot-labs/exploring-meaningful-motion-on-android-1cd95a4bc61d#.vqazussmj

    OneAPM Mobile Insight 以真实用户体验为度量标准进行 Crash 分析,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客

    8 条回复    2015-12-23 19:04:14 +08:00
    imn1
        1
    imn1  
       2015-12-22 18:58:34 +08:00
    最有意义的动画都是加密保存的那些
    radio777
        2
    radio777  
       2015-12-22 18:58:50 +08:00
    感谢,收藏慢慢看
    yaoyao1158
        3
    yaoyao1158  
       2015-12-22 19:14:27 +08:00
    让我想起当年做毕设 用 PPT 做的演示动画效果……
    点击触发那里 真的是煞费苦心……
    caliy
        4
    caliy  
       2015-12-23 09:22:46 +08:00
    @yaoyao1158 来来,握个手!!
    hqs123
        5
    hqs123  
       2015-12-23 10:05:55 +08:00
    android 初学者路过,看不懂啊哎。。。
    WaylanPunch
        6
    WaylanPunch  
       2015-12-23 10:23:29 +08:00
    很实用啊,公司的项目不一定用得到,自己的项目可以用嘛
    persimmon
        7
    persimmon  
       2015-12-23 17:02:30 +08:00
    然而大部分应用还是不争气, 某锤子情怀依旧, 苹果依然火爆....
    SR1
        8
    SR1  
       2015-12-23 19:04:14 +08:00
    好赞!之前看了 TransitionFramework ,感觉很牛逼,但没深究下去
    楼主看得很透彻啊!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3024 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 11:03 · PVG 19:03 · LAX 04:03 · JFK 07:03
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.