Music Player:从UI方案到代码
原文:Music Player: From UI Proposal to Code
部分开发者在UI方案稍微有点复杂的时候会觉得很难去写代码。他们之中的许多人都会在写代码的时候跳过许多重要的UI效果或者动画,致使最终结果跟原始的方案相差很远。
本文谈谈如何针对UI方案写代码,跳过一些基本的细节,重点关注transition与动画。
MaterialUp
一个设计师与开发者可以寻找与分享用于构建Material Design网站或app的资源的网站。这里有许多界面设计,开源app,库以及安卓,网站或者iOS上可以使用的app。
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
列表内容的退出和进入具有相同的过渡方式。
<?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
-
设置一个延迟让这些过渡和FAB的morph动画保持同步。
-
让_targetId属性指向的pane视图淡出或者淡入。_
-
把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
播放按钮的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
- 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
详情内容页的进入和返回具有相同的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
-
targetId属性指定的播放顺序容器淡出
-
targetId属性指定的按钮容器滑到底部
<item name="android:windowSharedElementEnterTransition">@transition/detail_shared_element_enter_transition</item>
detail_shared_element_enter_transition.xml hosted by GitHub
<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
-
为transition的变化率定义一个interpolator,可以是非线性的。
-
ProgressViewTransition是一个自定义的transition,使用ProgressView 来实现从水平进度到弧形进度的演变。
-
CoverViewTransition 是另一个自定义的transition,使用CoverView 实现方形封面到圆形轨道线封面的演变
-
对其他的共享元素使用默认的transition。
<item name="android:windowSharedElementReturnTransition">@transition/detail_shared_element_return_transition</item>
detail_shared_element_return_transition.xml hosted by GitHub
在这个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
-
使用相反的“演变”模式,从弧形进度到水平进度。
-
使用相反的“演变”模式,从圆形封面到方形封面。
-
对其他的共享元素使用默认的transition。
最终结果
最终的结果应该是那样。当然,最终的项目中可能丢失一些微小的细节,不过都是小事情啦。
整个项目可以在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