属性动画简单说明前篇(一)

来源:互联网 发布:数据新闻的特点 编辑:程序博客网 时间:2024/06/03 18:32

因为公司开发SDK的原因,公司开发需要做各种动画UI特效,也算是对动画有一定的了解,所以准备写个博客巩固下。本篇就对贝塞尔曲线加上属性动画来说一下。
1、线性别塞尔曲线的知识说明
线性贝塞尔曲线的公式如下:
B(t) = P0 + (P1 - P0)t 其中t的范围是[0,1](这个范围很是关键).
说白了就是两点Point0,Point1之间构成的一条直线(线段),其作用可以看做是从P0点到P1点的位移路径,假设A从P0走到P1,那么就是随着t的变换,A逐渐走P1点的一个过程。如下图(盗图):
这里写图片描述
我们知道一个点在平面中是有X,Y两个坐标点组成(特么的废话),假设Point0的坐标是(X0,Y0),Point1的坐标位(X1,Y1)那么A移动的过程中也即是随着t的渐变,A的横坐标点从X0逐渐移动到X1,纵坐标Y0逐渐移动到Y1的过程,用点来表示的话就是A经过一些列的点:(X0,Y0)–>(Xa,Ya)–>(Xb,Yb)–>…–>(X1,Y1)或者Point0–>PointA–>PointB–>…–>Point1才到Point1(此时t=1)。
我们在初中的时候学过直线方程y = kx +b是x跟y的关系,而贝塞尔曲线在应用中其实是x与t构成的直线函数以及y与t构成的直线函数关系:
B(tx) = (X1-X0)t+X0
B(ty) = (Y1-Y0)t + Y0
这里写图片描述
所以如果在android中想要让一个View从一位置移动到另外一个位置,如果用线性贝塞尔曲线的话,就是根据上面的两个函数根据变量t不断修改View的x和y的位置即可;当然因为(x,y)构成一个点,所以就是让View随着t的改变,从一个点移动到新的点的过程直到Point1。那么核心算法就是根据当前t的值(t->[0,1])根据上面的两个函数获取当前的newX和newY构成的坐标点(newX,newY)更新view的位置坐标点。
(感觉上面有点啰里啰嗦,表达能力欠缺)。
那么基本算法伪代码可以如下:

Point point0 = new Point(x0,y0);Point point1 = new Point(x1,y1);int k0 = x1- x0;//x与t直线函数的斜率int k1 = y1 - y0;//y与t直线函数的斜率float t = 0f;while(t<=0){   //最新的x和y的位置   int newX = k0*t + x0;   int newY = k1*t + y0;   //更新view的位置方式1   LayoutParams params = view.getLayoutParams();   params.leftMargin = newX;   params.rightMargin = newY;   view.setLayoutParams(params);   //t以某种规则递增,比如每次增加0.1   t+=0.1 }

2、自己写一个小小的测试例子
根据是上面的说明以及伪代码例子程序如下:
代码也很简单,首先第一一个Point类,包换了x和y:

class Point {    protected float x;//横坐标    protected float y;//纵坐标    public Point(float x,float y){        this.x = x;        this.y = y;    } }

然后定义一个线性贝塞尔曲线计算器类,这个类需要先传入线段的起始点,然后根据t来计算对应的新的Point对象:

class BezierLine {    private Point startPoint;//贝塞尔曲线起点    private Point endPoint;//贝塞尔曲线终点    public BezierLine(Point startPoint, Point endPoint) {        this.startPoint = startPoint;        this.endPoint = endPoint;    }    /**     * 根据线性贝塞尔函数,获取线性贝塞尔曲线上的某个点     * @param t 在[0,1]范围的某一个值     * @return 根据t的不同而返回的贝塞尔曲线的点     */    public  Point createBezierLine(float t){        float newX = (endPoint.x -startPoint.x)*t + startPoint.x;        float newY = (endPoint.y -startPoint.y)*t + startPoint.y;        return  new Point(newX,newY);    }}

以上可以说完事具备,只欠东风,那么怎么使用上述贝塞尔曲线来更新呢?这里提供一个简单的思路,就是用Handler来发送不断发送消息,简单的代码如下:

     private float t = 0.0f;    private BezierLine bezierLine;    private Handler handler = new Handler(){        @Override        public void handleMessage(Message msg) {            t+=0.01f;            if(t>1.0){                return;            }            //获取当前t对应的Point位置            Point newPoint =  bezierLine.createBezierLine(t);            //更新View的位置            updateViewLocation(newPoint);            //继续发送消息            handler.sendEmptyMessage(0);        }    };  private void updateViewLocation(Point point){        moveParams.leftMargin = (int)point.x;        moveParams.topMargin =(int)point.y;        moveView.setLayoutParams(moveParams);    }

运行的效果如图所示:
这里写图片描述
当然这种简单的运动通过scrollTo也可以简单实现,关于滚动的详细说明,可参考《View的滚动原理简单解析》和《View的滚动原理简单解析2》。

到此位置demo结束,只是简单的实现了两个点之间的运动估计,如果点很多的情况下怎么处理呢?比如如果View要进行如下的运动轨迹该怎么办?
这里写图片描述
在回答这个问题之前需要思考或者准备如下问题(以路径B为例):
1)从P0到P4的所需时间是多少毫秒?(答案是未知,也就是用户可配)
2)从P0–>P1、P1–>P2、P2–>P3、P3–>P4四个线段之间移动所消耗的时间是一致的吗?(答案是不一定,他们的耗时又长又短,这其实是一个动画中的插值器的概念,比如让P0–>P1的时间最短,其余的线段之间速度也设置的不一样)当然本文为了方便说明在此定义为点从P0–>P1、P1–>P2、P2–>P3、P3–>P4四个线段之间所耗时是一样的。也就是说假设传入的运动时间为duration表示,点的总数用n表示,那么每个线段之间的耗时比例关系如下:
这里写图片描述
根据上图很容易就得出了这些点与t得关系伪代码:

//点总数         int n;        //P0的起始时间为0        (0f,P0);        //p0 ..pn各个点与t的对应点断数        int lineSegment = n-1;        for (int i = 1; i < n; ++i) {             ((float)i/lineSegment, Pi);        }

代码实现如下:
1)定义一个TPoint类来表示t和P0,P1,P2,P3,P4的关系

public class TPoint {    protected float t;    protected Point point;    public  TPoint(float t,Point point){        this.t = t;        this.point = point;    }}

2)初始化点数P0,P1,P2,P3,P4列表,并且绑定各个点对应的t

 public void bindTPoint(){        //pointList是一个ArrayList        size = pointList.size();        tPoints = new TPoint[size];        tPoints[0] = new TPoint(0f,pointList.get(0));        //p0--p1构成的线段        int lineSegment = size -1;        for(int i=1;i<size;i++){            tPoints[i] = new TPoint((float)i/lineSegment,pointList.get(i));        }    }

就这样完成了第一步的工作!

在第一个例子的时候 BezierLine方式提供了startPoint和endPoint两个起止点就可以了,但是现在有若干个点怎么办呢,所以在这里优先重构的的就是BezierLine这个类:

 /**     * 根据线性贝塞尔函数,获取线性贝塞尔曲线上的某个点     * @param t 在[0,1]范围的某一个值     * @param startPoint 贝塞尔曲线开始的点     * @param endPoint 贝塞尔曲线结束的点     * @return 根据t的不同而返回的贝塞尔曲线的点     */    public static Point createBezierLine(float t,Point startPoint,Point endPoint){        float newX = (endPoint.x -startPoint.x)*t + startPoint.x;        float newY = (endPoint.y -startPoint.y)*t + startPoint.y;        return  new Point(newX,newY);    }

注意此时相邻两个点组成的路径的范围t仍然为[0,1];只不过t要换一种方法来解释,t对于相邻点之间有点类似于求进度的算法,一个作为起点一个作为终点,其数学公式如下:
这里写图片描述

那么根据上面的公式,根据当前时间获取最新位置点Point对象的代码如下:

public Point getNewPoint(){        //当前时间        long currentTime = System.currentTimeMillis();        //当前时间进度        float currentProgress = (float) (currentTime - startTime) / DURATION ;        if(currentProgress>1.0){            finish = true;        }        //判断当前时间进度是在哪一个线段上        TPoint prePoint = tPoints[0];         for(int i=1;i<size;i++){             TPoint nextPoint = tPoints[i];             //运动的点在prePoint和nextPoint之间             if(currentProgress<nextPoint.t){                //当前点在当前路径的进度                 float progress = (currentProgress-prePoint.t)/(nextPoint.t-prePoint.t);                 return BezierLine.createBezierLine(progress,                         prePoint.point, nextPoint.point);             }             prePoint = nextPoint;         }//end for        //其实这一步感觉不应该返回        return tPoints[size-1].point;    }

那么有了这个getNewPoint方法,直接调用第一个Demo中的updateViewLocation方法即可,同样是用handler来发送消息,并跟新位置,详细见文章最后代码下载链接,运行效果如图:
这里写图片描述
其实上面分析了这么多有点啰里啰嗦了,总结下来基本的算法思路很简单(在各个线段耗时相等的情况下,假设view从P0出发):
1)分配View 从P0到达P1,P2,P3,P4到这几个点的时间节点
1)根据当前时间和开始时间以及步骤1计算此时view应该位于哪一条路径上
2)计算view在当前路径相对当前路径起始点的进度。比如view此时位于p(n-1)和pn这条路径上,那么此进度当前路径的贝塞尔曲线的t值

本来本篇就涉及到属性动画的介绍的,但是昨天因为搬家折腾了一天,一篇博客写了两天,这样的话思路有点混乱了感觉,下一篇分析属性动画的初始化流程,不过感觉应该跟本文的思路相差不大;另外本篇只讨论了线性的,抛物线的运动轨迹改下createBezierLine的方程就OK了,就不多做说明。如有不当之处,欢迎批评指正,老规矩最后上代码:下载链接

1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 商标撕不下来怎么办 商标撕不下来时怎么办 商标还没下来怎么办 商标揭不下来怎么办 手机程序出现异常怎么办 公众号企业名称是*怎么办 家人生命受到威胁怎么办 海淘看不懂英文怎么办 对英语不感兴趣怎么办 装修无合同起诉怎么办 上海离职后档案怎么办 公司注销了银行帐户怎么办 360借条注销了怎么办 注销营业执照公章丢失怎么办 工厂招聘信息有假怎么办 个体餐饮怎么办核名 见父母后接下来怎么办 工商核名重名怎么办 核名通知书过期怎么办 包头鼎太风华怎么办 用人单位不续签劳动合同怎么办 全是英文看不懂怎么办 孩子智力发育晚怎么办 公司比赛成绩不好怎么办 解压手续过期了怎么办 违章通知单丢了怎么办 住在朋友家怎么办暂住证 告知单丢了怎么办 身份证丢了怎么办暂住证 合肥居住证失效了怎么办 异地办牌照暂住证怎么办 外地牌照上保险怎么办 住公租房怎么办暂住证 暂住证怎么办 异地学车 外地人怎么办北京市工作居住证 电大挂科很多怎么办 南通电大挂科怎么办 大学出现挂科怎么办 在北京没暂住证怎么办 我想买北京车牌怎么办 想买北京车牌怎么办