一、前言
1.我一直想寫一篇關於運動的文章,現在總算
千呼萬喚始出來
了。
2.本篇是一個長篇,各位看官自備水果、飲料、花生米,相信會給你會吃的很開心。
3.本專案原始碼見文尾捷文規範
第一條
先看一下幾個效果:(留圖鎮樓)
1.---瘋狂的分裂
2.---粉身碎骨
3.---畫筆疊合XOR
1.前置知識論述:
1).何為運動:視覺上看是一個物體在不同的時間軸上表現出不同的物理位置
2).位移 = 初位移 + 速度 * 時間
小學生的知識不多說
3).速度 = 初速度 + 加速度 * 時間
初中生的知識不多說
4).時間、位移、速度、加速度構成了現代科學的運動體系
2.使用View對運動學的模擬
1.時間:ValueAnimator的恆定無限執行----模擬時間流,每次重新整理間隔,記為:
1U
2.位移:物體在螢幕畫素位置----模擬世界,每個畫素距離記為:1px
3.速度(單位px/U)、加速度(px/U^2):自定義
注意:無論什麼語言,只要能夠模擬時間與位移,本篇的思想都可以適用,只是語法不同罷了
3.測試的物體,封裝類:
public class Ball implements Cloneable {
public float aX;//加速度
public float aY;//加速度Y
public float vX;//速度X
public float vY;//速度Y
public float x;//點位X
public float y;//點位Y
public int color;//顏色
public float r;//半徑
public Ball clone() {
Ball clone = null;
try {
clone = (Ball) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
複製程式碼
第一節:物體的勻速直線運動:
1.搭建測試View
開始是一個位於0,0點、x方向速度10、y方向速度0的小球
public class RunBall extends View {
private ValueAnimator mAnimator;//時間流
private Ball mBall;//小球物件
private Paint mPaint;//主畫筆
private Point mCoo;//座標系
private float defaultR = 20;//預設小球半徑
private int defaultColor = Color.BLUE;//預設小球顏色
private float defaultVX = 10;//預設小球x方向速度
private float defaultVY = 0;//預設小球y方向速度
public RunBall(Context context) {
this(context, null);
}
public RunBall(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mCoo = new Point(500, 500);
//初始化小球
mBall = new Ball();
mBall.color = defaultColor;
mBall.r = defaultR;
mBall.vX = defaultVX;
mBall.vY = defaultVY;
mBall.a = defaultA;
//初始畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//初始化時間流ValueAnimator
mAnimator = ValueAnimator.ofFloat(0, 1);
mAnimator.setRepeatCount(-1);
mAnimator.setDuration(1000);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateBall();//更新小球資訊
invalidate();
}
});
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(mCoo.x, mCoo.y);
drawBall(canvas, mBall);
canvas.restore();
}
/**
* 繪製小球
* @param canvas
* @param ball
*/
private void drawBall(Canvas canvas, Ball ball) {
mPaint.setColor(ball.color);
canvas.drawCircle(ball.x, ball.y, ball.r, mPaint);
}
/**
* 更新小球
*/
private void updateBall() {
//TODO --運動資料都由此函式變換
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mAnimator.start();//開啟時間流
break;
case MotionEvent.ACTION_UP:
mAnimator.pause();//暫停時間流
break;
}
return true;
}
}
複製程式碼
2.水平運動:
注:開錄屏+模擬器比較卡,加上變成gif,看上去一些卡,真機執行很流暢
RunBall#updateBall:只需加一句(也就是
位移 = 初位移 + 速度 * 時間
,這裡時間是1U)
private void updateBall() {
mBall.x += mBall.vX;
}
複製程式碼
3.反彈效果:(x大於400反彈):
只需反彈時將vX速度取反就行了,和現實一致
private void updateBall() {
mBall.x += mBall.vX;
if (mBall.x > 400) {
mBall.vX = -mBall.vX;
}
}
複製程式碼
4.反彈變色,無限迴圈:
/**
* 更新小球
*/
private void updateBall() {
mBall.x += mBall.vX;
if (mBall.x > 400) {
mBall.vX = -mBall.vX;
mBall.color = ColUtils.randomRGB();//更改顏色
}
if (mBall.x < -400) {
mBall.vX = -mBall.vX;
mBall.color = ColUtils.randomRGB();//更改顏色
}
}
複製程式碼
5.小球的箱式彈跳:
X軸的平移和Y軸的平移基本一致,就不說了,看一下x,y都改變,即速度斜向的情況
先把邊界值定義一下:以便複用
private float defaultVY = 5;//預設小球y方向速度
private float mMaxX = 400;//X最大值
private float mMinX = -400;//X最小值
private float mMaxY = 300;//Y最大值
private float mMinY = -100;//Y最小值
複製程式碼
現在updateBall方法裡新增對Y方向的修改:
/**
* 更新小球
*/
private void updateBall() {
mBall.x += mBall.vX;
mBall.y += mBall.vY;
if (mBall.x > mMaxX) {
mBall.vX = -mBall.vX;
mBall.color = ColUtils.randomRGB();//更改顏色
}
if (mBall.x < mMinX) {
mBall.vX = -mBall.vX;
mBall.color = ColUtils.randomRGB();//更改顏色
}
if (mBall.y > mMaxY) {
mBall.vY = -mBall.vY;
mBall.color = ColUtils.randomRGB();//更改顏色
}
if (mBall.y < mMinY) {
mBall.vY = -mBall.vY;
mBall.color = ColUtils.randomRGB();//更改顏色
}
}
複製程式碼
沒錯,就是這麼簡單,勻速運動做成這樣就差不多了,下面看變速運動
二、變速運動
1.自由落體
首先模擬我們最熟悉的自由落體,加速度aY = 0.98f,x,y初速度為0,初始y高度設為-400
private float defaultR = 20;//預設小球半徑
private int defaultColor = Color.BLUE;//預設小球顏色
private float defaultVX = 0;//預設小球x方向速度
private float defaultVY = 0;//預設小球y方向速度
private float defaultAY = 0.98f;//預設小球加速度
private float mMaxY = 0;//Y最大值
複製程式碼
updateBall里根據豎直加速度aY動態改變vY即可,這裡反彈之後依然會遵循物理定律
注意:你可以在反彈是乘個係數當做損耗值,更能模擬現實
private void updateBall() {
mBall.x += mBall.vX;
mBall.y += mBall.vY;
mBall.vY += mBall.aY;
if (mBall.y > mMaxY - mBall.r) {
mBall.vY = -mBall.vY;
mBall.color = ColUtils.randomRGB();//更改顏色
}
}
複製程式碼
2.平拋運動+模擬碰撞損耗
平拋也就是有一個初始的x方向速度的自由落體
修改初始水平速度和碰撞損耗係數
private float defaultVX = 15;//預設小球x方向速度
private float defaultF = 0.9f;//碰撞損耗
複製程式碼
/**
* 更新小球
*/
private void updateBall() {
mBall.x += mBall.vX;
mBall.y += mBall.vY;
mBall.vY += mBall.aY;
if (mBall.x > mMaxX) {
mBall.x = mMaxX;
mBall.vX = -mBall.vX * defaultF;
mBall.color = ColUtils.randomRGB();//更改顏色
}
if (mBall.x < mMinX) {
mBall.x = mMinX;
mBall.vX = -mBall.vX * defaultF;
mBall.color = ColUtils.randomRGB();//更改顏色
}
if (mBall.y > mMaxY) {
mBall.y = mMaxY;
mBall.vY = -mBall.vY * defaultF;
mBall.color = ColUtils.randomRGB();//更改顏色
}
if (mBall.y < mMinY) {
mBall.y = mMinY;
mBall.vY = -mBall.vY * defaultF;
mBall.color = ColUtils.randomRGB();//更改顏色
}
}
複製程式碼
3.斜拋運動:具有初始水平和垂直速度
修改一下初始垂直速度即可
private float defaultVY = -12;//預設小球y方向速度
複製程式碼
5.圓周運動:
可惜我無法用運動學模擬,需要合速度和合加速度保持不垂直,並且合加速度不變。看以後能不能實現
不過退而求其次,用畫布的旋轉可以讓小球做圓周運動
mark:ValueAnimator預設Interpolator竟然不是線性的,怪不得看著怪怪的
//初始化時間流ValueAnimator
mAnimator = ValueAnimator.ofFloat(0, 1);
mAnimator.setRepeatCount(-1);
mAnimator.setDuration(4000);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDeg = (float) animation.getAnimatedValue() * 360;
updateBall();//更新小球位置
invalidate();
}
});
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(mCoo.x, mCoo.y);
canvas.rotate(mDeg+90);
canvas.drawLine(0, 0, mBall.x, mBall.y, mPaint);
drawBall(canvas, mBall);
canvas.restore();
}
複製程式碼
6.鐘擺運動:
也是非運動學的鐘擺,通過旋轉畫布模擬:
//初始化時間流ValueAnimator
mAnimator = ValueAnimator.ofFloat(0, 1);
mAnimator.setRepeatCount(-1);
mAnimator.setDuration(2000);
mAnimator.setRepeatMode(ValueAnimator.REVERSE);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDeg = (float) animation.getAnimatedValue() * 360*0.5f;
updateBall();//更新小球位置
invalidate();
}
});
複製程式碼
7.估值器實現指定曲線方程運動:(此處sin為例)
/**
* 作者:張風捷特烈<br/>
* 時間:2018/11/16 0016:7:42<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:sin型估值器
*/
public class SinEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
//初始點
Ball startPos = (Ball) startValue;
//結束點
Ball endPos = (Ball) endValue;
//計算每次更新時的x座標
Ball clone = startPos.clone();
clone.x = startPos.x + fraction * (endPos.x - startPos.x);
//將y座標進行聯動
clone.y = (float) (Math.sin(clone.x * Math.PI / 180) * 100);
//返回更新後的點
return clone;
}
}
複製程式碼
//初始化時間流ValueAnimator
Ball startBall = new Ball();//小球的起點
startBall.color = Color.RED;
startBall.r = 20;
Ball endBall = startBall.clone();//小球的終點
endBall.x = 1800;
endBall.y = 300;
//使用ofObject,傳入估值器
mAnimator = ValueAnimator.ofObject(new SinEvaluator(), startBall, endBall);
mAnimator.setRepeatCount(-1);
mAnimator.setDuration(8000);
mAnimator.setRepeatMode(ValueAnimator.REVERSE);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(animation -> {
mBall = (Ball) animation.getAnimatedValue();//通過估值器計算,更新小球
invalidate();
});
複製程式碼
三、效果實現
1.碰撞分裂的效果實現
思路:由繪製一個小球到繪製一個小球集合,每當碰撞時在集合裡新增一個反向的小球
並將兩個小球半徑都減半即可,還是好理解的。
/**
* 作者:張風捷特烈<br/>
* 時間:2018/11/15 0015:8:10<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:小球運動測試
*/
public class RunBall extends View {
private ValueAnimator mAnimator;//時間流
private List<Ball> mBalls;//小球物件
private Paint mPaint;//主畫筆
private Paint mHelpPaint;//輔助線畫筆
private Point mCoo;//座標系
private float defaultR = 80;//預設小球半徑
private int defaultColor = Color.BLUE;//預設小球顏色
private float defaultVX = 10;//預設小球x方向速度
private float defaultF = 0.95f;//碰撞損耗
private float defaultVY = 0;//預設小球y方向速度
private float defaultAY = 0.5f;//預設小球加速度
private float mMaxX = 600;//X最大值
private float mMinX = -200;//X最小值
private float mMaxY = 300;//Y最大值
private float mMinY = -100;//Y最小值
public RunBall(Context context) {
this(context, null);
}
public RunBall(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mCoo = new Point(500, 500);
//初始畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBalls = new ArrayList<>();
Ball ball = initBall();
mBalls.add(ball);
mHelpPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mHelpPaint.setColor(Color.BLACK);
mHelpPaint.setStyle(Paint.Style.FILL);
mHelpPaint.setStrokeWidth(3);
//初始化時間流ValueAnimator
mAnimator = ValueAnimator.ofFloat(0, 1);
mAnimator.setRepeatCount(-1);
mAnimator.setDuration(2000);
mAnimator.setRepeatMode(ValueAnimator.REVERSE);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(animation -> {
updateBall();//更新小球位置
invalidate();
});
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(mCoo.x, mCoo.y);
drawBalls(canvas, mBalls);
canvas.restore();
}
/**
* 繪製小球集合
*
* @param canvas
* @param balls 小球集合
*/
private void drawBalls(Canvas canvas, List<Ball> balls) {
for (Ball ball : balls) {
mPaint.setColor(ball.color);
canvas.drawCircle(ball.x, ball.y, ball.r, mPaint);
}
}
/**
* 更新小球
*/
private void updateBall() {
for (int i = 0; i < mBalls.size(); i++) {
Ball ball = mBalls.get(i);
if (ball.r < 1) {//幫半徑小於1就移除
mBalls.remove(i);
}
ball.x += ball.vX;
ball.y += ball.vY;
ball.vY += ball.aY;
ball.vX += ball.aX;
if (ball.x > mMaxX) {
Ball newBall = ball.clone();//新建一個ball同等資訊的球
newBall.r = newBall.r / 2;
newBall.vX = -newBall.vX;
newBall.vY = -newBall.vY;
mBalls.add(newBall);
ball.x = mMaxX;
ball.vX = -ball.vX * defaultF;
ball.color = ColUtils.randomRGB();//更改顏色
ball.r = ball.r / 2;
}
if (ball.x < mMinX) {
Ball newBall = ball.clone();
newBall.r = newBall.r / 2;
newBall.vX = -newBall.vX;
newBall.vY = -newBall.vY;
mBalls.add(newBall);
ball.x = mMinX;
ball.vX = -ball.vX * defaultF;
ball.color = ColUtils.randomRGB();
ball.r = ball.r / 2;
}
if (ball.y > mMaxY) {
ball.y = mMaxY;
ball.vY = -ball.vY * defaultF;
ball.color = ColUtils.randomRGB();
}
if (ball.y < mMinY) {
ball.y = mMinY;
ball.vY = -ball.vY * defaultF;
ball.color = ColUtils.randomRGB();
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mAnimator.start();
break;
case MotionEvent.ACTION_UP:
// mAnimator.pause();
break;
}
return true;
}
private Ball initBall() {
Ball mBall = new Ball();
mBall.color = defaultColor;
mBall.r = defaultR;
mBall.vX = defaultVX;
mBall.vY = defaultVY;
mBall.aY = defaultAY;
mBall.x = 0;
mBall.y = 0;
return mBall;
}
}
複製程式碼
2.畫筆疊合XOR測試:
//初始化時準備一個小球陣列---引數值隨機一些
private void initBalls() {
for (int i = 0; i < 28; i++) {
Ball mBall = new Ball();
mBall.color = ColUtils.randomRGB();
mBall.r = rangeInt(80, 120);
mBall.vX = (float) (Math.pow(-1, Math.ceil(Math.random() * 1000)) * 20 * Math.random());
mBall.vY = rangeInt(-15, 35);
mBall.aY = 0.98f;
mBall.x = 0;
mBall.y = 0;
mBalls.add(mBall);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//建立一個圖層,在圖層上演示圖形混合後的效果
int sc = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
sc = canvas.saveLayer(new RectF(0, 0, 2500, 2500), null);
}
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));//設定對源的疊合模式
canvas.translate(mCoo.x, mCoo.y);
drawBalls(canvas, mBalls);
canvas.restoreToCount(sc);
}
複製程式碼
3.兩個小球的碰撞反彈
//準備兩個球
private void initBalls() {
for (int i = 0; i < 2; i++) {
Ball mBall = new Ball();
mBall.color = Color.RED;
mBall.r = 80;
mBall.vX = (float) (Math.pow(-1, Math.ceil(Math.random() * 1000)) * 20 * Math.random());
mBall.vY = rangeInt(-15, 35);
mBall.aY = 0.98f;
mBalls.add(mBall);
}
mBalls.get(1).x = 300;
mBalls.get(1).y = 300;
mBalls.get(1).color = Color.BLUE;
}
/**
* 兩點間距離函式
*/
public static float disPos2d(float x1, float y1, float x2, float y2) {
return (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
/**
* 更新小球
*/
private void updateBall() {
Ball redBall = mBalls.get(0);
Ball blueBall = mBalls.get(1);
//校驗兩個小球的距離
if (disPos2d(redBall.x, redBall.y, blueBall.x, blueBall.y) < 80 * 2) {
redBall.vX = -redBall.vX;
redBall.vY = -redBall.vY;
blueBall.vX = -blueBall.vX;
blueBall.vY = -blueBall.vY;
}
for (int i = 0; i < mBalls.size(); i++) {
Ball ball = mBalls.get(i);
ball.x += ball.vX;
ball.y += ball.vY;
ball.vY += ball.aY;
ball.vX += ball.aX;
if (ball.x > mMaxX) {
ball.x = mMaxX;
ball.vX = -ball.vX * defaultF;
}
if (ball.x < mMinX) {
ball.x = mMinX;
ball.vX = -ball.vX * defaultF;
}
if (ball.y > mMaxY) {
ball.y = mMaxY;
ball.vY = -ball.vY * defaultF;
}
if (ball.y < mMinY) {
ball.y = mMinY;
ball.vY = -ball.vY * defaultF;
}
}
}
複製程式碼
好了,就到這裡,關於View的運動還有很多可變化的東西,有興趣的可以去探索一些
後記:捷文規範
1.本文成長記錄及勘誤表
專案原始碼 | 日期 | 備註 |
---|---|---|
V0.1--github | 2018-11-15 | Android原生繪圖之讓你瞭解View的運動 |
2.更多關於我
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
我的github | 我的簡書 | 我的CSDN | 個人網站 |
3.宣告
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援