登录 立即注册
安币:

过渡动画框架Transition Framework [复制链接]

2018-11-7 10:17
liujh 阅读:251 评论:1 赞:0
Tag:  

先说下什么是 Transition(过渡动画). Lollipop(5.0) 中 Activity 和 Fragment 的过渡动画是基于 Android 一个叫作 Transition 的新特性实现的。 初次引入这个特性是在 KitKat(4.4) 中,Transition 框架提供了一个方便的 API 来构建应用中不同 UI 状态切换时的动画。 这个框架始终围绕两个关键概念:场景和过渡。 场景 描述应用中 UI 的状态(这个定义太抽象了,下面会具体解释),过渡 就是确定两个场景转换之间的过渡动画。

具体作用:

  • 可以在界面(Activity & Fragment)之间跳转的时候添加动画

  • 动画共享元素之间的转换活动

  • 界面中布局元素的过渡动画。

1. Transitions between Activity & Fragment(Content Transition)

在 Android 5.0 中, 切换 Activitys 或者 Fragments 时可以使用 Transitions 来构建精致的转场动画。虽然在之前的版本中已经引入 Activity 和 Fragment 的切换动画(通过 Activity#overridePendingTransition() 和 FragmentTransaction#setCustomAnimation() 方法实现),但是动画的对象只能是Activity/Fragment整体。而新的 API 将这个特性延伸,可以协调 Activity/Fragment 中每一个 view ,为其设置单独的的进入和退出 transition,轻松搞定流畅的屏幕切换动作。

android.transition预定义了三种过渡动画:Explode,Slide和Fade。

|Explode

|从中心移入或移出

|

Picture

Picture

Picture

不同的场景下,我们可以为同一个界面设置不同效果的过渡动画,这里解释下场景的意思:

假设 A 和 B 是两个 Activity,通过 A 来启动 B。 A 叫做 "调用Activity"(调用 startActivity() 的那个) B 就是 "被调用Activity"

根据上述情景,可以为activity划分出4种场景的动画:

  • Activity A 的 退出动画(ExitTransition ),即 A 启动 B 时 A 中 View 的动画

  • Activity B 的 进入动画(EnterTransition ),即 A 启动 B 时 B 中 View 的动画

  • Activity B 的 返回动画(ReturnTransition),即 B 返回 A 时 B 中 View 的动画

  • Activity A 的 重入动画(ReenterTransition), 即 B 返回 A 时 A 中 View 的动画

实现方法

接下来我们实际演示下为Activity添加过场动画的实现步骤,可通过xml和代码两种方式实现,以添加Fade动画为例:

  • 如果是xml方式实现,首先在/res下创建transition文件夹。
    res/transition/slide_from_right

<?xml version = 1.0 encoding = "utf-8"?>
<transitionSet xmls:android = "http://schemas.android.com/apk/res/android">

<slide duration = "500"
       slideEage = "left"/>

</transitionSet>

然后在values/styles.xml中创建带动画的主题,再加到Manifest.xml中就可以了:

<style name="TransitionAppTheme" parent="AppTheme">
        <item name="android:windowContentTransitions">true</item>
        <item name="android:windowEnterTransition">@transition/slide_from_right</item>
        <item name="android:windowExitTransition">@transition/slide_from_right</item>
        <!--避免跳转的Activity动画重叠,则下面两个属性设为false-->
        <item name="android:windowAllowEnterTransitionOverlap">false</item>
        <item name="android:windowAllowReturnTransitionOverlap">false</item>
    </style>
  • 如果直接用代码实现,可以如下实现

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    ... ...
    
    Slide slideTracition = newSlide();
    slideTracition.setSlideEdge(Gravity.LEFT);
    slideTracition.setDuration(getResources().getInteger(R.integer.anim_duration_long));

    //也可以直接取res中的transition资源
    //Transition slideTracition = TransitionInflater.from(this).inflateTransition(R.transition.slide_from_left);
    getWindow().setEnterTransition(slideTracition);
    getWindow().setExitTransition(slideTracition);
    
     ... ...
 }

最后,启动动画

//跳转
Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle()
startActivity(i, bundle);
//返回
finshAfterTransition();

上面我们只添加了EnterTransition和ExitTransition,如果未设置ReturnTransition和ReenterTransition的话,后两者分别为前两者的反向动画。
EnterTransition < - > ReturnTransition
ExitTransition < - > ReenterTransition

2.Share elements between Activity(元素共享)

共享元素过渡动画的背后是通过过渡动画将两个不同布局中的不同view关联起来。Transition框架知道用适当的动画向用户展示从一个view向另外 一个view过度。请记住:共享元素过渡的过程中,view并没有真正从一个布局跑到另外一个布局,整个过程基本都是在后一个布局中完成的。

Picture

元素共享的实现
  • 允许过渡动画
    需要在/res/style.xml添加

<item name="android:windowContentTransitions">true</item>
  • 在对应的xml文件指定TransitionName属性
    例如,我们指定activityA中的ImageView和activityB中的TextView为共享元素:

<ImageView
        android:id="@+id/square_blue"
        style="@style/MaterialAnimations.Icon.Big"
        android:src="@drawable/circle_24dp"
        android:transitionName="@string/square_blue_name" />
<TextView
       android:id="@+id/title"   
       style="@style/MaterialAnimations.TextAppearance.Title.Invers"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_gravity="center_vertical|start"
       android:text="@{sharedSample.name}"
       android:transitionName="@string/sample_blue_title" />

注意:这里必须为共享的俩个元素指定同一个TransitionName,不然不会出现共享效果

  • 启动Activity

Intent intent = new Intent(activity,target);
ActivityOptionCompat option = ActiviyoptionCampat.makeSceneTransitionAnimation(activity,  
new Pair<View, String>(viewHolder.binding.sampleIcon, activity.getString(R.string.square_blue_name)),
new Pair<View, String>(viewHolder.binding.sampleName, activity.getString(R.string.sample_blue_title)));

startActivity(intent,option.toBundle());

效果如下:

Picture

共享元素动画的效果设置

我们可以发现对于转场动画(Content transitions) 是根据每个过渡视图的可见性变化来调节的,而共享元素 transition 是根据每个共享元素视图的位置,大小和外观的变化来调节的。与转场动画类似,从 API 21 开始,框架提供了 几个自定义共享元素场景切换动画的 Transition 实现。

  • ChangeBounds - 捕获共享元素布局边界根据不同构造动画。 ChangeBounds 在共享元素 Transition 中经常使用,大多数共享元素在两个 Activity/Fragment 间会有大小 或/和 位置不同。

  • ChangeTransform - 捕获共享元素缩放和角度, 根据不同构建动画。

  • ChangeClipBounds - 捕获共享元素的 clip bounds (剪辑边界) ,根据不同构建动画。

  • ChangeImageTransform - 捕获共享元素 ImageView 的 变换矩阵( transform matrices) ,根据不同构建动画。结合 ChangeBounds, 可以让 ImageView 无缝的改变大小,形状和 ImageView.ScaleType 。

设置代码:

Slide slide = new Slide();
slide.setDuration(500);

ChangeBounds changeBounds = new ChangeBounds();
changeBounds.setDuration(500);

getWindow().setEnterTransition(slide);
getWindow().setSharedElementEnterTransition(changeBounds);

前面的代码我们并没有设置共享元素动画,因为如果我们的Theme使用的是 Material主题,当设置共享元素后会默认添加动画效果。

过渡动画以及共享元素在Fragment之间的实现

fragment使用过渡动画和共享元素,与Activity大同小异,也是直接添加Transition对象之后启动,主要是启动方法有所不同

private void addNextFragment(Sample sample, ImageView blue, boolean b) {
        SharedElementFragment2 elementFragment2 = SharedElementFragment2.newInstance(sample);
        Slide slide = new Slide();
        slide.setDuration(getResources().getInteger(R.integer.anim_duration_medium));
        slide.setSlideEdge(Gravity.RIGHT);
        
        ChangeBounds changeBounds = new ChangeBounds();
        changeBounds.setDuration(getResources().getInteger(R.integer.anim_duration_medium));
        
        elementFragment2.setEnterTransition(slide);
        elementFragment2.setAllowEnterTransitionOverlap(b);
        elementFragment2.setAllowReturnTransitionOverlap(b);
        elementFragment2.setSharedElementEnterTransition(changeBounds);

        getFragmentManager().beginTransaction().replace(R.id.sample2_content, elementFragment2).addToBackStack(null).addSharedElement(blue, getString(R.string.square_blue_name)).commit();
    }

效果如下:

Picture

3.TransitionManager 控制动画

TransitionManager是个很好用的工具,使用TransitionManager,我们给view添加一些简单属性动画的时候只需要得到这个view的根布局,然后设置下view动画之后的状态就可以了,TransitionManager会自动为每个view添加预设置好的属性动画,我们甚至可以用它对一个布局内的一组多个view一块儿添加动画,从而实现较为复杂的联动效果。事实上UI场景切换的效果就是通过它来实现的。

public class ExampleActivity extends Activity implements View.OnClickListener {
    private ViewGroup mRootView;
    private View mRedBox, mGreenBox, mBlueBox, mBlackBox;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //mRootView是要添加动画view的根布局
        mRootView = (ViewGroup) findViewById(R.id.layout_root_view);
        mRootView.setOnClickListener(this);

        mRedBox = findViewById(R.id.red_box);
        mGreenBox = findViewById(R.id.green_box);
        mBlueBox = findViewById(R.id.blue_box);
        mBlackBox = findViewById(R.id.black_box);
    }

    @Override
    public void onClick(View v) {
        TransitionManager.beginDelayedTransition(mRootView, new Fade());
        toggleVisibility(mRedBox, mGreenBox, mBlueBox, mBlackBox);
    }

    private static void toggleVisibility(View... views) {
        for (View view : views) {
            boolean isVisible = view.getVisibility() == View.VISIBLE;
            view.setVisibility(isVisible ? View.INVISIBLE : View.VISIBLE);
        }
    }
}

上面的例子我们为一组四个色块添加了Fade效果的动画,同样可以设置成Slide或者Explode,效果如下:

Picture

对于一些更为复杂的联动动画,TransitionManager还为我们提供了一种类似于切换布局就可以完成动画的方案,极大的简化了我们实现复杂动画

Picture

实现步骤如下:

  • 定义需要切换 layout xml页面;

  • 调用 Scene.getSceneForLayout() 保存每个Layout;

  • 调用 TransitionManager.go(scene1, new ChangeBounds()) 切换。
    代码示例:

private void setupLayout() {
    scene0 = Scene.getSceneForLayout(binding.sceneRoot, R.layout.activity_animations_scene0, this);
    scene1 = Scene.getSceneForLayout(binding.sceneRoot, R.layout.activity_animations_scene1, this);
    scene2 = Scene.getSceneForLayout(binding.sceneRoot, R.layout.activity_animations_scene2, this);
    scene3 = Scene.getSceneForLayout(binding.sceneRoot, R.layout.activity_animations_scene3, this);
    scene4 = Scene.getSceneForLayout(binding.sceneRoot, R.layout.activity_animations_scene4, this);
    binding.sample3Button1.setOnClickListener(this);
    binding.sample3Button2.setOnClickListener(this);
    binding.sample3Button3.setOnClickListener(this);
    binding.sample3Button4.setOnClickListener(this);
}

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.sample3_button1:
            TransitionManager.go(scene1, new ChangeBounds());
            break;
        case R.id.sample3_button2:
            TransitionManager.go(scene2, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds));
            break;
        case R.id.sample3_button3:
            TransitionManager.go(scene3,TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential));
            break;
        case R.id.sample3_button4:
            TransitionManager.go(scene3,TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential_with_interpolators));
            break;
    }
}

4.CircularReveal 显示或隐藏 的效果

ViewAnimationUtils.createCircularReveal()
当您显示或隐藏一组 UI 元素时,Circular Reveal 可为用户提供视觉连续性

Picture

参考说明:

Animator createCircularReveal (View view, // 将要变化的 View
            int centerX,                  // 动画圆的中心的x坐标
            int centerY,                  // 动画圆的中心的y坐标
            float startRadius,            // 动画圆的起始半径
            float endRadius               // 动画圆的结束半径
)

显示View:

private void animShow() {
    View myView = findViewById(R.id.my_view);
    // 从 View 的中心开始
    int cx = (myView.getLeft() + myView.getRight()) / 2;
    int cy = (myView.getTop() + myView.getBottom()) / 2;
    int finalRadius = Math.max(myView.getWidth(), myView.getHeight());

    //为此视图创建动画设计(起始半径为零)
    Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
    // 使视图可见并启动动画
    myView.setVisibility(View.VISIBLE);
    anim.start();
}

隐藏View:

private void animHide() {
    final View myView = findViewById(R.id.my_view);
    int cx = (myView.getLeft() + myView.getRight()) / 2;
    int cy = (myView.getTop() + myView.getBottom()) / 2;

    int initialRadius = myView.getWidth();

    // 半径 从 viewWidth -> 0
    Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);

    anim.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            myView.setVisibility(View.INVISIBLE);
        }
    });
    anim.start();
}

以上效果都只支持 API 23 以上,所以在我们常用的 APP 中都还不常见,但是效果真的很不错,很值得研究下。

分享到:
我来说两句
facelist
您需要登录后才可以评论 登录 | 立即注册
所有评论(1)
zhuyahui 2018-11-8 15:13
  
回复

站长推荐

通过邮件订阅最新安卓weekly信息
上一条 /4 下一条

下载安卓巴士客户端

全国最大的安卓开发者社区

广告投放| 下载客户端|申请友链|手机版|站点统计|安卓巴士 ( 粤ICP备15117877号 )

返回顶部