Android Transition 学习心得

Android Transition 学习心得

Android 5.0之后。Transition 被更多的应用起来,support也对其越来越多得支持。这种大背景下,各种动画的学习便是必不可少的了。而其中Transition便是其中的佼佼者。

下面是比较详细的介绍和应用实践,我也是主要通过这个项目学习,但强烈建议,只先通看文档,别上来直接就照着写(还有看完后,记得回来啊!!!):

Material-Animations

看完后,我那叫一个心血澎湃。但按住心情,现在才是真正意义上的开始学习。

我学习的过程,大概分以下几部分,可以做个参考,也非常欢迎大家的质疑和讨论。

  1. 先运行Demo或者Examples代码,运行结果ok后,认真研读代码。我始终认为,读一个轻量,但完整的工程代码,是快速学习的最有效的途径
  2. 对关键API(类和方法)的学习。知其然,也必须,知其所以然
  3. 尝试代码。写代码千万不要粘贴,千万不要忘记配置文件,android中千万不要忘记style等

Activity跳转

1)Activity跳转都是需要添加window属性。

配置文件:

<style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">
  ...
  //添加window开启Transtions动画属性
  <item name="android:windowContentTransitions">true</item> 

  //是否覆盖执行,其实可以理解成是否同步执行还是顺序执行
  <item name="android:windowAllowEnterTransitionOverlap">false</item>
  <item name="android:windowAllowReturnTransitionOverlap">false</item>
  ...
</style>

当然,也可以直接在代码里写

 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

不过不建议在代码里写,因为现在使用Support v7 比较多,所以,不是直接原生的Activity,会又很多莫名其妙的错误。

2)配置进出动画

private void setupWindowAnimations() {
    // Re-enter transition is executed when returning to this activity
    Slide slideTransition = new Slide();
    slideTransition.setSlideEdge(Gravity.LEFT);
 slideTransition.setDuration(getResources().getInteger(R.integer.anim_duration_long));
    getWindow().setReenterTransition(slideTransition); // 5.0以后的方法
    getWindow().setExitTransition(slideTransition); // 5.0以后的方法
}

3)启动页面跳转

这里需要注意的跳转的时候一定一定一定要配置ActivityOption。

// 配置这个option必不可少
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(activity,view,shareName);
startActivity(intent,options.toBundle());

当然,现在用support已经成为主流,下面是v4提供的支持类,用法相同

// 配置这个option必不可少
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity,view,shareName);
startActivity(intent,options.toBundle());

好了,基本Activity的跳转就实现了。官方提供了三种实践,ExplodeSlide 和Fade,分别是上下拉开,上下左右滑入滑出,以及淡出淡入等。

ShareElement共享元素

不同界面的相同元素,进行跳转的时候,共联跳转,看起来简直是舒服到不行。也是我学习TransitionAnimation最大的动力。

Material-Animations中有非常详细的介绍实现。

我整体试了一下,发觉使用Fragment比使用Activity动画不仅连贯很多,而且整体感更强。而且,Fragment的API对Transtion更加的亲善。

// Transition for fragment1
Slide slideTransition = new Slide(Gravity.LEFT); 
slideTransition.setDuration(getResources().getInteger(R.integer.anim_duration_long));
// Create fragment and define some of it transitions
SharedElementFragment1 sharedElementFragment1 = SharedElementFragment1.newInstance(sample);
// 下面这几个方法都是Fragment的方法,可见Google已经建议使用Fragment,进行共享元素跳转是相当好的体验
sharedElementFragment1.setReenterTransition(slideTransition); 
sharedElementFragment1.setExitTransition(slideTransition);
sharedElementFragment1.setSharedElementEnterTransition(new ChangeBounds());

getSupportFragmentManager().beginTransaction()
        .replace(R.id.sample2_content, sharedElementFragment1)
        .commit();

Google建议,使用Fragment去承载UI界面,Activity主要承载操作Fragment。因此,关于跳转,也强烈建议使用上面的实践。

View动画

Scenes

在一个界面内(Activity or Fragment)实现动画,就需要scenes,这个scene,翻译成视图,结果图都觉得很别扭,我就这么解释,scene是一种静态或者结果状态,就是最后是啥样子的。如果是位移动画,相当于初始位置的图,和运动完后的位置图。然后由,TransitionManager最后调用,go方法,他就跑起来了。

详细请看项目中的AnimationsActivity2类,核心代码是:

cene1 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene1, this);
scene2 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene2, this);
scene3 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene3, this);
scene4 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene4, this);

View button1 = findViewById(R.id.sample3_button1);
button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        TransitionManager.go(scene1, new ChangeBounds());
    }
});
View button2 = findViewById(R.id.sample3_button2);
button2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        TransitionManager.go(scene2, TransitionInflater.from(AnimationsActivity2.this).inflateTransition(R.transition.slide_and_changebounds));
    }
});

View button3 = findViewById(R.id.sample3_button3);
button3.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        TransitionManager.go(scene3, TransitionInflater.from(AnimationsActivity2.this).inflateTransition(R.transition.slide_and_changebounds_sequential));
    }
});

View button4 = findViewById(R.id.sample3_button4);
button4.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        TransitionManager.go(scene4, TransitionInflater.from(AnimationsActivity2.this).inflateTransition(R.transition.slide_and_changebounds_sequential_with_interpolators));
    }
});

View 属性改变引起的动画

这个就有点变态了,你更改了空间的layout属性,告诉Transtion一声,他就做成动画反馈在界面上。听起来就很叼!

实现起来非常非常的简单,简单到想哭。

private void changeLayout() {

  // 声明,由Transition接管界面变化的意思,这里的viewRoot相当于根视图,
  // 也就是说,想改变layout布局的view,父类要先被接管
    TransitionManager.beginDelayedTransition(viewRoot);

    ViewGroup.LayoutParams params = square.getLayoutParams();
    if (sizeChanged) {
        params.width = savedWidth;
    } else {
        savedWidth = params.width;
        params.width = 200;
    }
    sizeChanged = !sizeChanged;
    square.setLayoutParams(params);
}

private void changePosition() {
    TransitionManager.beginDelayedTransition(viewRoot);

    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) square.getLayoutParams();
    if (positionChanged) {
        lp.gravity = Gravity.CENTER;
    } else {
        lp.gravity = Gravity.LEFT;
    }
    positionChanged = !positionChanged;
    square.setLayoutParams(lp);
}

相信,介于此,5.0后的android上很多用户体验能提升一大截。

Reveal,揭示动画

这也是相当符合设计一种动画,通过一点引发触动(可以是接触点,也可以位移后触动),然后由点及面,揭示(展现)内容。

一般都是通过shared elements一起组合使用,完成界面跳转。

Circular Reveal 即通过原点,然后由点及面逐步揭示,而且还有一个弧度,视觉体验感非常好。

这里要用到ViewAnimationUtils类(这个类里承载了很多动画,非常还用!)

// 揭示原点,这里的点取的是控件的中点
int cx = (viewRoot.getLeft() + viewRoot.getRight()) / 2;
int cy = (viewRoot.getTop() + viewRoot.getBottom()) / 2;
int finalRadius = Math.max(viewRoot.getWidth(), viewRoot.getHeight()); // 揭示弧度

// 获得动画
Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, cx, cy, 0, finalRadius);

// 这里更改动画,是为了看到的揭示能比较清楚
viewRoot.setBackgroundColor(color);
anim.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        animateButtonsIn();
    }
});
anim.start();

如果不是由控件引起的,而是由点击(其实就是触摸)引起的,只需要传入接触点的x,y坐标就好了。

@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
    if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
        if (view.getId() == R.id.square_yellow) {
            revealFromCoordinates(motionEvent.getRawX(), motionEvent.getRawY());
        }
    }
    return false;
}
private Animator animateRevealColorFromCoordinates(int x, int y) {

    float finalRadius = (float) Math.hypot(viewRoot.getWidth(), viewRoot.getHeight());

    Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, x, y, 0, finalRadius);
    viewRoot.setBackgroundColor(color);
    anim.start();
}

刚才提到的改变layout属性,就可以改变动画其实也是可以用到圆揭示:

Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.changebounds_with_arcmotion);
transition.addListener(new Transition.TransitionListener() {
    @Override
    public void onTransitionEnd(Transition transition) {
        animateRevealColor(bgViewGroup, R.color.red);
    }
    (...)

});
//接管后,传入想要改变的动画
TransitionManager.beginDelayedTransition(bgViewGroup, transition);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
btnRed.setLayoutParams(layoutParams);

总结

写的比较简单,很多东西还是对API的熟悉和使用。

Material 正在改变Android,相信以后的UI无论从实感还是物理感,都会更加贴合用户,贴近使用。


comments powered by Disqus