Android动画 —— 过渡框架

作者: 王可大虾 | 来源:发表于2017-10-21 00:39 被阅读54次

    前不久,我写了篇关于Activity之间的过渡跳转的文章(有兴趣的请戳 Android动画 —— Activity过渡),算是初窥了Android过渡Transition)的基本概念,以及它在Activity跳转中的应用。今天,我们就来进一步学习一下它,然后实现自定义的过渡,填补系统内置效果的不足。

    一、过渡框架浅谈

    Android提供过渡框架,目的是让应用的界面“动起来”,以提升视觉吸引力和视觉上的线索,让用户知道应用到底是怎么工作的。

    好像还是不甚明了?

    从开发的角度来讲,根本上,过渡框架就是实现了View树View Hierarchy)之间的动画切换 —— 用动画将两个View树下的所有View连接起来。这里的View树,可能一个View,也可能是一个复杂的ViewGroup。

    过渡框架支持的功能包括:

    • 组级动画

    对View树中的所有View应用一个或多个动画效果

    • 基于过渡的动画

    根据起始View和结束View的属性值变化来应用动画

    • 预置动画

    引入预置动画以实现通用效果,例如渐隐或移动

    • 资源文件

    从资源文件加载View树和内置动画

    • 生命周期

    定义了生命周期回调,在动画和View树变化过程中提供更好的控制

    二、场景过渡

    场景

    在过渡框架中,有一个很重要的概念,称为场景Scene)。一个场景,保存了一个View树的状态及其所有View的属性值,也保存了View树的父引用,场景的改变与动画都将发生在这个父引用里面。

    Android字义了类Scene来表示一个场景。

    场景的过渡过程将涉及起始终止两个状态。在多数情况下,我们不用显示设置起始场景。为什么不用设置?第一,如果已经有应用过过渡,那么接下来的过渡将以已应用过的过渡的终止场景作为起始场景。第二,如果从未应用过渡,那么过渡框架会从当前的屏幕状态收集所有View的信息,然后把它们作为过渡的起始场景。

    过渡

    场景过渡的使用主要包括三个步骤:

    1. 创建起始场景、终止场景
    2. 设置过渡动画
    3. TransitionManager启动动画

    说了这么多,大概还是云里雾里?还是来个例子尝尝鲜吧。

    首先,定义场景所在的View树父布局:

        <FrameLayout
            android:id="@+id/scene_root"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="16dp"
            android:paddingTop="16dp">
    
            <include layout="@layout/a_scene" />
    
        </FrameLayout>
    

    前面已经说到,场景的转换将发生在它的父引用里面,也就是这里的FrameLayout了。而这里include的布局(a_scene),其实就是我们的起始场景布局。

    a_scene.xml

        <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/scene_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        
            <!-- Ids here must be added and be the same with those in another_scene.xml as well-->
            <ImageView
                android:id="@+id/sun"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_sun" />
        
            <ImageView
                android:id="@+id/moon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_moon"/>
        </LinearLayout>
    

    然后,定义终止场景布局another_scene。

    another_scene.xml

        <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/scene_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        
            <ImageView
                android:id="@+id/moon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_moon"/>
        
            <ImageView
                android:id="@+id/sun"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_sun" />
        </LinearLayout>
    

    两个场景的布局控件相同,只是摆放的位置不同。

    场景布局定义完毕,那就要开始创建场景了。调用Scene类的静态方法getSceneForLayout (ViewGroup sceneRoot, int layoutId, Context context),第一个参数是场景的父引用,第二个参数是场景的布局id。

        mSceneRoot = (FrameLayout) findViewById(R.id.scene_root);
        mAScene = Scene.getSceneForLayout(mSceneRoot, R.layout.a_scene, this);
        mAnotherScene = Scene.getSceneForLayout(mSceneRoot, R.layout.another_scene, this);
    

    最后,启动场景过渡动画。这里使用ChangeBounds过渡。调用TransitionManager的静态方法go (Scene scene, Transition transition),第一个参数是要切换的目的场景,第二个参数是过渡动画。

        TransitionManager.go(mAnotherScene, new ChangeBounds());
    

    效果如下:

    Scenes.gif.gif

    是不是很方便?虽然用一般的属性动画,也可以做得到这样的效果,但是利用过渡框架,可就简单多了。

    值得注意的是:两个场景的“相同”元素,需要使用相同的id,否则没有动画效果

    三、无场景过渡

    有时候,我们只是需要在当前界面添加或者删除一个View —— 也就是说,变化前后的两个View树几乎是一样的。如果使用场景过渡来实现变化效果,需要维护两个几乎相同的场景布局,未免小题大做。这种情况,无场景的过渡就有了用武之地。

    无场景过渡仅包含一个View树,但是维持两个状态,通过“延时过渡”(Delayed transition)实现两个状态的过渡效果。

    无场景过渡的使用也主要包括三个步骤:

    1. 调用TransitionManager.beginDelayedTransition()保存View树状态并设置过渡
    2. View树发生改变,过渡框架记录新的状态
    3. 系统重绘,过渡框架启动过渡动画

    实际上,需要自行控制的主要是前两步,其它的都由过渡框架完成。

    还是来个例子。首先,定义一个过渡动画,包括ChangeBounds和Fade两个效果的组合。然后调用beginDelayedTransition()保存View树状态,并设置好前面定义的过渡动画。接着,addView或者removeView使得View树发生变化。当系统重绘时,将执行上述过渡动画。这里,不断地添加与删除View,观察过渡效果。

        mWithoutTransition = new TransitionSet().addTransition(new ChangeBounds()).addTransition(new Fade());
        if (!mIsRemoved) {
            TransitionManager.beginDelayedTransition(mWithoutRoot, mWithoutTransition);
            mWithoutRoot.removeView(mRight);
            mIsRemoved = true;
        } else {
            TransitionManager.beginDelayedTransition(mWithoutRoot, mWithoutTransition);
            mWithoutRoot.addView(mRight);
            mIsRemoved = false;
        }
    
    Without scenes.gif.gif

    View的增删再也不用那么无趣了。

    四、局限性

    当然,虽然过渡框架这么有用,它也有使用限制。

    在SurfaceView上应用过渡动画,可能就不会正常实现。前面已经看到,过渡是与View树的变化是息息相关的,View树的变化引起View树重绘,然后过渡才会执行。View的动画是在UI线程,而SurfaceView实例的更新却在非UI线程,所以很可能造成不一致问题。

    TextureView上应用特定的过渡动画,也可能不能正常实现。

    AdapterView系的类,也不能正确实现过渡动画,因为它们与过渡框架不兼容。

    另外,如果在过渡过程中改变一个TextView的大小,文本会在完全改变大小之前跳到新位置,也没法有效实现过渡。官方建议,应该避免过渡过程中改变包含文本TextView的View的尺寸。

    五、自定义过渡

    介绍完过渡框架在实际应用中的基本使用,是时候切入本文重点了 —— 自定义过渡。

    实现

    该如何实现呢?

    过渡框架提供了一个类Transition,它是一个抽象类,ChangeBoundsChangeImageTransform等等这些都是它的实现子类。类似地,自定义过渡动画,只需要继承Transition,并实现下面几个关键方法即可。

        public class CustomTransition extends Transition {
        
            @Override
            public void captureStartValues(TransitionValues values) {
                //TODO 捕获需要关注的起始场景的相关属性值
            }
        
            @Override
            public void captureEndValues(TransitionValues values) {
                //TODO 捕获需要关注的终止场景的相关属性值
            }
        
            @Override
            public Animator createAnimator(ViewGroup sceneRoot,
                                           TransitionValues startValues,
                                           TransitionValues endValues) {
                //TODO 根据起始、终止场景的属性值,决定是否生成过渡动画及动画效果的实现
            }
        }
    

    捕获属性值

    过渡框架的动画来自属性动画系统(Property Animation),因为属性动画是根据View的属性值在一段时间的变化来实现的,那么,过渡框架自然也是需要确定起止属性值的。

    对于一个过渡,动画所需要的属性是确定的,所以过渡框架只提供这些需要的属性到过渡中(区别于属性动画系统),然后通过捕获属性的回调方法来保存属性值。

    起始属性值

    起始属性值在回调方法captureStartValues(TransitionValues transitionValues)中设置,然后将View的起始属性值传递给过渡框架。

    其中,参数中的TransitionValues类包含两个域:

    • view:View类型,保存所关注的View的引用
    • values:Map类型,保存所有的属性值

    为避免冲突,官方建议values的键的命名按如下规则:

    package_name:transition_name:property_name
    

    终止属性值

    终止属性值在回调方法captureEndValues(TransitionValues transitionValues)中设置。此方法之于captureStartValues中的参数,虽然包含相同的view引用,但是却维护不同的values值,独立存在。

    动画适配器

    自定义过渡动画需要实现的第三个方法就是动画适配器了。

        Animator createAnimator(ViewGroup sceneRoot,
                                   TransitionValues startValues,
                                   TransitionValues endValues)
    

    参数sceneRoot是根View,startValues和endValues分别是前面捕获生成的起始和终止属性值。如果确定起始和终止的属性值有变化,那就可以生成自定义动画,并返回给过渡框架,框架将添加动画时长、插值器等等并启动此动画。默认返回null,框架不作操作。

    createAnimator()被回调的次数,依赖于起始与终止场景中的“变化数” —— 也就是说,到底我们需要改变多少个目标对象(毕竟,不同目标,动画可能并不相同)。比如,起始场景有5个目标,其中3个留至结束,2个删除。终止场景有4个目标,3个来自于起始场景,1个是新增,那么,createAnimator()将被回调3 + 2 + 1=6次。

    自定义过渡动画

    说了这么多,我们来看看具体的代码实现。

    背景颜色过渡

    首先来看一个官方的例子,此过渡用于改变背景颜色(ChangeColor)。

        /*
         * Copyright (C) 2014 The Android Open Source Project
         *
         * Licensed under the Apache License, Version 2.0 (the "License");
         * you may not use this file except in compliance with the License.
         * You may obtain a copy of the License at
         *
         *      http://www.apache.org/licenses/LICENSE-2.0
         *
         * Unless required by applicable law or agreed to in writing, software
         * distributed under the License is distributed on an "AS IS" BASIS,
         * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         * See the License for the specific language governing permissions and
         * limitations under the License.
         */
        
        package com.djx.customtransition;
        
        import android.animation.Animator;
        import android.animation.ArgbEvaluator;
        import android.animation.ValueAnimator;
        import android.graphics.drawable.ColorDrawable;
        import android.graphics.drawable.Drawable;
        import android.transition.Transition;
        import android.transition.TransitionValues;
        import android.view.View;
        import android.view.ViewGroup;
        
        public class ChangeColor extends Transition {
        
            /** Key to store a color value in TransitionValues object */
            private static final String PROPNAME_BACKGROUND = "customtransition:change_color:background";
        
            /**
             * Convenience method: Add the background Drawable property value
             * to the TransitionsValues.value Map for a target.
             */
            private void captureValues(TransitionValues values) {
                // Capture the property values of views for later use
                values.values.put(PROPNAME_BACKGROUND, values.view.getBackground());
            }
        
            @Override
            public void captureStartValues(TransitionValues transitionValues) {
                captureValues(transitionValues);
            }
        
            // Capture the value of the background drawable property for a target in the ending Scene.
            @Override
            public void captureEndValues(TransitionValues transitionValues) {
                captureValues(transitionValues);
            }
        
            // Create an animation for each target that is in both the starting and ending Scene. For each
            // pair of targets, if their background property value is a color (rather than a graphic),
            // create a ValueAnimator based on an ArgbEvaluator that interpolates between the starting and
            // ending color. Also create an update listener that sets the View background color for each
            // animation frame
            @Override
            public Animator createAnimator(ViewGroup sceneRoot,
                                           TransitionValues startValues, TransitionValues endValues) {
                // This transition can only be applied to views that are on both starting and ending scenes.
                if (null == startValues || null == endValues) {
                    return null;
                }
                // Store a convenient reference to the target. Both the starting and ending layout have the
                // same target.
                final View view = endValues.view;
                // Store the object containing the background property for both the starting and ending
                // layouts.
                Drawable startBackground = (Drawable) startValues.values.get(PROPNAME_BACKGROUND);
                Drawable endBackground = (Drawable) endValues.values.get(PROPNAME_BACKGROUND);
                // This transition changes background colors for a target. It doesn't animate any other
                // background changes. If the property isn't a ColorDrawable, ignore the target.
                if (startBackground instanceof ColorDrawable && endBackground instanceof ColorDrawable) {
                    ColorDrawable startColor = (ColorDrawable) startBackground;
                    ColorDrawable endColor = (ColorDrawable) endBackground;
                    // If the background color for the target in the starting and ending layouts is
                    // different, create an animation.
                    if (startColor.getColor() != endColor.getColor()) {
                        // Create a new Animator object to apply to the targets as the transitions framework
                        // changes from the starting to the ending layout. Use the class ValueAnimator,
                        // which provides a timing pulse to change property values provided to it. The
                        // animation runs on the UI thread. The Evaluator controls what type of
                        // interpolation is done. In this case, an ArgbEvaluator interpolates between two
                        // #argb values, which are specified as the 2nd and 3rd input arguments.
                        ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(),
                                startColor.getColor(), endColor.getColor());
                        // Add an update listener to the Animator object.
                        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                            @Override
                            public void onAnimationUpdate(ValueAnimator animation) {
                                Object value = animation.getAnimatedValue();
                                // Each time the ValueAnimator produces a new frame in the animation, change
                                // the background color of the target. Ensure that the value isn't null.
                                if (null != value) {
                                    view.setBackgroundColor((Integer) value);
                                }
                            }
                        });
                        // Return the Animator object to the transitions framework. As the framework changes
                        // between the starting and ending layouts, it applies the animation you've created.
                        return animator;
                    }
                }
                // For non-ColorDrawable backgrounds, we just return null, and no animation will take place.
                return null;
            }
    

    非常简单,背景的变化通过ValueAnimator的更新监听进行控制。

    使用起来也和系统内置过渡效果一样。

    在AS中生成一个简单工程,在主界面MainActivity添加一个文本“Hello World”,然后SecondActivity中同样添加一个文本“Hello World”,设置Activity的共享过渡,并添加过渡动画为这个ChangeColor

    SecondActivity.java

    package com.djx.customtransition;
    
    import android.app.SharedElementCallback;
    import android.graphics.drawable.ColorDrawable;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.transition.ChangeBounds;
    import android.transition.Transition;
    import android.transition.TransitionSet;
    import android.view.View;
    import android.widget.TextView;
    
    import java.util.List;
    
    public class SecondActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_second);
    
            getWindow().setSharedElementEnterTransition(getTransition());
            setEnterSharedElementCallback(new SharedElementCallback() {
                @Override
                public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
                    TextView target = (TextView) sharedElements.get(0);
                    target.setBackground(new ColorDrawable(getColor(R.color.bg_start)));
                }
    
                @Override
                public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
                    TextView target = (TextView) sharedElements.get(0);
                    target.setBackground(new ColorDrawable(getColor(R.color.bg_end)));
                }
            });
        }
    
        private Transition getTransition() {
            TransitionSet set = new TransitionSet();
    
            ChangeBounds bounds = new ChangeBounds();
            bounds.addTarget(R.id.hello);
            set.addTransition(bounds);
    
            ChangeColor bg = new ChangeColor();
            bg.addTarget(R.id.hello);
            set.addTransition(bg);
    
            return set;
        }
    }
    

    关于上面的代码,需要注意两点。

    第一,为了设置正确的起始和终止场景属性值,共享过渡的回调SharedElementCallback必须添加,因为几个关键方法的回调顺序为:

    onSharedElementStart -> captureStartValues -> onSharedElementEnd -> captureEndValues
    

    如果不在onSharedElementStartonSharedElementEnd中设置正确的始末属性值,那过渡无法知晓属性值的变化,createAnimator不会回调,过渡动画也就无从谈起。

    第二,过渡动画不仅添加了自定义的ChangeColor,还添加了ChangeBounds。单独一个ChangeColor是无法生效的。

    在主界面中启动SecondActivity,看下效果

    ChangeColor.gif

    文本颜色过渡

    前面官方的例子,自然成功实现,没有问题。现在依葫芦画瓢,来实现一个文本颜色的过渡动画。代码如下:

        package com.djx.customtransition;
        
        import android.animation.Animator;
        import android.animation.ArgbEvaluator;
        import android.animation.ObjectAnimator;
        import android.content.Context;
        import android.transition.Transition;
        import android.transition.TransitionValues;
        import android.util.AttributeSet;
        import android.view.View;
        import android.view.ViewGroup;
        import android.widget.TextView;
        
        public class ChangeTextColor extends Transition {
        
            private static final String PROPNAME_TEXT_COLOR = "com.djx.customtransition:ChangeTextColor:textColor";
        
            public ChangeTextColor() {
            }
        
            public ChangeTextColor(Context context, AttributeSet attrs) {
                super(context, attrs);
            }
        
            private void captureValues(TransitionValues transitionValues) {
                if (transitionValues.view instanceof TextView) {
                    transitionValues.values.put(PROPNAME_TEXT_COLOR,
                            ((TextView) transitionValues.view).getCurrentTextColor());
                }
            }
        
            @Override
            public void captureStartValues(TransitionValues transitionValues) {
                captureValues(transitionValues);
            }
        
            @Override
            public void captureEndValues(TransitionValues transitionValues) {
                captureValues(transitionValues);
            }
        
            @Override
            public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
                                           TransitionValues endValues) {
                if (startValues == null || endValues == null) {
                    return null;
                }
                final View view = endValues.view;
                if (view instanceof TextView) {
                    TextView textView = (TextView) view;
                    int start = (Integer) startValues.values.get(PROPNAME_TEXT_COLOR);
                    int end = (Integer) endValues.values.get(PROPNAME_TEXT_COLOR);
                    if (start != end) {
                        return ObjectAnimator.ofObject(textView, "textColor",
                                new ArgbEvaluator(), start, end);
                    }
                }
                return null;
            }
        }
    

    也非常简单,颜色的变化直接使用ObjectAnimator生成了一个属性动画。在SecondActivity中,注释掉前面的ChangeColor过渡,添加ChangeTextColor

        private Transition getTransition() {
                TransitionSet set = new TransitionSet();
                
                ChangeBounds bounds = new ChangeBounds();
                bounds.addTarget(R.id.hello);
                set.addTransition(bounds);
        
        //        ChangeColor bg = new ChangeColor();
        //        bg.addTarget(R.id.hello);
        //        set.addTransition(bg);
        
                ChangeTextColor textColor = new ChangeTextColor();
                textColor.addTarget(R.id.hello);
                set.addTransition(textColor);
                
                return set;
            }
    

    同时,在onSharedElementStartonSharedElementEnd中分别设置起始和终止的文本颜色值。

            setEnterSharedElementCallback(new SharedElementCallback() {
                    @Override
                    public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
                        TextView target = (TextView) sharedElements.get(0);
            //            target.setBackground(new ColorDrawable(getColor(R.color.bg_start)));
                        target.setTextColor(getColor(R.color.colorPrimary));
                    }
        
                    @Override
                    public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
                        TextView target = (TextView) sharedElements.get(0);
        //                target.setBackground(new ColorDrawable(getColor(R.color.bg_end)));
                        target.setTextColor(getColor(R.color.colorAccent));
                    }
                });
    

    主界面中启动SecondActivity,效果如下

    ChangeTextColor.gif.gif

    同时添加ChangeColor和ChangeTextColor,主界面中启动SecondActivity,效果正常

    ChangeColor & ChangeTextColor.gif.gif

    文本大小过渡

    既然背景颜色和文本颜色都可以过渡,那么文本大小呢?

    同样,实现一个文本大小过渡ChangeTextSize

    package com.djx.customtransition;
    
    import android.animation.Animator;
    import android.animation.ValueAnimator;
    import android.transition.Transition;
    import android.transition.TransitionValues;
    import android.util.TypedValue;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.TextView;
    
    public class ChangeTextSize extends Transition {
    
        private static final String PROPNAME_TEXT_SIZE = "com.djx.customtransition:ChangeTextSize:textSize";
    
        private void captureValues(TransitionValues transitionValues) {
            if (transitionValues.view instanceof TextView) {
                transitionValues.values.put(PROPNAME_TEXT_SIZE,
                        ((TextView) transitionValues.view).getTextSize());
            }
        }
    
        @Override
        public void captureStartValues(TransitionValues transitionValues) {
            captureValues(transitionValues);
        }
    
        @Override
        public void captureEndValues(TransitionValues transitionValues) {
            captureValues(transitionValues);
        }
    
        @Override
        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
            if (startValues == null || endValues == null) {
                return null;
            }
            final View view = endValues.view;
            if (view instanceof TextView) {
                final TextView textView = (TextView) view;
                float start = (Float) startValues.values.get(PROPNAME_TEXT_SIZE);
                float end = (Float) endValues.values.get(PROPNAME_TEXT_SIZE);
                if (start != end) {
                    ValueAnimator animator = ValueAnimator.ofFloat(start, end);
                    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                            Object value = animation.getAnimatedValue();
                            if (null != value) {
                                textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (Float) value);
                            }
                        }
                    });
                    return animator;
                }
            }
            return null;
        }
    }
    

    修改SecondActivity中的“Hello Wolrd”文本大小,然后把此过渡添加至前面的过渡组合中

    ChangeTextSize.gif

    咦?好像不是那么回事了!仔细看看,其实文本的大小,还是按预期过渡的,是一个逐渐变大变小的过程。但是,主界面到二界面时,直到过渡的最后一刻,控件的尺寸才变成实际大小,即文本变大后的实际控件尺寸。

    这就是前面官方已经提到过的关于包含文本控件时的过渡局限性了。

    有没有办法解决?

    当然有!国外的大牛已经搞定了,膜拜之。

    问题原因已经知道,就是过渡过程中,文本在变大,但是View本身的尺寸却没有跟随着变大 —— 因为过渡框架不知道应该改变控件尺寸啊!那么,就在设置终止文本大小值的时候,也把控件的尺寸修改一下可好?看看代码上怎么做:

    public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
                    TextView target = (TextView) sharedElements.get(0);
    
                    // Record the TextView's old width/height.
                    int oldWidth = target.getMeasuredWidth();
                    int oldHeight = target.getMeasuredHeight();
                    
                    target.setTextColor(getColor(R.color.colorAccent));
                    target.setBackground(new ColorDrawable(getColor(R.color.bg_end)));
                    target.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize(R.dimen.end_text_size));
    
                    // Re-measure the TextView (since the text size has changed).
                    int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
                    int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
                    target.measure(widthSpec, heightSpec);
    
                    // Record the TextView's new width/height.
                    int newWidth = target.getMeasuredWidth();
                    int newHeight = target.getMeasuredHeight();
    
                    // Set the new bounds
                    int widthDiff = newWidth - oldWidth;
                    int heightDiff = newHeight - oldHeight;
                    target.layout(target.getLeft(), target.getTop(),
                            target.getRight() + widthDiff, target.getBottom() + heightDiff);
                }
    

    首先保存一个原始尺寸,设置新的文本大小后重新测量,然后根据始末尺寸差值,设置过渡结束时的控件尺寸。

    Threesome.gif

    嗯,原生过渡框架所谓的局限性也不再局限了,完美!

    六、小结

    过渡框架中的场景过渡和无场景过渡,使用起来非常方便,大家可以发散思维把它们应用到除这里讲到的更多的应用场景中去。

    自定义过渡时,从原生例子入手,但是过程中失败很多,也踩了很多雷,多是因为理解不透彻,到最后搞明白的时候发现,其实官书API或者文档已经说清楚了。

    路漫漫其修远兮啊!

    照样,例子奉上,仅供参考。

    相关文章

      网友评论

        本文标题:Android动画 —— 过渡框架

        本文链接:https://www.haomeiwen.com/subject/dszwyxtx.html