从点到面,给Button的属性动画

属性动画是API 11加进来的一个新特性,其实在现在来说也没什么新的了。属性动画可以对任意view的属性做动画,实现动画的原理就是在给定的时间内把属性从一个值变为另一个值。因此可以说属性动画什么都可以干,只要view有这个属性。

所以我们这里对Button来做一个简单的属性动画:改变这个Button的宽度。也可以用Tween Animation,但是明显有一点不能满足要求的地方是Tween Animation只能做Scale动画,也就是缩放。你可以对这个button做缩放来达到增加宽度的效果,但是这个时候按钮的文字也会跟着出现缩放和变形。同时很重要的一点,Tween Animation不改变view的本来位置和大小。看起来这个按钮变大了,但是点击动画执行前的按钮没有覆盖的位置是没有效果的。

我们简略的看一下这个Tween动画是怎么样的。

首先,用xml的文件定义一个Scale动画,对宽度扩大为原来的两倍,高度扩大为原来的六倍。在动画之后填充。动画执行完成后就可以清楚的看到按钮文字跟着按钮动画执行完成后之后的效果。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fillAfter="true"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator">
    <scale
        android:fromXScale="100%"
        android:fromYScale="100%"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="200%"
        android:toYScale="600%" />
</set>

执行这个Tween动画:

var button = findViewById(R.id.tween_button) as Button
button.setOnClickListener { v ->

    var anim = AnimationUtils.loadAnimation(this@TweenAnimActvity, R.anim.scale_anim)
    v.startAnimation(anim)
}

这里必须说明,上面这段代码是Kotlin语言写的。自从用了之后就再不想用java了。只要你有一定的java基础,阅读这段代码并没有什么难度。

点击原来按钮区域以外的地方,按钮是不会有任何的反应的。

看看效果:


试试Property动画吧

直接看看按照同样的缩放大小生成的效果吧:

property_anim.png
前后两者相差还是很明显的。越明显越突出了属性动画存在的必要。这种必要不止是效果上看到的,
还有交互和开发的时候代码相关的。

所以无论如何都要使用属性动画了。这里使用最简答的方法: ObjectAnimator来做这个动画:

ObjectAnimator.ofInt(mAnimateButton, "width", mAnimateButton.getWidth(), 1000)
   .setDuration(1000)
   .start();

看起来很简单就实现了按钮的动画。但是运行的时候就会出现问题。因为,属性动画在执行的时候需要改变指定的属性,这里是width,的值。使用的就是属性对应的getWidthsetWidth方法。getWidth在没有给定动画的初值时,使用这个方法获得初始值。setWidth则在给定的时间内不断地被用来修改属性值来达到动画的效果。注意,这个方法不是只是用一次

但是来看看ButtongetWidthsetWidth两个方法的代码:

    /**
     * Return the width of the your view.
     *
     * @return The width of your view, in pixels.
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }
    /**
     * Makes the TextView exactly this many pixels wide.
     * You could do the same thing by specifying this number in the
     * LayoutParams.
     *
     * @see #setMaxWidth(int)
     * @see #setMinWidth(int)
     * @see #getMinWidth()
     * @see #getMaxWidth()
     *
     * @attr ref android.R.styleable#TextView_width
     */
    @android.view.RemotableViewMethod
    public void setWidth(int pixels) {
        mMaxWidth = mMinWidth = pixels;
        mMaxWidthMode = mMinWidthMode = PIXELS;

        requestLayout();
        invalidate();
    }

显然在setWidth的时候,并没有用给定的值去修改按钮layout param的宽度。

在这种情况下Google给了三种解决方法:

  1. 给你的view加上get和set方法。但是这需要你有这个权限。
  2. 用一个类来包装目标view,间接的给这个view来添加get和set方法。
  3. ValueAnimatorAnimatorUpdateListener监听动画,自己修改每个时间片的属性修改。

Button添加get和set方法不是很现实,所以只能选择后两者。
下面一一介绍后面两个方法。

间接给出get、set方法

这个方法看起来很简单,定义一个类间接给出get、set方法就是这样的:

class ViewWrapper {
    View mTargetView;

    public ViewWrapper(View v) {
        mTargetView = v;
    }

    public void setWidth(int width) {
        mTargetView.getLayoutParams().width = width;
        mTargetView.requestLayout();
    }

    // for view's width
    public int getWidth() {
        int width = mTargetView.getLayoutParams().width;
        return width;
    }

    // for view's height
    public void setHeight(int height) {
        mTargetView.getLayoutParams().height = height;
        mTargetView.requestLayout();
    }

    public int getHeight() {
        int height = mTargetView.getLayoutParams().height;
        return height;
    }
}
  1. 既然动画是需要修改layout params的宽度,那么我们在这个set方法里就修改layout params的宽度。
  2. 返回layout params的宽度。这个值是view在动画之前的宽度。

然后在按钮点击之后开始这个修改宽度的动画:

 @Override
    public void onClick(View v) {
        Log.d("##ViewWrapperActivity", "width is " + v.getWidth());

        // 1
        ViewWrapper viewWrapper = new ViewWrapper(v);  
        // 2
        ObjectAnimator animator = ObjectAnimator.ofInt(viewWrapper, "width", /*viewWrapper.getWidth(),*/ 1500);  
        // 3
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                Log.d("##ANIM", "started");
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                Log.d("##ANIM", "stopped");
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        // 4
        animator.setDuration(3000).start();
    }
  1. 用包装类包装view,这里是按钮。
  2. 开始动画,动画的对象现在为包装类对象。这里可以修改属性动画的定义了,属性动画可以对任何对象修改属性。这里的包装类对象明显不是一个view
  3. 这里增加了一个监听器,监听动画是刚开始还是已经结束。
  4. 开始动画。在三秒钟的时间内修改按钮的宽度,从初始值修改为1500像素宽。

看起来已经很完美了,运行这个段代码。点击按钮后。好吧,这个动画很奇怪,并没有运行“完全”。点一下动一点,但是没有达到宽度为1500像素。虽然动画监听器AnimatorListener的方法onAnimationEnd已经执行,而且也打出了执行完成的log,但是宽度始终达不到。所以说动画执行并不“完全”。

那么这是为什么呢?先给出正确的代码各位可以参考着考虑一下:

public class ViewWrapperActivity extends Activity implements View.OnClickListener {

    private Button mAnimateButton;
    // 1
    private ViewWrapper mWrapper;

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

        mAnimateButton = (Button) findViewById(R.id.animate_button);
        mAnimateButton.setOnClickListener(this);
        // 2
        mWrapper = new ViewWrapper(mAnimateButton);
    }

    @Override
    public void onClick(View v) {
        Log.d("##ViewWrapperActivity", "width is " + v.getWidth());
        // 3
        int width = v.getLayoutParams().width;
        int height = v.getHeight(); // current height
        // 4
        PropertyValuesHolder widthHolder = PropertyValuesHolder.ofInt("width", width * 2);
        PropertyValuesHolder heightHolder = PropertyValuesHolder.ofInt("height", height * 6);
        // 5
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mWrapper, widthHolder, heightHolder);
        animator.setInterpolator(new LinearInterpolator());
        animator.addListener(new Animator.AnimatorListener() {
            // ...
        });
        animator.setDuration(3000).start();
    }
}
  1. 声明包装类对象类成员。
  2. onCreate方法里初始化包装类对象。
  3. widthheight获取Button当前的宽度和高度。
  4. 在这定义对宽度做2倍的扩大,对高度做6倍的扩大。两个动画的定义都存放在PropertyValuesHolder中,并在后面的实现中使用。使用这个类存放对不同属性的动画定义,方便使用。这两个动画会同时并行执行。
  5. mWrapper执行前面定义的两个动画。这两个动画同时执行。要使两个动画顺序执行可以AnimatorSet来实现:
    int width = v.getWidth();
    int height = v.getHeight();

    AnimatorSet animSet = new AnimatorSet();

    ObjectAnimator widthAnim = ObjectAnimator.ofInt(mSequenceWrapper, "width", width * 2);
    ObjectAnimator heightAnim = ObjectAnimator.ofInt(mSequenceWrapper, "height", height * 6);

    animSet.play(widthAnim).before(heightAnim);
    animSet.setDuration(1000);
    animSet.setInterpolator(new AccelerateDecelerateInterpolator());
    animSet.start();

这样就可以一次动画达到指定宽度和高度了。具体是为什么呢?欢迎再后面的评论中一起讨论。;)

ValueAnimatorAnimatorUpdateListener的组合来实现动画

这个就比较简单了,直接看代码:

    private void performAnimation(final View targetView, final int start, final int end) {
        // 1
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        // 2
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            private final static String ANIM_TAG = "##Value animator";
            private IntEvaluator mIntEvaluator = new IntEvaluator();

            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                int currentValue = (Integer) animator.getAnimatedValue();
                Log.d(ANIM_TAG, "current value: " + currentValue);
                // 3
                float fraction = animator.getAnimatedFraction();
                targetView.getLayoutParams().width = mIntEvaluator.evaluate(fraction, start, end);
                targetView.requestLayout();
            }
        });
        // 4
        valueAnimator.setDuration(1000).start();
    }
  1. ValueAnimator来做动画。ValueAnimator并不会实质的做什么。所以需要后面的AnimatorUpdateListener来做一些粗活儿。这里指定的从1到100也没有什么实质的作用。并不是把按钮的宽度从1变到100。后面的代码很清晰的表达了这一点。
  2. 添加AnimatorUpdateListener。最主要的就是在方法public void onAnimationUpdate(ValueAnimator animator)中做动画。每一个时间片都会调用一次这个方法。每调用这个方法一次就给这个按钮的宽度设定一个新的值。
  3. 第三步的算法是获取当前动画进行的时间片占整个动画时间的百分比,这里是fraction。然后根据这个百分比来计算当前时间片对应的按钮宽度是多少。
    当前宽度 = 初始宽度 + fraction * (结束宽度 - 初始宽度)。
    这也就解释了代码mIntEvaluator.evaluate(fraction, start, end)的作用。

完整代码看这里
到这里全部解释完。欢迎拍砖,欢迎讨论!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容

  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,580评论 0 9
  • 在手机上去实现一些动画效果算是件比较炫酷的事情,因此Android系统在一开始的时候就给我们提供了两种实现动画效果...
    Ten_Minutes阅读 3,861评论 3 11
  • ICQ 20岁了 1996年11月15日,以色列大学生发布的ICQ,开启一个时代。没有ICQ,不知道会不会有腾讯。...
    coffeecoder阅读 180评论 2 1
  • 现在已经好晚了啊,刚跟人讲了一通恋爱经验,听到一个意外的事实之后,现在感觉就像是被打乱了的序码。心里回想着事情从发...
    好想读懂全世界阅读 596评论 2 1
  • 见到真爱的人,心电图就会从小山变成巨浪。 任何东西,只要太深,都是一把刀。 喜欢一个人也许可以用词语来形容,也许可...
    五木先生努力阅读 957评论 5 7