利用Transition制作炫酷的切换动画

来源:互联网 发布:移动网络营业 编辑:程序博客网 时间:2024/06/05 04:11

前言   

  

    使用Transition动画框架,可以帮你做到:


    1不同Activity切换的时候,根据每个activity对应的layout内容的不同做整体的场景变换的动画。


     2 不同activity切换的时候,不同activity对应的layout有相同的元素,比如activity1中有一个button,activity2有一个相同的button,transition框架可以在整体场景变换的同时,特别照顾到这个button,让动画变换的过程中,这个button享有视觉连贯性。


    3 在相同的activity里,当同一个view内容发生变化,比如在代码中removie或者add了某个ui元素,或者更改了某个已有元素的尺寸,颜色信息,Transition动画框架也可以根据这个变化做动画变换。



 不同Activity切换的Transition动画


      当从activity1跳转到activity2的时候,1会执行exitTransition, 而2会执行enterTransition,你可以在代码中使用


getWindow().setExitTransition(slide/fade...);getWindow().setEnterTransition(slide/fade...);


为这个activity设置它进入和退出时需要用到的过场动画(就是方法参数),谷歌为我们预设了几个transition,分别是Explode,Slide,Fade。可以看一下他们的效果

ExplodeSlideFadetransition_explodetransition_slidetransition_fade

在定义Explode,Slide,Fade这几个动画效果的时候,可以使用xml也可以直接在代码中定义

 1 使用xml

   在res/transtion文件夹下定义一个xml文件

res/transition/activity_fade.xml<?xml version="1.0" encoding="utf-8"?><fade xmlns:android="http://schemas.android.com/apk/res/"    android:duration="1000"/>
res/transition/activity_slide.xml<?xml version="1.0" encoding="utf-8"?><slide xmlns:android="http://schemas.android.com/apk/res/"    android:duration="1000"/>
  
 然后需要在代码中使用TransitionInflator方法来加载这个xml文件生成对应的动画效果。

MainActivity.java    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_transition);        setupWindowAnimations();    }    private void setupWindowAnimations() {        Slide slide = TransitionInflater.from(this).inflateTransition(R.transition.activity_slide);        getWindow().setExitTransition(slide);    }



TransitionActivity.java    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_transition);        setupWindowAnimations();    }    private void setupWindowAnimations() {        Fade fade = TransitionInflater.from(this).inflateTransition(R.transition.activity_fade);        getWindow().setEnterTransition(fade);    }


2 在代码中定义

MainActivity.java    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_transition);        setupWindowAnimations();    }    private void setupWindowAnimations() {        Slide slide = new Slide();        slide.setDuration(1000);        getWindow().setExitTransition(slide);    }TransitionActivity.java    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_transition);        setupWindowAnimations();    }    private void setupWindowAnimations() {        Fade fade = new Fade();        fade.setDuration(1000);        getWindow().setEnterTransition(fade);    }


效果如下


在这个过程中 


首先Actvity1启动了Activity2

Transition框架发现Activity1退出,为activity1里的可见ui组件执行Slide动画

Transition框架发现Activity2退出,为activity2里的可见ui组件执行Fade动画

当按下返回键,如果我们没有为其设置returnTransition和reenterTransition选项的话,Transition框架会对应的activity执行我们为其设置的enter和exit动画运动过程相反的动画作为默认选项。

3 ReturnTransition & ReenterTransition

他们是enterTransition和exitTransition的对应

我们把之前那个例子改一下,为TransitionActivity添加一个return动画,用的是Slide,那么当我们按下返回键的时候,会看到一个slide效果,而不是fade(之前设置的enter动画的倒放)效果。
TransitionActivity.java    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_transition);        setupWindowAnimations();    }    private void setupWindowAnimations() {        Fade fade = new Fade();        fade.setDuration(1000);        getWindow().setEnterTransition(fade);        Slide slide = new Slide();        slide.setDuration(1000);        getWindow().setReturnTransition(slide);            }
下面可以看到 没有设置return动画和设置了return动画的区别

Without Return TransitionWith Return TransitionEnter: Fade InEnter: Fade InExit: Fade OutExit: Slide outtransition_fadetransition_fade2



二  Activity之间的享元切换


你可以通过为两个不同activity中某个view设置一个唯一的统一的标识,告诉Transition这个view是这两aictivity共享的元素,变换的时候要额外照顾到,那么transition动画框架就会在场景切换的时候,让这个view顺滑的从前一个activity转移到另一个activity(注意,这里的转移并不是真正的转移,这两个item并不是同一个对象,他们是彼此独立的两个view)



1 设置windowContentTransition


首先要对app的style文件做一些修改

values/styles.xml<style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">    ...    <item name="android:windowContentTransitions">true</item    ...</style>

还可以在这里定义整个app默认的 enter, exit 和 shared element transitions动画

<style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">    ...    <!-- specify enter and exit transitions -->    <item name="android:windowEnterTransition">@transition/explode</item>    <item name="android:windowExitTransition">@transition/explode</item>    <!-- specify shared element transitions -->    <item name="android:windowSharedElementEnterTransition">@transition/changebounds</item>    <item name="android:windowSharedElementExitTransition">@transition/changebounds</item>    ...</style>

2在布局文件中为享元view定义相同的Transition Name,通过android:transitioName来定义,享元之间id和属性可以不同,但是TransitionName必须相同。

layout/activity_a.xml<ImageView        android:id="@+id/small_blue_icon"        style="@style/MaterialAnimations.Icon.Small"        android:src="@drawable/circle"        android:transitionName="@string/blue_name" />layout/activity_b.xml<ImageView        android:id="@+id/big_blue_icon"        style="@style/MaterialAnimations.Icon.Big"        android:src="@drawable/circle"        android:transitionName="@string/blue_name" />

c 通过享元方式启动另一个activity

通过ActivityOptions.makeSceneTransitionAnimation()方法来定义享元具体是哪个view和Transition Name

MainActivity.javablueIconImageView.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View v) {        Intent i = new Intent(MainActivity.this, SharedElementActivity.class);        View sharedView = blueIconImageView;        String transitionName = getString(R.string.blue_name);        ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, sharedView, transitionName);        startActivity(i, transitionActivityOptions.toBundle());    }});

最终效果如下图





三 在Fragment之间进行享元切换动画

前两步和上面基本相同

1 设置windowContentTransition

values/styles.xml<style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">    ...    <item name="android:windowContentTransitions">true</item>    ...</style>
2 在布局文件中为享元view定义相同的Transition Name
layout/fragment_a.xml<ImageView        android:id="@+id/small_blue_icon"        style="@style/MaterialAnimations.Icon.Small"        android:src="@drawable/circle"        android:transitionName="@string/blue_name" />layout/fragment_b.xml<ImageView        android:id="@+id/big_blue_icon"        style="@style/MaterialAnimations.Icon.Big"        android:src="@drawable/circle"        android:transitionName="@string/blue_name" />

3 享元方式启动另一个Fragment

FragmentB fragmentB = FragmentB.newInstance(sample);// Defines enter transition for all fragment viewsSlide slideTransition = new Slide(Gravity.RIGHT);slideTransition.setDuration(1000);sharedElementFragment2.setEnterTransition(slideTransition);// Defines enter transition only for shared elementChangeBounds changeBoundsTransition = TransitionInflater.from(this).inflateTransition(R.transition.change_bounds);fragmentB.setSharedElementEnterTransition(changeBoundsTransition);getFragmentManager().beginTransaction()        .replace(R.id.content, fragmentB)        .addSharedElement(blueView, getString(R.string.blue_name))        .commit();

最终结果如下




四 叠加Transition动画效果


你可以设置是否让exit动画和enter动画叠加在一起执行

如果设置为true 那么enter动画会立刻执行

如果设置为false 那么exter动画会等到exit动画执行完毕之后再执行

这对Fragment和Activity之间的享元切换都有效


FragmentB fragmentB = FragmentB.newInstance(sample);// Defines enter transition for all fragment viewsSlide slideTransition = new Slide(Gravity.RIGHT);slideTransition.setDuration(1000);sharedElementFragment2.setEnterTransition(slideTransition);// Defines enter transition only for shared elementChangeBounds changeBoundsTransition = TransitionInflater.from(this).inflateTransition(R.transition.change_bounds);fragmentB.setSharedElementEnterTransition(changeBoundsTransition);// Prevent transitions for overlappingfragmentB.setAllowEnterTransitionOverlap(overlap);fragmentB.setAllowReturnTransitionOverlap(overlap);getFragmentManager().beginTransaction()        .replace(R.id.content, fragmentB)        .addSharedElement(blueView, getString(R.string.blue_name))        .commit();

动画是否叠加的具体区别见下面的动图

Overlap TrueOverlap FalseFragment_2 appears on top of Fragment_1Fragment_2 waits until Fragment_1 is goneshared_element_overlapshared_element_no_overlap 五 在同一个Activity中,根据Activity布局文件中view元素的变动来进行Transition动画


1 Scene


在activity对应的布局文件中,定义一个占位用的视图,一般用framelayout,称之为稍后场景变换动画执行

所在的根视图rootView,然后在layout文件夹下,新建几个layout文件,每一个layout文件对应的视图都是一个scene,

可以这么理解,我们打算在Activity对应的界面视图的上半部分放置即将运行的动画,这个上半部分我们用帧布局

占位,称之为rootView,然后动画效果是一个小球从左边移动到右边,那么我们只需要在layout文件夹下面

定义两个layout文件,一个layout文件中,小球在左边,另一个文件中小球在右边。这两个layout,在代码中通过

Scene.getSceneForLayout(rootView,R.layout.***,this)生成一个Scene,此时,rootView和每一个scene的链接关系

就建立起来了。在通过TransitionManager.go方法就可以让动画在rootView中运行起来。


还是看一下代码吧

scene1 = 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);(...)@Overridepublic void onClick(View v) {    switch (v.getId()) {        case R.id.button1:            TransitionManager.go(scene1, new ChangeBounds());            break;        case R.id.button2:            TransitionManager.go(scene2, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds));            break;        case R.id.button3:            TransitionManager.go(scene3, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential));            break;        case R.id.button4:            TransitionManager.go(scene4, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential_with_interpolators));            break;      }}

上面的代码就会在同一个Activity中的四个Scene中间生成对应的动画,而且每个动画都不同



2 布局文件中View属性的变动


Transition动画还可以检测到同一个布局文件中某些视图元素的属性变化,然后做相应的动画效果,你可以随意在代码中修改

你需要的一切变动,比如颜色,大小,位置之类的,之后的动画Transition会自动帮你实现。

做到这些 只需要

a 先调用BeginDelayTransition方法

TransitionManager.beginDelayedTransition(sceneRoot);

b 更改layout布局中的view属性

ViewGroup.LayoutParams params = greenIconView.getLayoutParams();params.width = 200;greenIconView.setLayoutParams(params);

更改greenIconView的width时,会触发view的onMeasure()方法,此时Transition框架会记录下该view的起始width值并

做出相应的动画



六 享元动画+circle Reveal动画


享元方式的过场动画前面已经提到了,而CircleReveal是属性动画的一种,和享元切换一样都是安卓5.0之后引入的新特性

,对了忘了提,上面享元切换和这个circleReveal动画在执行前最好都加上一个版本判断

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){}

CircleReveal实现圆形缩放效果,可用来突出显示某个部分。他和享元切换一起使用可以引导用户的视觉焦点,给他的点击以反馈。


上面的动画 依次执行了

从MainActivity到RevealActivity的一个享元切换动画

在RevealActivity中有一个监听器,当它监听到RevealActivity中的享元切换动画执行完之后,为Toobar执行了一个CircleReveal属性动画

同时为RevealActivity中其他视图执行了一个放大的属性动画

Listen to shared element enter transition endTransition transition = TransitionInflater.from(this).inflateTransition(R.transition.changebounds_with_arcmotion);getWindow().setSharedElementEnterTransition(transition);transition.addListener(new Transition.TransitionListener() {    @Override    public void onTransitionEnd(Transition transition) {        animateRevealShow(toolbar);        animateButtonsIn();    }    (...)});


Reveal Toolbarprivate void animateRevealShow(View viewRoot) {    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.setVisibility(View.VISIBLE);    anim.setDuration(1000);    anim.setInterpolator(new AccelerateInterpolator());    anim.start();}
Scale up activity layout viewsprivate void animateButtonsIn() {    for (int i = 0; i < bgViewGroup.getChildCount(); i++) {        View child = bgViewGroup.getChildAt(i);        child.animate()                .setStartDelay(100 + i * DELAY)                .setInterpolator(interpolator)                .alpha(1)                .scaleX(1)                .scaleY(1);    }}

更多CircleReveal动画的例子

你可以随意设置你想要的Reveal动画,但前提是这些动画可以有效的告知用户 app对用户的点击都做了什么反馈



1 从目标视图中心开始动画


int cx = (viewRoot.getLeft() + viewRoot.getRight()) / 2;int cy = viewRoot.getTop();int finalRadius = Math.max(viewRoot.getWidth(), viewRoot.getHeight());Animator anim = ViewAnimationUtils.createCircularReveal(viewRoot, cx, cy, 0, finalRadius);viewRoot.setBackgroundColor(color);anim.start();

2 从目标视图顶部开始 并结合其他属性动画

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();


3 从点击位置开始动画


@Overridepublic 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();}


4 结合Transition动画



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);



1 0
原创粉丝点击