关于Transitions-Everywhere

Animate all the things. Transitions in Android(译)

原文链接

Animate all the things. Transitions in Android

Github

Transitions-Everywhere

译文开始

嘿,各位Android开发者。我将告诉你们一些Android动画的新动向。Google最终发布了Material Design: 动画不仅仅是iOS了。其中的一个新概念: Material motion.

“动作使得(界面交互)变得有意义。即便对象在不断的变化,用户依然可以连续直观地体验到其过程。在 material design 的世界中,Motion使得空间关系,功能性和意向的表达 变得连贯而更具美感。”
– Material Design guidelines.

实际上,创建动画是个耗时的过程。在过往的开发中,只需要调用setVisibility(View.VISIBLE)就显示新功能的业务逻辑,肯定是充满诱惑力的。但是记住:每次你忽略补充有意义的过渡动画的机会,在世界某个地方就多了一名悲伤的设计师。
如果我告诉你,动画要比你想象的省力?你有没有听过Transitions API?是的,这是谷歌推广的Activities之间精美的动画,可悲的是,它仅适用于Android5.0以后。所以实际上没有人使用它。但是想象一下,这个API可以在许多不同的情况下得到有效利用,并且更令人兴奋的是,可在旧Android版本中使用。

让我们从一些历史开始

Android 4.0中,介绍了ViewGroup新的animateLayoutChange参数。但是,就算调用getLayoutTransition()和配置里面的一些东西,仍然不稳定,不够灵活。所以你不能使用它做太多事情。
4.4 Kitkat带给我们场景转换的想法。场景在技术上是指场景根中(布局容器)所有View的状态。转场(Transition)是将View的动画集合,从一个场景平滑过渡到另一个场景。举几个栗子,它会更加吸引人?当然。

想象一下,我们有一个按钮

点击之后,我们想在按钮的下面出现一串文字。这是布局文件:

1
2
3
4
5
6
7
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:id = "@+id/transitions_container" android:layout_width = "match_parent" android:layout_height = "match_parent" android:gravity = "center" android:orientation = "vertical" >
  
     < Button android:id = "@+id/button" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "DO MAGIC" />
  
     < TextView android:id = "@+id/text" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_marginTop = "16dp" android:text = "Transitions are awesome!" android:visibility = "gone" />
  
</ LinearLayout >

然后代码中添加一个click listener:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final ViewGroup transitionsContainer = (ViewGroup) view.findViewById(R.id.transitions_container);
final TextView text = (TextView) transitionsContainer.findViewById(R.id.text);
final Button button = (Button) transitionsContainer.findViewById(R.id.button);
  
button.setOnClickListener( new View.OnClickListener() {
 
     boolean visible;
 
     @Override
     public void onClick(View v) {
         TransitionManager.beginDelayedTransition(transitionsContainer);
         visible = !visible;
         text.setVisibility(visible ? View.VISIBLE : View.GONE);         
     }
 
});

效果:
have_a_text_appeared

还不错。一行代码搞定了动画。有趣的事情不只是text显示有动画,按钮的位置也有动画。当TextView显示是引起的布局变化,Transition框架会自动完成布局变化的动画,所以你不需要自己来做。就算前一个动画还在运行,你也可以启动一个新的动画。Transition框架会停止正在运行的动画,然后在当前位置继续动画。所有的都是自动的。

通过beginDelayedTransition(final ViewGroup sceneRoot, Transition transition)第二个参数,我们可以制定动画类型。

Transition的简单类型

  • ChangeBounds. 用于View的位置和大小动画。上面的例子就是移动按钮。
  • Fade. 它继承Visibility,并执行最流行的动画 – 淡入和淡出。上面的例子就是TextView的显示(淡入和淡出)。
  • TransitionSet. 它是Transition组合成的动画组。他们可以一起或顺序启动,可以通过setOrdering()来修改顺序。
  • AutoTransition. 它其实是一个TransitionSet,包含顺序动画:Fade out, ChangeBounds and Fade in。首先,View淡出动画,然后View的位置和大小变化动画,最后View淡入动画。

Backport(向后移植)

每个人都希望只写一个实现,在每一个Android版本中有一致的行为。希望我们使用Transitions API上,能够做到这一点。前一段时间我已经发现了两个非常相似的库::

github.com/guerwan/TransitionsBackport
github.com/pardom/TransitionSupportLibrary

他们不再维护,他们都错过了一些东西,还可以向后移植。所以基于他们两个库,我创建了一个自己的库,并增加了很多与老Android版本兼容的东西。从Lollipop到Marshmallow所有API的变化都已经合并了。
所以,我们有它:Transitions Everywhere 使Transitions API支持Android 4.0及以上。
开始使用它,只需要添加依赖(gradle):

1
2
3
dependencies {
     compile "com.andkulikov:transitionseverywhere:1.6.5"
}

然后将所有import android.transition.*,替换成com.transitionseverywhere.*

transitions_everywhere

我们还可以做什么

首先, 我们可以修改时长(duration)、插值器(interpolator)和延时启动(start delay):

1
2
3
transition.setDuration( 300 );
transition.setInterpolator( new FastOutSlowInInterpolator());
transition.setStartDelay( 200 );

让我们看看其他的可用的Transition类型

Slide(滑动)

就像 Fade transition, extends Visibility class. 它帮助View从一测滑向另一测,例子Slide(Gravity.RIGHT):
slide_from_right

Explode and Propagation

Explode 很像Slide, 只是view在计算的方向上滑动,由Transition epicenter提供 (通过setEpicenterCallback方法来设置).
TransitionPropagation 为每个动画计算启动延时。比如, Explode 默认使用 CircularPropagation。动画启动延时取决于view和epicenter之间的距离。要使用它, 调用setPropagation 。

比方说,我们有RecyclerView与GridLayoutManager,在点击任何一个元素时,我们删除所有元素。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void onClick(View clickedView) {
     // save rect of view in screen coordinates
     final Rect viewRect = new Rect();
     clickedView.getGlobalVisibleRect(viewRect);
  
     // create Explode transition with epicenter
     Transition explode = new Explode()
         .setEpicenterCallback( new Transition.EpicenterCallback() {
             @Override
             public Rect onGetEpicenter(Transition transition) {
                 return viewRect;
             }
         });
     explode.setDuration( 1000 );
     TransitionManager.beginDelayedTransition(recyclerView, explode);
  
     // remove all views from Recycler View
     recyclerView.setAdapter( null );
}

explode_and_propagation

ChangeImageTransform

ChangeImageTransform 图像矩阵变化动画. 当我们改变ImageView的scaleType时,它是非常有用。大多数情况下,都和ChangeBounds配合使用:位置、大小、scaleType的变化动画。

1
2
3
4
5
6
7
8
9
10
11
TransitionManager.beginDelayedTransition(transitionsContainer, new TransitionSet()
     .addTransition( new ChangeBounds())
     .addTransition( new ChangeImageTransform()));
  
ViewGroup.LayoutParams params = imageView.getLayoutParams();
params.height = expanded ? ViewGroup.LayoutParams.MATCH_PARENT :
     ViewGroup.LayoutParams.WRAP_CONTENT;
imageView.setLayoutParams(params);
  
imageView.setScaleType(expanded ? ImageView.ScaleType.CENTER_CROP :
     ImageView.ScaleType.FIT_CENTER);

change_image_transform

Path (Curved) motion

“真实世界的力量,例如重力,驱动一个元素沿弧线的移动而非沿直线。”
– Material Design guidelines.

对于二维坐标的View转移操作(适用于使用ChangeBounds位置变化的例子),我们可以使用ChangeBounds.setPathMotion()方法设置移动的路径.

1
2
3
4
5
6
7
TransitionManager.beginDelayedTransition(transitionsContainer,
     new ChangeBounds().setPathMotion( new ArcMotion()).setDuration( 500 ));
  
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) button.getLayoutParams();
params.gravity = isReturnAnimation ? (Gravity.LEFT | Gravity.TOP) :
     (Gravity.BOTTOM | Gravity.RIGHT);
button.setLayoutParams(params);

path_curved_motion

TransitionName

我们需要删除容器的所有view,并添加一组新的view。而一些新的元素实际上和之前创建的是相同的。我们如何告诉框架,删除哪些元素,哪些元素移动到一个新的位置? 简单。只需调用静态方法TransitionManager.setTransitionName(View v, String transitionName),为每个view提供唯一的名称。
例如,如果我们创建标题列表,每次点击按钮重新创建View并打乱排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
createViews(inflater, layout, titles);
shuffleButton.setOnClickListener( new View.OnClickListener() {
  
     @Override
     public void onClick(View v) {
         TransitionManager.beginDelayedTransition(layout, new ChangeBounds());
         Collections.shuffle(titles);
         createViews(inflater, layout, titles);
     }
  
});
  
// 需要为每个view设置transition name
 
private static void createViews(LayoutInflater inflater, ViewGroup layout, List&lt;String&gt; titles) {
     layout.removeAllViews();
     for (String title : titles) {
         TextView textView = (TextView) inflater.inflate(R.layout.text_view, layout, false );
         textView.setText(title);
         TransitionManager.setTransitionName(textView, title);
         layout.addView(textView);
     }
}

transition_name

Scale

这个不是Transitions API的中的,而是我增加的。它可以使用缩放动画来显示或隐藏view。简单的使用:new Scale():
scale

也可以和其他Transition组合使用 例如, Fade:

1
2
3
4
5
6
7
8
TransitionSet set = new TransitionSet()
     .addTransition( new Scale( 0 .7f))
     .addTransition( new Fade())
     .setInterpolator(visible ? new LinearOutSlowInInterpolator() :
         new FastOutLinearInInterpolator());
 
TransitionManager.beginDelayedTransition(transitionsContainer, set);
text2.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);

scale_fade

Recolor

猜一下这一个是什么动画。 棒棒哒!背景和/或文本颜色的变化动画。

1
2
3
4
5
6
TransitionManager.beginDelayedTransition(transitionsContainer, new Recolor());
  
button.setTextColor(getResources().getColor(!isColorsInverted ? R.color.second_accent :R.color.accent));
button.setBackgroundDrawable(
     new ColorDrawable(getResources().getColor(!mColorsInverted ? R.color.accent :
         R.color.second_accent)));

re_color

Rotate

没时间解释, 例如:

1
2
TransitionManager.beginDelayedTransition(transitionsContainer, new Rotate());
icon.setRotation(isRotated ? 135 : 0 );

rotate

ChangeText

帮助为文字变化增加淡入淡出动画,例如:

1
2
3
TransitionManager.beginDelayedTransition(transitionsContainer,
     new ChangeText().setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN));
  textView.setText(isSecondText ? TEXT_2 : TEXT_1);

change_text

Targets

Transitions很容易配置. 你可以为任何Transition指定目标view,而且只有它们的可以动画.

Methods to add target:

  • addTarget(View target) — view itself
  • addTarget(int targetViewId) — id of view
  • addTarget(String targetName) — do you remember about method TransitionManager.setTransitionName?
  • addTarget(Class targetType) — for example android.widget.TextView.class

To remove target:

  • removeTarget(View target)
  • removeTarget(int targetId)
  • removeTarget(String targetName)
  • removeTarget(Class target)

To exclude some views:

  • excludeTarget(View target, boolean exclude)
  • excludeTarget(int targetId, boolean exclude)
  • excludeTarget(Class type, boolean exclude)
  • excludeTarget(Class type, boolean exclude)

And for excluding all children of some ViewGroup:

  • excludeChildren(View target, boolean exclude)
  • excludeChildren(int targetId, boolean exclude)
  • excludeChildren(Class type, boolean exclude)

Create Transition with xml

Transition可以xml. Xml创建, 文件目录 res/anim。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version= "1.0" encoding= "utf-8" ?>
<transitionSet xmlns:app= "http://schemas.android.com/apk/res-auto"
               app:transitionOrdering= "together"
               app:duration= "400" >
     <changeBounds/>
     <changeImageTransform/>
     <fade
        app:fadingMode= "fade_in"
        app:startDelay= "200" >
         <targets>
             <target app:targetId= "@id/transition_title" />
         </targets>
     </fade>
</transitionSet>
 
// And inflating:
TransitionInflater.from(getContext()).inflateTransition(R.anim.my_the_best_transition);

Activity and Fragment transitions

Activity Transitions没办法向后移植,对不起(sadpanda)。大量的逻辑隐藏在Activity中。这同样适用于Fragment transitions。我们要创造我们自己的Fragment transitions逻辑。

Custom Transitions

对于任何view,Transitions可以用于各种用途。让我们创建一些独特-我们自己的Transition。所有的这些我们都需要实现三个方法:captureStartValues,captureEndValues and createAnimator. 前两个方法是动画之前和动画之后捕获view的状态。

我们将创建一个水平进度条(ProgressBar)进度平滑变化的动画:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
private class ProgressTransition extends Transition {
  
     /**
      * Property is like a helper that contain setter and getter in one place
      */
     private static final Property&lt;ProgressBar, Integer&gt; PROGRESS_PROPERTY =
         new IntProperty&lt;ProgressBar&gt;() {
  
         @Override
         public void setValue(ProgressBar progressBar, int value) {
             progressBar.setProgress(value);
         }
  
         @Override
         public Integer get(ProgressBar progressBar) {
             return progressBar.getProgress();
         }
     };
  
     /**
       * Internal name of property. Like a intent bundles
       */
     private static final String PROPNAME_PROGRESS = "ProgressTransition:progress" ;
  
     @Override
     public void captureStartValues(TransitionValues transitionValues) {
         captureValues(transitionValues);
     }
  
     @Override
     public void captureEndValues(TransitionValues transitionValues) {
         captureValues(transitionValues);
     }
  
     private void captureValues(TransitionValues transitionValues) {
         if (transitionValues.view instanceof ProgressBar) {
             // save current progress in the values map
             ProgressBar progressBar = ((ProgressBar) transitionValues.view);
             transitionValues.values.put(PROPNAME_PROGRESS, progressBar.getProgress());
         }
     }
  
     @Override
     public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
             TransitionValues endValues) {
         if (startValues != null &amp;&amp; endValues != null &amp;&amp; endValues.view instanceof ProgressBar) {
             ProgressBar progressBar = (ProgressBar) endValues.view;
             int start = (Integer) startValues.values.get(PROPNAME_PROGRESS);
             int end = (Integer) endValues.values.get(PROPNAME_PROGRESS);
             if (start != end) {
                 // first of all we need to apply the start value, because right now
                 // the view is have end value
                 progressBar.setProgress(start);
                 // create animator with our progressBar, property and end value
                 return ObjectAnimator.ofInt(progressBar, PROGRESS_PROPERTY, end);
             }
          }
          return null ;
     }
}

然后怎么使用我们全新的 Transition?

1
2
3
4
5
private void setProgress( int value) {
     TransitionManager.beginDelayedTransition(mTransitionsContainer, new ProgressTransition());
     value = Math.max( 0 , Math.min( 100 , value));
     mProgressBar.setProgress(value);
}

效果:
progress_transition

PS

本文中所有的例子,都在Github的工程中:

github.com/andkulikov/transitions-everywhere

Never stop moving (and transitioning).

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值