国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

線性貝塞爾曲線的簡單說明(一)

2019-11-07 23:12:06
字體:
來源:轉載
供稿:網友

因為公司開發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了,就不多做說明。如有不當之處,歡迎批評指正,老規矩最后上代碼:下載鏈接


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 手机| 雅江县| 高州市| 九龙县| 神农架林区| 扎赉特旗| 兴安盟| 虞城县| 台安县| 万年县| 临西县| 寿光市| 曲沃县| 安庆市| 金门县| 龙海市| 涿州市| 陕西省| 格尔木市| 铜川市| 白山市| 大厂| 宣恩县| 蕉岭县| 临安市| 蓬安县| 石家庄市| 慈溪市| 龙泉市| 洞口县| 楚雄市| 青海省| 溧水县| 繁峙县| 崇礼县| 望谟县| 宁乡县| 体育| 邵武市| 珠海市| 囊谦县|