zoukankan      html  css  js  c++  java
  • Transition学习笔记

    概述

    Android 4.4.2 (API level 19)引入Transition框架,之后很多APP上都使用该框架做出很酷炫的效果,如 Google Play Newsstand app

    还有github上很火的plaid

    在app中适当得使用上Transition能带来较好的用户体验,视频中介绍了该框架的基本使用以及其中核心的一些类和方法,只有学会这些基本的API才能在之后的Activity/Fragment过渡定制一些自己想要的效果。

    先看官网的一张关系图

    图中有三个核心的类,分别是Scene、Transition和TransitionManager,下面对这个三个核心类展开分析。

    Scene

    Scene场景,用于保存布局中所有View的属性值,创建Scene的方式可以通过getSceneForLayout方法

    getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)

    比如:

    1
    2
    mScene0 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene0, getContext());
    mScene1 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene1, getContext());

    也可以直接new Scene(ViewGroup sceneRoot, View layout)

    1
    2
    3
    4
    View view0 = inflater.inflate(R.layout.scene0, container, false);
    View view1 = inflater.inflate(R.layout.scene1, container, false);
    mScene0 = new Scene(mSceneRoot, view0);
    mScene1 = new Scene(mSceneRoot, view1);

    两种方式都需要传SceneRoot,即该场景的根节点。

    Transition

    Transition过渡动画,前面创建了两个场景,分别保存了视图的一些属性,比如Visibility、position等,Transition就是对于这些属性值的改变定义过渡的效果。从上图可以看到系统内置了一些常用的Transition,Transition的创建可以通过加载xml,如:

    res/transition/fade_transition.xml

    1
    <fade xmlns:android="http://schemas.android.com/apk/res/android" />

    然后在代码中:

    1
    2
    3
    Transition mFadeTransition =
    TransitionInflater.from(this).
    inflateTransition(R.transition.fade_transition);

    或者直接在代码中:

    1
    Transition mFadeTransition = new Fade();

    TransitionManager

    TransitionManeger用于将Scene和Transition联系起来,它提供了一系列的方法如setTransition(Scene fromScene, Scene toScene, Transition transition)指明起始场景和结束场景、他们的过渡动画是什么,go(Scene scene, Transition transition),到指定的场景所使用的过渡动画是什么,beginDelayedTransition(ViewGroup sceneRoot, Transition transition),在当前场景到下一帧的过渡效果是什么。比如这里使用go()方法,效果:

    注意这里两个Scene中红绿两个方块除了位置和大小不一样,id是一致的,transition记录下两个Scene前后属性值,根据属性值的改变执行过渡动画,默认情况下对SceneRoot下的所有View执行动画效果,我们可以通过Transition.addTarget和removeTarget方法选择性添加或移除执行动画的View。

    常用API

    有时候我们只想改变当前已展示的视图层级中View的状态,可以通过beginDelayedTransition实现,下面列举系统内置的Transition的使用。

    AutoTransition

    AutoTransition默认的动画效果,对应xml tag为autoTransition

    其实是以下几个动画组合顺序执行:

    1
    2
    3
    4
    5
    6
    <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="sequential">
    <fade android:fadingMode="fade_out" />
    <changeBounds />
    <fade android:fadingMode="fade_in" />
    </transitionSet>

    在代码中使用:

    1
    2
    3
    4
    5
    6
    TransitionManager.beginDelayedTransition(mRoot, new AutoTransition());
    if (mTextView.getVisibility() != View.VISIBLE) {
    mTextView.setVisibility(View.VISIBLE);
    } else {
    mTextView.setVisibility(View.GONE);
    }

    ChangeBounds

    ChangeBounds对应xml tag为changeBounds,根据前后布局界限的变化执行动画

    1
    2
    3
    4
    5
    6
    7
    8
    TransitionManager.beginDelayedTransition(mRoot, new ChangeBounds());
    FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTarget.getLayoutParams();
    if ((lp.gravity & Gravity.START) == Gravity.START) {
    lp.gravity = Gravity.BOTTOM | Gravity.END;
    } else {
    lp.gravity = Gravity.TOP | Gravity.START;
    }
    mTarget.setLayoutParams(lp);

    ChangeClipBounds

    ChangeClipBounds对应xml tag为changeClipBounds,作用对象:View的getClipBounds()值

    1
    2
    3
    4
    5
    6
    7
    Rect BOUNDS = new Rect(20, 20, 100, 100);
    TransitionManager.beginDelayedTransition(mRoot, new ChangeClipBounds());
    if (BOUNDS.equals(ViewCompat.getClipBounds(mImageView))) {
    ViewCompat.setClipBounds(mImageView, null);
    } else {
    ViewCompat.setClipBounds(mImageView, BOUNDS);
    }

    ChangeImageTransform

    对应xml tag为changeImageTransform,作用对象:ImageView的matrix

    1
    2
    TransitionManager.beginDelayedTransition(mRoot, new ChangeImageTransform());
    mImageView.setScaleType(ImageView.ScaleType.XXX);

    ChangeScroll

    对应xml tag为changeScroll,作用对象:View的scroll属性值

    1
    2
    TransitionManager.beginDelayedTransition(mRoot, new ChangeScroll());
    mTarget.scrollBy(-100, -100);

    ChangeTransform

    对应xml tag 为changeTransform,作用对象:View的scale和rotation

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    TransitionManager.beginDelayedTransition(mRoot, new ChangeTransform());
    if (mContainer2.getChildCount() > 0) {
    mContainer2.removeAllViews();
    showRedSquare(mContainer1);
    } else {
    mContainer1.removeAllViews();
    showRedSquare(mContainer2);
    mContainer2.getChildAt(0).setRotation(45);
    }
     
    private void showRedSquare(FrameLayout container) {
    final View view = LayoutInflater.from(getContext())
    .inflate(R.layout.red_square, container, false);
    container.addView(view);
    }

    Explode

    对应xml tag为explode,作用对象:View的Visibility

    1
    2
    3
    4
    5
    TransitionManager.beginDelayedTransition(mRoot, new Explode());
    int vis = mViews.get(0).getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE;
    for (View view : mViews) {
    view.setVisibility(vis);
    }

    Fade

    对应xml tag为fade,作用对象:View的Visibility

    可以在初始化时指定IN或者OUT分别对应淡入和淡出,也可以通过fade.setMode方法设置,若不指定默认为淡入淡出效果

    1
    2
    3
    4
    5
    TransitionManager.beginDelayedTransition(mRoot, new Fade());
    int vis = mViews.get(0).getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE;
    for (View view : mViews) {
    view.setVisibility(vis);
    }

    Slide

    对应xml tag为slide,作用对象:View的Visibility

    可以初始化时传入Gravity.XX,也可以通过slide.setSlideEdge方法设置,默认方向为Gravity.BOTTOM

    1
    2
    3
    4
    5
    TransitionManager.beginDelayedTransition(mRoot, new Slide());
    int vis = mViews.get(0).getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE;
    for (View view : mViews) {
    view.setVisibility(vis);
    }

    TransitionSet

    对应xml tag为transitionSet

    可以在代码中创建transitionSet如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    mTransition = new TransitionSet();
    mTransition.addTransition(new ChangeImageTransform());
    mTransition.addTransition(new ChangeTransform());
    TransitionManager.beginDelayedTransition(mOuterFrame, mTransition);
    if (mInnerFrame.getChildCount() > 0) {
    mInnerFrame.removeAllViews();
    addImageView(mOuterFrame, ImageView.ScaleType.CENTER_CROP, mPhotoSize);
    } else {
    mOuterFrame.removeViewAt(1);
    addImageView(mInnerFrame, ImageView.ScaleType.FIT_XY,
    FrameLayout.LayoutParams.MATCH_PARENT);
    }

    也可以通过加载xml布局创建transitionSet:

    xml布局长这样:

    1
    2
    3
    4
    5
    6
    <?xml version="1.0" encoding="utf-8"?>
    <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="together">
    <changeImageTransform/>
    <changeTransform/>
    </transitionSet>

    通过transitionOrdering属性设置动画执行的顺序,together表示同时执行,sequential表示顺序执行,在代码中可以调用TransitionSet的setOrdering(int)方法,属性值传ORDERING_SEQUENTIAL或者ORDERING_TOGETHER

    在代码中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    mTransition = (TransitionSet) TransitionInflater.from(getContext()).inflateTransition(R.transition.transition);
    TransitionManager.beginDelayedTransition(mOuterFrame, mTransition);
    if (mInnerFrame.getChildCount() > 0) {
    mInnerFrame.removeAllViews();
    addImageView(mOuterFrame, ImageView.ScaleType.CENTER_CROP, mPhotoSize);
    } else {
    mOuterFrame.removeViewAt(1);
    addImageView(mInnerFrame, ImageView.ScaleType.FIT_XY,
    FrameLayout.LayoutParams.MATCH_PARENT);
    }

    这里结合changeImageTransform和changeTransform,效果如下:

    PathMotion

    Transition的辅助工具,以path的方式指定过渡效果,两个具体实现类ArcMotion和PatternPathMotion,看下ArcMotion的效果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    mTransition = new AutoTransition();
    mTransition.setPathMotion(new ArcMotion());
    TransitionManager.beginDelayedTransition(mRoot, mTransition);
    FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTarget.getLayoutParams();
    if ((lp.gravity & Gravity.START) == Gravity.START) {
    lp.gravity = Gravity.END | Gravity.BOTTOM;
    } else {
    lp.gravity = Gravity.START | Gravity.TOP;
    }
    mTarget.setLayoutParams(lp);

    它的运动轨迹是条曲线,有兴趣的可以研究下它的实现算法,在源码中有个很萌的图如下:

    自定义Transition

    除了系统内置的Transition,我们还可以自定义Transition效果,需要继承Transition

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class CustomTransition extends Transition {
    @Override
    public void captureStartValues(TransitionValues values) {}
     
    @Override
    public void captureEndValues(TransitionValues values) {}
     
    @Override
    public Animator createAnimator(ViewGroup sceneRoot,
    TransitionValues startValues,
    TransitionValues endValues) {}
    }

    其工作原理是在captureStartValues和captureEndValues中分别记录View的属性值,官网建议确保属性值不冲突,属性值的命名格式参考:

    1
    package_name:transition_name:property_name

    在createAnimator中创建动画,对比属性值的改变执行动画效果,如自定义修改颜色动画效果:

    在两个Scene中使用自定义过渡动画,效果如下:

    Note

    1.Android 版本在4.0(API Level 14)到4.4.2(API Level 19)使用Android Support Library’s

    2.对于 SurfaceView可能不起效果,因为SurfaceView的实例是在非UI线程更新的,因此会造成和其他视图动画不同步。

    3.某些特定的转换类型在应用到TextureView时可能不会产生所需的动画效果。

    4.继承自AdapterView的如ListView,与该框架不兼容。

    5.不要对包含文本的视图的大小进行动画

    Thanks to

    Google Demo

    Github Demo传送门

  • 相关阅读:
    Python 使用ASE加密与解密
    21年我的目标
    Python使用struct模块发送字节数据解析协议
    强大的awk,解决liunx中解决浮点数计算
    没错,这是我20年正式总结
    2020年度总结--杂谈,不是技术文章
    libcurl 工具类
    【C++开源库】Windows 下编译 libcurl 库
    Qt 封装HTTP网络工具类HttpClient
    Qt 访问网络
  • 原文地址:https://www.cnblogs.com/dongweiq/p/7611073.html
Copyright © 2011-2022 走看看