
這還只是張圖片
本文原創(chuàng),這篇可不能匿名轉(zhuǎn)載。
背景:我一哥們公司做智能設(shè)備的,該動(dòng)畫用在手機(jī)和家中網(wǎng)絡(luò)連接時(shí)用,他讓我看了下需求。剛看到這動(dòng)畫時(shí)感覺產(chǎn)品\UI設(shè)計(jì)的不錯(cuò),想著試試。昨天開始做的,本來感覺很簡(jiǎn)單,但做起來貌似沒那么簡(jiǎn)單;最后花了近一天時(shí)間終于搞定了??纯葱Ч€行!

niceloading.gif
- 如果有想直接用的同道中人,看前半部分就行;如果想批評(píng)指正我的思考的看看后半部分
1.直接上代碼(NiceLoadingView)
package com.hadisi.niceloading;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
/**
* Created by hadisi5216 on 2016/7/12.
*/
public class NiceLoadingView extends View {
private Context mContext;
private Paint mPaint;
private int widthSpecSize;
private int heightSpecSize;
private int radiusSmall = 38;
private int radiusbig = 76;
private int moveX;
private int XPoint;
private int mState = -1;//0失敗,1成功,-1默認(rèn)
private boolean mflag;
private ValueAnimator animator;
public NiceLoadingView(Context context) {
super(context);
}
public NiceLoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NiceLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
mPaint = new Paint();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(0xFFFFBC53);
mPaint.setAntiAlias(true);
if (Math.abs(moveX) > widthSpecSize * 5 / 4) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize * 7 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 7 / 4 + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize && Math.abs(moveX) < widthSpecSize * 3 / 2) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize * 3 / 2 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 3 / 2 + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize * 3 / 4 && Math.abs(moveX) < widthSpecSize * 5 / 4) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize * 5 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 5 / 4 + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize / 2 && Math.abs(moveX) < widthSpecSize) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize - Math.abs(moveX) : widthSpecSize - widthSpecSize + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize / 4 && Math.abs(moveX) < widthSpecSize * 3 / 4) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize * 3 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 3 / 4 + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > 0 && Math.abs(moveX) < widthSpecSize / 2) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize / 2 - Math.abs(moveX) : widthSpecSize - widthSpecSize / 2 + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
//中間大圓
if (Math.abs(moveX) > 0 && Math.abs(moveX) < widthSpecSize * 5 / 4) {
radiusbig = 2 * radiusSmall - radiusSmall * (Math.abs(moveX)) / (widthSpecSize * 5 / 4);
radiusbig = (radiusbig > radiusSmall) ? radiusbig : radiusSmall;
canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
}
if (Math.abs(moveX) < 12 && mState >= 0) {
if (mState == 0) {
canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.connect_failed);
canvas.drawBitmap(bitmap, null, new Rect(widthSpecSize / 2 - radiusbig, heightSpecSize / 2 - radiusbig, widthSpecSize / 2 + radiusbig, heightSpecSize / 2 + radiusbig), mPaint);
}
if (mState == 1) {
canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.connect_success);
canvas.drawBitmap(bitmap, null, new Rect(widthSpecSize / 2 - radiusbig, heightSpecSize / 2 - radiusbig, widthSpecSize / 2 + radiusbig, heightSpecSize / 2 + radiusbig), mPaint);
}
}
}
public void start() {
if (animator != null)
animator.cancel();
moveX = widthSpecSize * (-9 / 4);
mState = -1;
mflag = true;
post(new Runnable() {
@Override
public void run() {
animator = ValueAnimator.ofFloat(0f, 1.0f);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (mState < 0) {
moveX = (moveX > widthSpecSize * 7 / 4) ? widthSpecSize * (-9 / 4) : moveX + 12;
} else {
if (moveX > 0)
moveX = (moveX > widthSpecSize * 7 / 4) ? widthSpecSize * (-9 / 4) : moveX + 12;
else if (moveX < 0 && mflag) {
moveX += 12;
if (Math.abs(moveX) < 12)
mflag = false;
}
}
postInvalidate();
}
});
animator.start();
}
});
}
public void success() {
mState = 1;
}
public void failed() {
mState = 0;
}
}
項(xiàng)目已上傳到github,戳著
2.怎么用?
- 布局文件中
<com.hadisi.niceloading.NiceLoadingView
android:id="@+id/nice_loading"
android:layout_width="match_parent"
android:layout_height="100dp" />
- 你要用的地方
NiceLoadingView niceLoading = (NiceLoadingView) findViewById(R.id.nice_loading);
……
//開始連接時(shí)
niceLoading.start();
……
//連接成功時(shí)
niceLoading.success();
……
//連接失敗時(shí)
niceLoading.failed();
3.我怎么實(shí)現(xiàn)的!
仔細(xì)看效果圖可以得出:
1、有6個(gè)小圓依次從屏幕左側(cè)移入屏幕中間,然后又依次從屏幕中間移出屏幕右側(cè)。
2、中間有個(gè)大圓在隨著小圓的依次靠近慢慢變大,離開慢慢變小;注意在左側(cè)第1個(gè)小圓到達(dá)中間時(shí)才出現(xiàn)大圓,在最后一個(gè)小圓準(zhǔn)備向右側(cè)移動(dòng)時(shí)消失;大圓的半徑在小圓半徑和大圓半徑之間。
3、不管何時(shí)得到成功和失敗的狀態(tài),動(dòng)畫終止時(shí)都是在小圓依次從左邊進(jìn)入中間后。
4、動(dòng)畫完成后顯示成功/失敗圖片和大圓。
-
1. 6個(gè)小圓的運(yùn)動(dòng)
我是這樣想的:當(dāng)?shù)?個(gè)小圓移動(dòng)到widthSpecSize/4(widthSpecSize 為控件的寬度)時(shí)第2個(gè)小圓開始移動(dòng)、當(dāng)?shù)?個(gè)小圓移動(dòng)到widthSpecSize/4 時(shí)第3個(gè)小圓開始移動(dòng)......當(dāng)?shù)?個(gè)小圓移動(dòng)到widthSpecSize/4 時(shí)第6個(gè)小圓開始移動(dòng)、第6個(gè)小圓移動(dòng)到widthSpecSize/2 時(shí)繼續(xù)移動(dòng)、當(dāng)?shù)?個(gè)小圓移動(dòng)到widthSpecSize * 3/4時(shí)第5個(gè)小圓開始移動(dòng)......當(dāng)?shù)?個(gè)小圓移動(dòng)到widthSpecSize * 3/4時(shí)第1個(gè)小圓開始移動(dòng)、最后第1個(gè)小球移出屏幕右側(cè),到此為一個(gè)循環(huán)。
假設(shè)有一個(gè)位移變量moveX,moveX在不斷增加,其變化范圍為(a,b);可以看出按照我的想法,第1個(gè)小圓在范圍的兩邊時(shí)開始移動(dòng)、第6個(gè)小圓在變化范圍的中間部分開始移動(dòng)。
我們可以繼續(xù)假設(shè)變化范圍為(-a,a),這樣第1個(gè)小圓在范圍的絕對(duì)值大時(shí)開始移動(dòng)、第6個(gè)小圓在變化范圍的絕對(duì)值小時(shí)開始移動(dòng);其實(shí)這種重復(fù)的動(dòng)作很容易想到絕對(duì)值控制
找張紙畫下很容易得到moveX的變化范圍在(-widthSpecSize * 7/4 , widthSpecSize * 7/4)之間。

自己畫的圖,有點(diǎn)丑
對(duì)照?qǐng)D很快可以得出下面代碼
if (Math.abs(moveX) > widthSpecSize * 5 / 4) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize * 7 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 7 / 4 + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize && Math.abs(moveX) < widthSpecSize * 3 / 2) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize * 3 / 2 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 3 / 2 + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize * 3 / 4 && Math.abs(moveX) < widthSpecSize * 5 / 4) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize * 5 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 5 / 4 + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize / 2 && Math.abs(moveX) < widthSpecSize) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize - Math.abs(moveX) : widthSpecSize - widthSpecSize + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize / 4 && Math.abs(moveX) < widthSpecSize * 3 / 4) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize * 3 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 3 / 4 + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > 0 && Math.abs(moveX) < widthSpecSize / 2) {
XPoint = (moveX < 0) ? XPoint = widthSpecSize / 2 - Math.abs(moveX) : widthSpecSize - widthSpecSize / 2 + Math.abs(moveX);
canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
-
2. 中間大圓的運(yùn)動(dòng)
中間變化的大圓在左側(cè)第1個(gè)小圓到達(dá)中間時(shí)才出現(xiàn)大圓,在第1個(gè)小圓準(zhǔn)備向右側(cè)移動(dòng)時(shí)消失,變化范圍(-widthSpecSize * 5/4 , widthSpecSize * 5/4)之間。大圓的半徑在小圓半徑和大圓半徑之間,我們用radiusbig = radiusbig - radiusSmall * (Math.abs(moveX)) / (widthSpecSize * 5/4)計(jì)算大圓半徑,可以得到慢慢變大和變小的效果,然后控制在小于radiusSmall時(shí)用radiusSmall。
if (Math.abs(moveX) > 0 && Math.abs(moveX) < widthSpecSize * 5 / 4) {
radiusbig = radiusbig - radiusSmall * (Math.abs(moveX)) / (widthSpecSize * 5 / 4);
radiusbig = (radiusbig > radiusSmall) ? radiusbig : radiusSmall;
canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
}
-
3. 動(dòng)畫終止的控制
正常當(dāng)一個(gè)循環(huán)結(jié)束時(shí)我們需要重新給moveX賦值為widthSpecSize * (-7/4),當(dāng)收到成功或失敗狀態(tài)時(shí)需要判斷當(dāng)前的狀態(tài),等到動(dòng)畫進(jìn)行到結(jié)束狀態(tài)(小圓依次從左邊進(jìn)入中間后)。見下面代碼,mState為當(dāng)前狀態(tài)(0失敗,1成功,-1默認(rèn))。
我重新賦值時(shí)將moveX設(shè)為 * widthSpecSize * (-9/4),因?yàn)橐粋€(gè)循環(huán)結(jié)束后有點(diǎn)停頓會(huì)感覺舒服點(diǎn),這個(gè)無所謂,自己感覺而已*
if (mState < 0) {
moveX = (moveX > widthSpecSize * 7 / 4) ? widthSpecSize * (-9 / 4) : moveX + 12;
} else {
if (moveX > 0)
moveX = (moveX > widthSpecSize * 7 / 4) ? widthSpecSize * (-9 / 4) : moveX + 12;
else if (moveX < 0 && mflag) {
moveX += 12;
if (Math.abs(moveX) < 12)
mflag = false;
}
}
-
4. 顯示成功/失敗圖片
這個(gè)簡(jiǎn)單,在收到成功或失敗狀態(tài),待動(dòng)畫完成時(shí)先畫一個(gè)大圓,再畫一個(gè)bitmap
if (Math.abs(moveX) < 12 && mState >= 0) {
if (mState == 0) {
canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.connect_failed);
canvas.drawBitmap(bitmap, null, new Rect(widthSpecSize / 2 - radiusbig, heightSpecSize / 2 - radiusbig, widthSpecSize / 2 + radiusbig, heightSpecSize / 2 + radiusbig), mPaint);
}
if (mState == 1) {
canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.connect_success);
canvas.drawBitmap(bitmap, null, new Rect(widthSpecSize / 2 - radiusbig, heightSpecSize / 2 - radiusbig, widthSpecSize / 2 + radiusbig, heightSpecSize / 2 + radiusbig), mPaint);
}
}
4.優(yōu)化
- 可以優(yōu)化,將paint的顏色等屬性、大小圓的半徑、優(yōu)化畫小圓的邏輯,使小圓個(gè)數(shù)可變等抽象出來..........
其實(shí)核心的就是想法,隨便怎么優(yōu)化。反正我就弄到這了,油而不膩,我覺得挺好,不需要太多優(yōu)化。吼吼....