Music Player:从UI方案到代码

原文:Music Player: From UI Proposal to Code 

部分开发者在UI方案稍微有点复杂的时候会觉得很难去写代码。他们之中的许多人都会在写代码的时候跳过许多重要的UI效果或者动画,致使最终结果跟原始的方案相差很远。

本文谈谈如何针对UI方案写代码,跳过一些基本的细节,重点关注transition与动画。

MaterialUp

一个设计师与开发者可以寻找与分享用于构建Material Design网站或app的资源的网站。这里有许多界面设计,开源app,库以及安卓,网站或者iOS上可以使用的app。

1-eEa4woRL5vja-6PMg6nU4Q.gif

Music Player transition by Anish Chandran

浏览这个网站你可以找到一个由Anish Chandran创建的名为 Music Player 的用户界面资源。

这个设计方案给了我们一个音乐播放app该如何以流畅和连续的方式使用Material和Motion design的例子。

热身

首先,我们需要做一些帮助我们写出motion代码的准备工作。

把motion方案分解成帧

把动态图文件转换成单个的帧。这可以帮助我们查看动画与过渡的每一个步骤。ps:如果是gif图,ps这样的软件就可以。

按类型分解

我们有许多同时发生的动画,这样去思考如何写代码会非常困难。可以把这些动画按照类型分解,比如:view滑动到底部,view淡出,view过渡到一个新的Activity,等等。

不管有没有动画,下一个建议都值得在每一个布局中采用。

简化你的视图结构

创建一个尽可能简单的View结构,避免在同一布局中使用过多的ViewGroup。这样可以使过渡动画的设计,维护以及提高app性能都变的简单。

揭开谜底

在布局文件中,一些ViewGroup把 android:transitionGroup属性设置为true,让自己在Activity Transition期间被作为一个整体对待。比如这里的播放列表容器(main layout file)和按钮容器(detail layout file)。

<RelativeLayout
    android:id="@+id/playlist"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@id/cover"
    android:gravity="center_vertical"
    android:padding="@dimen/activity_vertical_margin"
    android:transitionGroup="true">
…
<LinearLayout
    android:id="@+id/controls"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:gravity="center_horizontal"
    android:transitionGroup="true"
    app:layout_marginBottomPercent="5%">
…

transition_group.xml hosted by GitHub

在 styles.xml中我们有Main Activity 和 Detail Activity使用的主题。

  • AppTheme.Main
<item name="android:windowSharedElementsUseOverlay">false</item>

windowSharedElementsUseOverlay.xml hosted  by GitHub

禁用共享元素视图的overlay。在 Music Player的布局中我们需要在shared element view从Main Activity到Detail Activity移动的时候禁用overlay。如果它是启用的,某些共享元素视图可能会以错误的方式覆盖到其它view上。

<item name="android:windowExitTransition">@transition/list_content_exit_transition</item>
<item name="android:windowReenterTransition">@transition/list_content_reenter_transition</item>

list_content_exit_reenter_transition.xml hosted  by GitHub

1-rMvtJavdxILApk7tYp89qA.gif

列表内容的退出和进入具有相同的过渡方式。

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@integer/anim_duration_default"
    
    // 1
    android:startDelay="@integer/anim_duration_default">
    // 2
    <fade>
        <targets>
            <target android:targetId="@id/pane" />
        </targets>
    </fade>
    
    // 3
    <slide android:slideEdge="bottom">
        <targets>
            <target android:excludeId="@android:id/statusBarBackground" />
            <target android:excludeId="@id/pane" />
            <target android:excludeId="@android:id/navigationBarBackground" />
        </targets>
    </slide>
</transitionSet>

list_content_exit_reenter_transition.xml hosted by GitHub

  1. 设置一个延迟让这些过渡和FAB的morph动画保持同步。

  2. 让_targetId属性指向的pane视图淡出或者淡入。_

  3. 把RecyclerView和播放列表滑倒底部,并使用e_xcludeId属性把状态栏,pane以及navigation bar 排除在外。_

<item name="android:windowSharedElementExitTransition">@transition/list_shared_element_exit_transition</item>
<item name="android:windowSharedElementReenterTransition">@transition/list_shared_element_reenter_transition</item>

list_shared_element_exit_reenter_transition.xml hosted by GitHub

1-Ysv2-KGZkOftUq04kghxmQ.gif

播放按钮的exit 和 reenter的transition方法几乎相同。

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:duration="@integer/anim_duration_default">
    // 1
    <transition
        class="com.sample.andremion.musicplayer.transition.PlayButtonTransition"
        app:mode="play|pause" />
</transitionSet>

list_shared_element_exit_reenter_transition.xml hosted  by GitHub

  1. AppTheme.Detail是一个自定义的transition,它封装了一个AnimatedVectorDrawable来把播放按钮变为暂停或者反过来,这取决于mode的值。
<item name="android:windowEnterTransition">@transition/detail_content_enter_transition</item>
<item name="android:windowReturnTransition">@transition/detail_content_return_transition</item>

detail_content_enter_return_transition.xml hosted by GitHub

1-JtrZZ8yKvIejUucy5pcQrA.gif

详情内容页的进入和返回具有相同的transition方法。

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@integer/anim_duration_default">
    // 1
    <fade>
        <targets>
            <target android:targetId="@id/ordering" />
        </targets>
    </fade>
    
    // 2
    <slide android:slideEdge="bottom">
        <targets>
            <target android:targetId="@id/controls" />
        </targets>
    </slide>
</transitionSet>

detail_content_enter_return_transition.xml hosted by GitHub

  1. targetId属性指定的播放顺序容器淡出

  2. targetId属性指定的按钮容器滑到底部

<item name="android:windowSharedElementEnterTransition">@transition/detail_shared_element_enter_transition</item>

detail_shared_element_enter_transition.xml hosted by GitHub

1-82gfLjD1GP4X0Y7MLR_1QA.gif

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@integer/anim_duration_default"
    
    // 1
    android:interpolator="@android:interpolator/accelerate_quad">
    
    // 2
    <transition class="com.sample.andremion.musicplayer.transition.ProgressViewTransition" />
    
    // 3
    <transition class="com.sample.andremion.musicplayer.transition.CoverViewTransition" />
    
    // 4
    <transitionSet>
        <changeBounds />
        <changeTransform />
        <changeClipBounds />
        <changeImageTransform />
    </transitionSet>
</transitionSet>

detail_shared_element_enter_transition.xml hosted by GitHub

  1. 为transition的变化率定义一个interpolator,可以是非线性的。

  2. ProgressViewTransition是一个自定义的transition,使用ProgressView 来实现从水平进度到弧形进度的演变。

  3. CoverViewTransition 是另一个自定义的transition,使用CoverView 实现方形封面到圆形轨道线封面的演变

  4. 对其他的共享元素使用默认的transition。

<item name="android:windowSharedElementReturnTransition">@transition/detail_shared_element_return_transition</item>

detail_shared_element_return_transition.xml hosted by GitHub

1-F0AxfqH0-hTysHGJSsaxpg.gif

在这个transition中,我们使用和 detail_shared_element_enter_transition几乎相同的方法。不过对每部分添加了一些延时以符合UI上的效果。

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:duration="@integer/anim_duration_default"
    android:interpolator="@android:interpolator/accelerate_quad">
    // 1
    <transitionSet>
        <changeBounds />
        <changeTransform />
        <changeClipBounds />
        <changeImageTransform />
        <transition
            class="com.sample.andremion.musicplayer.transition.ProgressViewTransition"
            app:morph="1" />
        <targets>
            <target android:targetId="@id/progress" />
        </targets>
    </transitionSet>
    // 2
    <transitionSet android:startDelay="@integer/anim_duration_short">
        <changeBounds />
        <changeTransform />
        <changeClipBounds />
        <changeImageTransform />
        <transition
            class="com.sample.andremion.musicplayer.transition.CoverViewTransition"
            app:shape="circle" />
        <targets>
            <target android:targetId="@id/cover" />
        </targets>
    </transitionSet>
    // 3
    <transitionSet android:startDelay="@integer/anim_duration_default">
        <changeBounds />
        <changeTransform />
        <changeClipBounds />
        <changeImageTransform />
    </transitionSet>
</transitionSet>

detail_shared_element_return_transition.xml hosted by GitHub

  1. 使用相反的“演变”模式,从弧形进度到水平进度。

  2. 使用相反的“演变”模式,从圆形封面到方形封面。

  3. 对其他的共享元素使用默认的transition。

最终结果

1-ngFKqDoa7DgrfuPtrOaRoA.gif

最终的结果应该是那样。当然,最终的项目中可能丢失一些微小的细节,不过都是小事情啦。

整个项目可以在https://github.com/andremion/Music-Player找到。

你可以在下面的链接阅读到更多关于安卓上 meaningful motion的信息。

Applying meaningful motion on Android
How to apply meaningful and delightful motion in a sample Android appAndré Mion

来自:UI实验室