从Android动画到贝塞尔曲线

基础知识:

动画通过连续播放一系列画面,给视觉造成连续变化的图画。很通俗的一种解释。也很好理解。那么我们先来一个案例看看。

动画案例:百度贴吧小熊奔跑

效果:

topic.gif

代码:


    <?xml version="1.0" encoding="utf-8"?>
    <animation-list
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="false">
        <item android:drawable="@drawable/gif_loading1" android:duration="50"></item>
        <item android:drawable="@drawable/gif_loading2" android:duration="50"></item>
        <item android:drawable="@drawable/gif_loading3" android:duration="50"></item>
        <item android:drawable="@drawable/gif_loading4" android:duration="50"></item>
        <item android:drawable="@drawable/gif_loading5" android:duration="50"></item>
        <item android:drawable="@drawable/gif_loading6" android:duration="50"></item>
        <item android:drawable="@drawable/gif_loading7" android:duration="50"></item>
    </animation-list>


        AnimationDrawable iv_topicAnimation;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_topic);
            ImageView iv_topic = (ImageView) findViewById(R.id.iv_topic);
            iv_topic.setBackgroundResource(R.drawable.anim_topic);
            iv_topicAnimation = (AnimationDrawable) iv_topic.getBackground();
        }

        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
            super.onWindowFocusChanged(hasFocus);
            iv_topicAnimation.start();
        }

        public static void startActiivty(Context context) {
            context.startActivity(new Intent(context, TopicActivity.class));
        }

It's important to note that the start()
method called on the AnimationDrawable cannot be called during the onCreate()
method of your Activity, because the AnimationDrawable is not yet fully attached to the window. If you want to play the animation immediately, without requiring interaction, then you might want to call it from the onWindowFocusChanged() method in your Activity, which will get called when Android brings your window into focus.

可以看到,实现小熊奔跑的效果是非常简单的。你需要理解的是

1.小熊奔跑的效果是有一张张图片交替播放,而动起来的。
2.动画通过连续播放一系列画面,给视觉造成连续变化的图画。

Paste_Image.png

好了,前面只是一个热身,下面才是真正的介绍Android动画了。

Android动画:

  • 逐帧动画(frame-by-frame animation)
  • 补间动画(tweened animation)
  • 属性动画(property animation)

对于逐帧动画和补间动画这里就不打算具体深入,但是你必须要知道的

  • 补间动画,只是改变View的显示效果而已,并不会真正的改变View的属性
  • 补间动画,只作用于View上面

写了个demo来解释一下上面的意思:

tween.gif
     bt_tween.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    TranslateAnimation animation = new TranslateAnimation(0, 0, 0, 300);
                    animation.setDuration(1000);
                    animation.setFillAfter(true);
                    tv_tween_hello.startAnimation(animation);
                }
            });

            tv_tween_hello.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(context,"hello animation",Toast.LENGTH_SHORT).show();
                }
            });

可以看到其实hello Animation平移之后点击没用了,而点击之前的位置的时候,还是有效的。这也正是我刚才说的

补间动画:只是改变View的显示效果而已,并不会真正的改变View的属性

看到这里,你也知道如果要用补间动画来做一些交互的动画是很蛋疼的。一般做法是:

属性动画

在介绍属性动画前,我们还是先来看一个demo。

property.gif

        protected Person person;
        protected TextView tv_property_info;
        protected Button bt_property;


        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_property);
            person = new Person();
            person.setName("张三");
            bt_property = (Button) findViewById(R.id.bt_property);
            tv_property_info = (TextView) findViewById(R.id.tv_property_info);
            bt_property.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            ObjectAnimator objectAnimator = ObjectAnimator.ofInt(person, "age", 1, 100);
            objectAnimator.addUpdateListener(this);
            objectAnimator.setDuration(5000);
            objectAnimator.start();
        }

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int values = (int) animation.getAnimatedValue();
            person.setAge(values);
            tv_property_info.setText(person.toString());
        }

上面代码模拟了一个Person对象,从0-100岁之间的变化,可能你有点看不懂。我这是在干嘛,但是如果你还记的刚才我说的。<b>属性动画,可以对任何对象属性进行修改,而补间动画,只作用于View上面。</b>我们的demo就是对Person这个对象中属性age不断进行修改。现在,我们假设一个TextView要进行平移或则缩放,或则旋转。只要将对应的属性进行修改,然后重绘。不就可以达到动画的效果了吗?试试吧!

<img src="http://upload-images.jianshu.io/upload_images/763512-c1017788fad0c5a0.gif?imageMogr2/auto-orient/strip">

    protected TextView tv_property_info;
        protected Button bt_property;
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_property);
            bt_property = (Button) findViewById(R.id.bt_property);
            bt_property.setText("View的改变");
            tv_property_info = (TextView) findViewById(R.id.tv_property_info);
            bt_property.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv_property_info, "TranslationY", 0, 300);
            objectAnimator.addUpdateListener(this);
            objectAnimator.setDuration(500);
            objectAnimator.start();
        }

我们来总结一下.当我们调用

 ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv_property_info, "TranslationY", 0, 300);
 objectAnimator.setDuration(500);

的时候,将初始值和结束值给ObjectAnimator,并且告诉它所需的时长,然后它的内部使用一种时间循环的机制来计算值与值之间的过渡。然后将变化的值返回给我们。然后我们获取这个间隙的值,重绘界面就给视觉造成连续变化的图画。那么内部是怎么计算这个值的呢?

TypeEvaluator
    /**
     * This evaluator can be used to perform type interpolation between <code>float</code> values.
     */
    public class FloatEvaluator implements TypeEvaluator<Number> {

        /**
         * This function returns the result of linearly interpolating the start and end values, with
         * <code>fraction</code> representing the proportion between the start and end values. The
         * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
         * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
         * and <code>t</code> is <code>fraction</code>.
         *
         * @param fraction   The fraction from the starting to the ending values
         * @param startValue The start value; should be of type <code>float</code> or
         *                   <code>Float</code>
         * @param endValue   The end value; should be of type <code>float</code> or <code>Float</code>
         * @return A linear interpolation between the start and end values, given the
         *         <code>fraction</code> parameter.
         */
        public Float evaluate(float fraction, Number startValue, Number endValue) {
            float startFloat = startValue.floatValue();
            return startFloat + fraction * (endValue.floatValue() - startFloat);
        }
    }

通过查看源码我们知道,系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值。如果,我们想要自己控制这个时间段的运动轨迹的话,就可以自定义TypeEvaluator来返回轨迹点。下面让我们自己定义一个TypeEvaluator吧!

动画案例:饿了么购物车。

先看效果图片,这样有利于思考.


想要实现这个效果,我们必须先定义一个类。来记录小球的x和y的变量。

        public class Point implements Parcelable {
            public int x;
            public int y;

            public Point() {}

            public Point(int x, int y) {
                this.x = x;
                this.y = y;
            }

            public Point(Point src) {
                this.x = src.x;
                this.y = src.y;
            }

            /**
             * Set the point's x and y coordinates
             */
            public void set(int x, int y) {
                this.x = x;
                this.y = y;
            }
          }

这个类是android自带的一个类。用来记录小球的运动轨迹点,那么现在我们只需要小球的起始点和终点,然后就可以计算出小球的运动轨迹。那么怎么获取开始点和终点呢?终点的话,是一个定值。而开始点是我们根据点击事件获取的。可以根据

     int position[] = new int[2];
     view.getLocationInWindow(position);

来获取x=position[0];y=position[1]; 现在起始点和终点都已经确定好了,那么我们来计算一下小球运动的轨迹吧。从动画中可以看出来。类似于抛物线。这里我想到了用贝塞尔曲线来做。因为我们只需要确定一个控制点,就可以根据公式算出小球的运动轨迹。

Paste_Image.png

二次方公式
二次方贝兹曲线的路径由给定点P0、P1、P2的函数B(t)追踪:



TrueType字型就运用了以贝兹样条组成的二次贝兹曲线。

有了计算公式,现在还缺一个控制点。那么这个点,该怎么确定呢?

    int pointX = (startPosition.x + endPosition.x) / 2;
    int pointY = (int) (startPosition.y - convertDpToPixel(100, mContext));
    Point controllPoint = new Point(pointX, pointY);

我这里确定控制点是,取起始点和终点X的中点,y取开始点网上偏移一个距离。这样就会有一个抛物线的角度了。当然你可以通过http://myst729.github.io/bezier-curve/ 微调下你的控制点。好了,现在我们3个点都已经确定好了,现在就需要计算小球的运动轨迹,然后绘制到Window上去就OK了。


     public class BezierEvaluator implements TypeEvaluator<Point> {

            private Point controllPoint;

            public BezierEvaluator(Point controllPoint) {
                this.controllPoint = controllPoint;
            }

            @Override
            public Point evaluate(float t, Point startValue, Point endValue) {
                int x = (int) ((1 - t) * (1 - t) * startValue.x + 2 * t * (1 - t) * controllPoint.x + t * t * endValue.x);
                int y = (int) ((1 - t) * (1 - t) * startValue.y + 2 * t * (1 - t) * controllPoint.y + t * t * endValue.y);
                return new Point(x, y);
            }
        }

上面就是取当小球在时间t的时候,x和y分别是多少,然后去改变View。

      @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Point point = (Point) animation.getAnimatedValue();
            setX(point.x);
            setY(point.y);
            invalidate();
        }

记得在动画播放完成后记得移除掉这个小球.


    anim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    ViewGroup viewGroup = (ViewGroup) getParent();
                    viewGroup.removeView(NXHooldeView.this);
                }
            });

那么现在如果叫你实现这种效果,你有思路吗?

也许放弃 才能靠近你 不再见你**
你才会把我记起 时间累积
这盛夏的果实 回忆里寂寞的香气
我要试着离开你 不要再想你
虽然这并不是我本意
你曾说过 会永远爱我
也许承诺 不过因为没把握 别用沉默
再去掩饰甚么 当结果是那么赤裸裸
以为你会说甚么 才会离开我

附上本有所有代码的链接:https://github.com/BelongsH/AnimationExample/tree/master

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

推荐阅读更多精彩内容

  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 5,955评论 1 38
  • 本笔记的原文本链接 Property Animation Overview 属性动画总览 The property...
    Jaesoon阅读 1,014评论 2 2
  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,580评论 0 9
  • 动画基础概念 动画分类 Android 中动画分为两种,一种是 Tween 动画、还有一种是 Frame 动画。 ...
    Rtia阅读 1,142评论 0 6
  • Android框架提供了两种类型的动画:View Animation(也称视图动画)和Property Anima...
    RxCode阅读 1,583评论 1 5