1.我一直想寫一篇關於運動的文章,如今總算
千呼萬喚始出來
了。
2.本篇是一個長篇,各位看官自備水果、飲料、花生米,相信會給你會吃的很開心。
3.本項目源碼見文尾捷文規範
第一條android
1).何爲運動:視覺上看是一個物體在不一樣的時間軸上表現出不一樣的物理位置
2).位移 = 初位移 + 速度 * 時間
小學生的知識很少說
3).速度 = 初速度 + 加速度 * 時間
初中生的知識很少說
4).時間、位移、速度、加速度構成了現代科學的運動體系git
1.時間:ValueAnimator的恆定無限執行----模擬時間流,每次刷新間隔,記爲:
1U
2.位移:物體在屏幕像素位置----模擬世界,每一個像素距離記爲:1px
3.速度(單位px/U)、加速度(px/U^2):自定義
注意:不管什麼語言,只要可以模擬時間與位移,本篇的思想均可以適用,只是語法不一樣罷了
github
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;
}
}
複製代碼
開始是一個位於0,0點、x方向速度十、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;
}
}
複製代碼
注:開錄屏+模擬器比較卡,加上變成gif,看上去一些卡,真機運行很流暢canvas
RunBall#updateBall:只需加一句(也就是
位移 = 初位移 + 速度 * 時間
,這裏時間是1U)數組
private void updateBall() {
mBall.x += mBall.vX;
}
複製代碼
只需反彈時將vX速度取反就好了,和現實一致bash
private void updateBall() {
mBall.x += mBall.vX;
if (mBall.x > 400) {
mBall.vX = -mBall.vX;
}
}
複製代碼
/**
* 更新小球
*/
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();//更改顏色
}
}
複製代碼
X軸的平移和Y軸的平移基本一致,就不說了,看一下x,y都改變,即速度斜向的狀況微信
先把邊界值定義一下:以便複用dom
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方向的修改:ide
/**
* 更新小球
*/
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();//更改顏色
}
}
複製代碼
沒錯,就是這麼簡單,勻速運動作成這樣就差很少了,下面看變速運動
首先模擬咱們最熟悉的自由落體,加速度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();//更改顏色
}
}
複製代碼
平拋也就是有一個初始的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();//更改顏色
}
}
複製代碼
修改一下初始垂直速度便可
private float defaultVY = -12;//默認小球y方向速度
複製代碼
惋惜我沒法用運動學模擬,須要合速度和合加速度保持不垂直,而且合加速度不變。看之後能不能實現
不過退而求其次,用畫布的旋轉可讓小球作圓周運動
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();
}
複製代碼
也是非運動學的鐘擺,經過旋轉畫布模擬:
//初始化時間流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();
}
});
複製代碼
/**
* 做者:張風捷特烈<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();
});
複製代碼
思路:由繪製一個小球到繪製一個小球集合,每當碰撞時在集合裏添加一個反向的小球
並將兩個小球半徑都減半便可,仍是好理解的。
/**
* 做者:張風捷特烈<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;
}
}
複製代碼
//初始化時準備一個小球數組---參數值隨機一些
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);
}
複製代碼
//準備兩個球
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的運動還有不少可變化的東西,有興趣的能夠去探索一些
項目源碼 | 日期 | 備註 |
---|---|---|
V0.1--github | 2018-11-15 | Android原生繪圖之讓你瞭解View的運動 |
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
個人github | 個人簡書 | 個人CSDN | 我的網站 |
1----本文由張風捷特烈原創,轉載請註明 2----歡迎廣大編程愛好者共同交流 3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正 4----看到這裏,我在此感謝你的喜歡與支持