Android實(shí)現(xiàn)跳動(dòng)的小球加載動(dòng)畫(huà)效果
先來(lái)看看效果圖
跳動(dòng)的小球做這個(gè)動(dòng)畫(huà),需掌握:
1、屬性動(dòng)畫(huà)
2、Path類(lèi)、Canvas類(lèi)
3、貝塞爾曲線
4、SurfaceView用法
5、自定義attr屬性
6 、架構(gòu): 狀態(tài)模式,控制器
7 、自由落體,拋物線等概念
不多說(shuō)了,直接上碼
1.DancingView.java
public class DancingView extends SurfaceView implements SurfaceHolder.Callback { public static final int STATE_DOWN = 1;//向下?tīng)顟B(tài) public static final int STATE_UP = 2;//向上狀態(tài) public static final int DEFAULT_POINT_RADIUS = 10; public static final int DEFAULT_BALL_RADIUS = 13; public static final int DEFAULT_LINE_WIDTH = 200; public static final int DEFAULT_LINE_HEIGHT = 2; public static final int DEFAULT_LINE_COLOR = Color.parseColor("#FF9800"); public static final int DEFAULT_POINT_COLOR = Color.parseColor("#9C27B0"); public static final int DEFAULT_BALL_COLOR = Color.parseColor("#FF4081"); public static final int DEFAULT_DOWN_DURATION = 600;//ms public static final int DEFAULT_UP_DURATION = 600;//ms public static final int DEFAULT_FREEDOWN_DURATION = 1000;//ms public static final int MAX_OFFSET_Y = 50;//水平下降最大偏移距離 public int PONIT_RADIUS = DEFAULT_POINT_RADIUS;//小球半徑 public int BALL_RADIUS = DEFAULT_BALL_RADIUS;//小球半徑 private Paint mPaint; private Path mPath; private int mLineColor; private int mPonitColor; private int mBallColor; private int mLineWidth; private int mLineHeight; private float mDownDistance; private float mUpDistance; private float freeBallDistance; private ValueAnimator mDownController;//下落控制器 private ValueAnimator mUpController;//上彈控制器 private ValueAnimator mFreeDownController;//自由落體控制器 private AnimatorSet animatorSet; private int state; private boolean ismUpControllerDied = false; private boolean isAnimationShowing = false; private boolean isBounced = false; private boolean isBallFreeUp = false; public DancingView(Context context) { super(context); init(context, null); } public DancingView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public DancingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { initAttributes(context, attrs); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStrokeWidth(mLineHeight); mPaint.setStrokeCap(Paint.Cap.ROUND); mPath = new Path(); getHolder().addCallback(this); initController(); } private void initAttributes(Context context, AttributeSet attrs) { TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.DancingView); mLineColor = typeArray.getColor(R.styleable.DancingView_lineColor, DEFAULT_LINE_COLOR); mLineWidth = typeArray.getDimensionPixelOffset(R.styleable.DancingView_lineWidth, DEFAULT_LINE_WIDTH); mLineHeight = typeArray.getDimensionPixelOffset(R.styleable.DancingView_lineHeight, DEFAULT_LINE_HEIGHT); mPonitColor = typeArray.getColor(R.styleable.DancingView_pointColor, DEFAULT_POINT_COLOR); mBallColor = typeArray.getColor(R.styleable.DancingView_ballColor, DEFAULT_BALL_COLOR); typeArray.recycle(); } private void initController() { mDownController = ValueAnimator.ofFloat(0, 1); mDownController.setDuration(DEFAULT_DOWN_DURATION); mDownController.setInterpolator(new DecelerateInterpolator()); mDownController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mDownDistance = MAX_OFFSET_Y * (float) animation.getAnimatedValue(); postInvalidate(); } }); mDownController.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { state = STATE_DOWN; } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mUpController = ValueAnimator.ofFloat(0, 1); mUpController.setDuration(DEFAULT_UP_DURATION); mUpController.setInterpolator(new DancingInterpolator()); mUpController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mUpDistance = MAX_OFFSET_Y * (float) animation.getAnimatedValue(); if (mUpDistance >= MAX_OFFSET_Y) { //進(jìn)入自由落體狀態(tài) isBounced = true; if (!mFreeDownController.isRunning() && !mFreeDownController.isStarted() && !isBallFreeUp) { mFreeDownController.start(); } } postInvalidate(); } }); mUpController.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { state = STATE_UP; } @Override public void onAnimationEnd(Animator animation) { ismUpControllerDied = true; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); mFreeDownController = ValueAnimator.ofFloat(0, 8f); mFreeDownController.setDuration(DEFAULT_FREEDOWN_DURATION); mFreeDownController.setInterpolator(new DecelerateInterpolator()); mFreeDownController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { //該公式解決上升減速 和 下降加速 float t = (float) animation.getAnimatedValue(); freeBallDistance = 40 * t - 5 * t * t; if (ismUpControllerDied) {//往上拋,到臨界點(diǎn) postInvalidate(); } } }); mFreeDownController.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { isBallFreeUp = true; } @Override public void onAnimationEnd(Animator animation) { isAnimationShowing = false; //循環(huán)第二次 startAnimations(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); animatorSet = new AnimatorSet(); animatorSet.play(mDownController).before(mUpController); animatorSet.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { isAnimationShowing = true; } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } /** * 啟動(dòng)動(dòng)畫(huà),外部調(diào)用 */ public void startAnimations() { if (isAnimationShowing) { return; } if (animatorSet.isRunning()) { animatorSet.end(); animatorSet.cancel(); } isBounced = false; isBallFreeUp = false; ismUpControllerDied = false; animatorSet.start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 一條繩子用左右兩部分的二階貝塞爾曲線組成 mPaint.setColor(mLineColor); mPath.reset(); //起始點(diǎn) mPath.moveTo(getWidth() / 2 - mLineWidth / 2, getHeight() / 2); if (state == STATE_DOWN) {//下落 /**************繪制繩子開(kāi)始*************/ //左部分 的貝塞爾 mPath.quadTo((float) (getWidth() / 2 - mLineWidth / 2 + mLineWidth * 0.375), getHeight() / 2 + mDownDistance, getWidth() / 2, getHeight() / 2 + mDownDistance); //右部分 的貝塞爾 mPath.quadTo((float) (getWidth() / 2 + mLineWidth / 2 - mLineWidth * 0.375), getHeight() / 2 + mDownDistance, getWidth() / 2 + mLineWidth / 2, getHeight() / 2); mPaint.setStyle(Paint.Style.STROKE); canvas.drawPath(mPath, mPaint); /**************繪制繩子結(jié)束*************/ /**************繪制彈跳小球開(kāi)始*************/ mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mBallColor); canvas.drawCircle(getWidth() / 2, getHeight() / 2 + mDownDistance - BALL_RADIUS, BALL_RADIUS, mPaint); /**************繪制彈跳小球結(jié)束*************/ } else if (state == STATE_UP) { //向上彈 /**************繪制繩子開(kāi)始*************/ //左部分的貝塞爾 mPath.quadTo((float) (getWidth() / 2 - mLineWidth / 2 + mLineWidth * 0.375), getHeight() / 2 + 50 - mUpDistance, getWidth() / 2, getHeight() / 2 + (50 - mUpDistance)); //右部分的貝塞爾 mPath.quadTo((float) (getWidth() / 2 + mLineWidth / 2 - mLineWidth * 0.375), getHeight() / 2 + 50 - mUpDistance, getWidth() / 2 + mLineWidth / 2, getHeight() / 2); mPaint.setStyle(Paint.Style.STROKE); canvas.drawPath(mPath, mPaint); /**************繪制繩子結(jié)束*************/ mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mBallColor); //彈性小球,自由落體 if (!isBounced) { //上升 canvas.drawCircle(getWidth() / 2, getHeight() / 2 + (MAX_OFFSET_Y - mUpDistance) - BALL_RADIUS, BALL_RADIUS, mPaint); } else { //自由落體 canvas.drawCircle(getWidth() / 2, getHeight() / 2 - freeBallDistance - BALL_RADIUS, BALL_RADIUS, mPaint); } } mPaint.setColor(mPonitColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(getWidth() / 2 - mLineWidth / 2, getHeight() / 2, PONIT_RADIUS, mPaint); canvas.drawCircle(getWidth() / 2 + mLineWidth / 2, getHeight() / 2, PONIT_RADIUS, mPaint); } @Override public void surfaceCreated(SurfaceHolder holder) { Canvas canvas = holder.lockCanvas();//鎖定整個(gè)SurfaceView對(duì)象,獲取該Surface上的Canvas. draw(canvas); holder.unlockCanvasAndPost(canvas);//釋放畫(huà)布,提交修改 } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }
2.DancingInterpolator.java
public class DancingInterpolator implements Interpolator { @Override public float getInterpolation(float input) { return (float) (1 - Math.exp(-3 * input) * Math.cos(10 * input)); } }
3.自定義屬性 styles.xml
<declare-styleable name="DancingView"> <attr name="lineWidth" format="dimension" /> <attr name="lineHeight" format="dimension" /> <attr name="pointColor" format="reference|color" /> <attr name="lineColor" format="reference|color" /> <attr name="ballColor" format="reference|color" /> </declare-styleable>
注意:顏色、尺寸、參數(shù)可以自己測(cè)試,調(diào)整。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)和工作能有所幫助哦。
- Android實(shí)現(xiàn)仿iOS菊花加載圈動(dòng)畫(huà)效果
- Android繪制圓形百分比加載圈效果
- Android自定義加載圈動(dòng)畫(huà)效果
- android動(dòng)態(tài)加載布局文件示例
- Android使用控件ImageView加載圖片的方法
- Android使用glide加載gif動(dòng)畫(huà)設(shè)置播放次數(shù)
- Android中Glide加載圓形圖片和圓角圖片實(shí)例代碼
- Android自定義Dialog實(shí)現(xiàn)加載對(duì)話框效果
- Android通用LoadingView加載框架詳解
- Android實(shí)現(xiàn)加載圈
相關(guān)文章
Android 詳解沉浸式狀態(tài)欄的實(shí)現(xiàn)流程
沉浸式就是要給用戶(hù)提供完全沉浸的體驗(yàn),使用戶(hù)有一種置身于虛擬世界之中的感覺(jué)。沉浸式模式就是整個(gè)屏幕中顯示都是應(yīng)用的內(nèi)容,沒(méi)有狀態(tài)欄也沒(méi)有導(dǎo)航欄,用戶(hù)不會(huì)被一些系統(tǒng)的界面元素所打擾,讓我們來(lái)實(shí)現(xiàn)下網(wǎng)上傳的沸沸揚(yáng)揚(yáng)的安卓沉浸式狀態(tài)欄2021-11-11Flutter質(zhì)感設(shè)計(jì)之彈出菜單
這篇文章主要為大家詳細(xì)介紹了Flutter質(zhì)感設(shè)計(jì)之彈出菜單,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08Android 使用Zbar實(shí)現(xiàn)掃一掃功能
這篇文章主要介紹了Android 使用Zbar實(shí)現(xiàn)掃一掃功能,本文用的是Zbar實(shí)現(xiàn)掃一掃,因?yàn)楦鶕?jù)本人對(duì)兩個(gè)庫(kù)的使用比較,發(fā)現(xiàn)Zbar解碼比Zxing速度要快,實(shí)現(xiàn)方式也簡(jiǎn)單,需要的朋友可以參考下2023-03-03Android開(kāi)發(fā)之ProgressDialog進(jìn)度對(duì)話框用法示例
這篇文章主要介紹了Android開(kāi)發(fā)之ProgressDialog進(jìn)度對(duì)話框用法,簡(jiǎn)單介紹了ProgressDialog進(jìn)度對(duì)話框常見(jiàn)函數(shù)功能,并結(jié)合實(shí)例形式分析了ProgressDialog組件創(chuàng)建及使用進(jìn)度對(duì)話框相關(guān)操作技巧,需要的朋友可以參考下2019-03-03Android開(kāi)發(fā)手冊(cè)Chip監(jiān)聽(tīng)及ChipGroup監(jiān)聽(tīng)
這篇文章主要為大家介紹了Android開(kāi)發(fā)手冊(cè)Chip監(jiān)聽(tīng)及ChipGroup監(jiān)聽(tīng),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android GridView不改變背景色實(shí)現(xiàn)網(wǎng)格線效果
這篇文章主要介紹了Android GridView不改變背景色實(shí)現(xiàn)網(wǎng)格線效果,需要的朋友可以參考下2016-03-03Android開(kāi)發(fā)者需要知道的8個(gè)項(xiàng)目管理技巧
這篇文章主要為大家詳細(xì)介紹了Android開(kāi)發(fā)者需要知道的8個(gè)項(xiàng)目管理技巧,感興趣的小伙伴們可以參考一下2016-02-02Android6.0開(kāi)發(fā)中屏幕旋轉(zhuǎn)原理與流程分析
這篇文章主要介紹了Android6.0開(kāi)發(fā)中屏幕旋轉(zhuǎn)原理與流程,結(jié)合實(shí)例形式詳細(xì)分析了Android6.0屏幕旋轉(zhuǎn)的原理與相關(guān)實(shí)現(xiàn)流程,并附帶了Android動(dòng)態(tài)開(kāi)啟與禁用屏幕旋轉(zhuǎn)的實(shí)現(xiàn)方法,需要的朋友可以參考下2017-11-11