从点到面,给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基础,阅读这段代码并没有什么难度。

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

看看效果:
38290-20160302222820892-1090175483.png

试试Property动画吧

直接看看按照同样的缩放大小生成的效果吧:
38290-20160302222845251-1381038279.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)的作用。

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

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 330987132 | Go:217696290 | Python:336880185 | 做人要厚道,转载请注明出处!
相关文章
|
1月前
|
XML Java Android开发
Android控件之基础控件——进度条类的view——TextView、Checkbox复选控件、RadioButton单选控件、ToggleButton开关、SeekBar拖动条、menu、弹窗
Android控件之基础控件——进度条类的view——TextView、Checkbox复选控件、RadioButton单选控件、ToggleButton开关、SeekBar拖动条、menu、弹窗
|
2月前
|
数据处理 C# UED
42.c#:progressbar控件
42.c#:progressbar控件
19 1
ScrollView和HorizontalScrollView无法设置点击事件的源码解析
最近的开发过程中,发现存在ScrollView和HorizontalScrollView无法设置点击事件的现象。 我们知道,通常在设置点击事件时,位于View树上方的子View的OnClickListener,会优先于父View的OnClickListener执行。 开发过程中我们会经常使用类似的方式来给布局设置点击事件,比如给ListView的Item背景设置OnClickListener,用于点击item空白区域的跳转操作;然后再给item内部的子元素分别设置OnClickListener用于各自不同的点击操作。
Dialog和DialogFragment 设置背景透明
Dialog和DialogFragment 设置背景透明
882 0
|
前端开发
scroll-view实现不了滑动效果
scroll-view实现不了滑动效果
247 0
|
API Android开发
【Android 属性动画】属性动画 Property Animation 与 视图动画 View Animation 区别
【Android 属性动画】属性动画 Property Animation 与 视图动画 View Animation 区别
109 0
|
Android开发 Java 前端开发
Android属性动画Animator实现卫星Button
动画效果,卫星Button扇形展开和关闭 1. Animator和Animation Animator框架是android4.0之后添加的一个动画框架,和之前的Animation框架相比,Animator可以进行更多和更精细化的动画控制,而且比之前更简单和更高效。
859 0
|
XML Android开发 数据格式