一个绚丽的loading动效分析与实现!
最終效果如下
從效果上看,我們需要考慮以下幾個問題:
1.葉子的隨機產生;
2.葉子隨著一條正余弦曲線移動;
3.葉子在移動的時候旋轉,旋轉方向隨機,正時針或逆時針;
4.葉子遇到進度條,似乎是融合進入;
5.葉子不能超出最左邊的弧角;
7.葉子飄出時的角度不是一致,走的曲線的振幅也有差別,否則太有規律性,缺乏美感;
總的看起來,需要注意和麻煩的地方主要是以上幾點,當然還有一些細節問題,比如最左邊是圓弧等等;
那接下來我們將效果進行分解,然后逐個擊破:
整個效果來說,我們需要的圖主要是飛動的小葉子和右邊旋轉的風扇,其他的部分都可以用色值進行繪制,當然我們為了方便,就連底部框一起切了;
先從gif 圖里把飛動的小葉子和右邊旋轉的風扇、底部框摳出來,小葉子圖如下:
我們需要處理的主要有兩個部分:
1. 隨著進度往前繪制的進度條;
2. 不斷飛出來的小葉片;
我們先處理第一部分 - 隨著進度往前繪制的進度條:
進度條的位置根據外層傳入的 progress 進行計算,可以分為圖中 1、2、3 三個階段:
首先根據進度條的寬度和當前進度、總進度算出當前的位置:
//mProgressWidth為進度條的寬度,根據當前進度算出進度條的位置 mCurrentProgressPosition = mProgressWidth * mProgress / TOTAL_PROGRESS;然后按照上面的邏輯進行繪制,其中需要計算上圖中的紅色弧角角度,計算方法如下:
// 單邊角度 int angle = (int) Math.toDegrees(Math.acos((mArcRadius - mCurrentProgressPosition)/ (float) mArcRadius));Math.acos() -反余弦函數;
Math.toDegrees() - 弧度轉化為角度,Math.toRadians 角度轉化為弧度
所以圓弧的起始點為:
int startAngle = 180 - angle;圓弧劃過的角度為:2 * angle
這一塊的代碼如下:
// mProgressWidth為進度條的寬度,根據當前進度算出進度條的位置 mCurrentProgressPosition = mProgressWidth * mProgress / TOTAL_PROGRESS; // 即當前位置在圖中所示1范圍內 if (mCurrentProgressPosition < mArcRadius) { Log.i(TAG, "mProgress = " + mProgress + "---mCurrentProgressPosition = " + mCurrentProgressPosition + "--mArcProgressWidth" + mArcRadius); // 1.繪制白色ARC,繪制orange ARC // 2.繪制白色矩形 // 1.繪制白色ARC canvas.drawArc(mArcRectF, 90, 180, false, mWhitePaint); // 2.繪制白色矩形 mWhiteRectF.left = mArcRightLocation; canvas.drawRect(mWhiteRectF, mWhitePaint); // 3.繪制棕色 ARC // 單邊角度 int angle = (int) Math.toDegrees(Math.acos((mArcRadius - mCurrentProgressPosition) / (float) mArcRadius)); // 起始的位置 int startAngle = 180 - angle; // 掃過的角度 int sweepAngle = 2 * angle; Log.i(TAG, "startAngle = " + startAngle); canvas.drawArc(mArcRectF, startAngle, sweepAngle, false, mOrangePaint); } else { Log.i(TAG, "mProgress = " + mProgress + "---transfer-----mCurrentProgressPosition = " + mCurrentProgressPosition + "--mArcProgressWidth" + mArcRadius); // 1.繪制white RECT // 2.繪制Orange ARC // 3.繪制orange RECT // 1.繪制white RECT mWhiteRectF.left = mCurrentProgressPosition; canvas.drawRect(mWhiteRectF, mWhitePaint); // 2.繪制Orange ARC canvas.drawArc(mArcRectF, 90, 180, false, mOrangePaint); // 3.繪制orange RECT mOrangeRectF.left = mArcRightLocation; mOrangeRectF.right = mCurrentProgressPosition; canvas.drawRect(mOrangeRectF, mOrangePaint); }葉子部分
首先根據效果情況基本確定出 曲線函數,標準函數方程為:y = A(wx+Q)+h,其中w影響周期,A影響振幅 ,周期T= 2 * Math.PI/w;
根據效果可以看出,周期大致為總進度長度,所以確定w=(float) ((float) 2 * Math.PI /mProgressWidth);
仔細觀察效果,我們可以發現,葉子飄動的過程中振幅不是完全一致的,產生一種錯落的效果,既然如此,我們給葉子定義一個Type,根據Type 確定不同的振幅;
我們創建一個葉子對象:
private class Leaf { // 在繪制部分的位置 float x, y; // 控制葉子飄動的幅度 StartType type; // 旋轉角度 int rotateAngle; // 旋轉方向--0代表順時針,1代表逆時針 int rotateDirection; // 起始時間(ms) long startTime; }類型采用枚舉進行定義,其實就是用來區分不同的振幅:
private enum StartType { LITTLE, MIDDLE, BIG }創建一個LeafFactory類用于創建一個或多個葉子信息:
private class LeafFactory { private static final int MAX_LEAFS = 6; Random random = new Random(); // 生成一個葉子信息 public Leaf generateLeaf() { Leaf leaf = new Leaf(); int randomType = random.nextInt(3); // 隨時類型- 隨機振幅 StartType type = StartType.MIDDLE; switch (randomType) { case 0: break; case 1: type = StartType.LITTLE; break; case 2: type = StartType.BIG; break; default: break; } leaf.type = type; // 隨機起始的旋轉角度 leaf.rotateAngle = random.nextInt(360); // 隨機旋轉方向(順時針或逆時針) leaf.rotateDirection = random.nextInt(2); // 為了產生交錯的感覺,讓開始的時間有一定的隨機性 mAddTime += random.nextInt((int) (LEAF_FLOAT_TIME * 1.5)); leaf.startTime = System.currentTimeMillis() + mAddTime; return leaf; } // 根據最大葉子數產生葉子信息 public List<Leaf> generateLeafs() { return generateLeafs(MAX_LEAFS); } // 根據傳入的葉子數量產生葉子信息 public List<Leaf> generateLeafs(int leafSize) { List<Leaf> leafs = new LinkedList<Leaf>(); for (int i = 0; i < leafSize; i++) { leafs.add(generateLeaf()); } return leafs; }定義兩個常亮分別記錄中等振幅和之間的振幅差:
// 中等振幅大小 private static final int MIDDLE_AMPLITUDE = 13; // 不同類型之間的振幅差距 private static final int AMPLITUDE_DISPARITY = 5; // 中等振幅大小 private int mMiddleAmplitude = MIDDLE_AMPLITUDE; // 振幅差 private int mAmplitudeDisparity = AMPLITUDE_DISPARITY;有了以上信息,我們則可以獲取到葉子的Y值:
// 通過葉子信息獲取當前葉子的Y值 private int getLocationY(Leaf leaf) { // y = A(wx+Q)+h float w = (float) ((float) 2 * Math.PI / mProgressWidth); float a = mMiddleAmplitude; switch (leaf.type) { case LITTLE: // 小振幅 = 中等振幅 - 振幅差 a = mMiddleAmplitude - mAmplitudeDisparity; break; case MIDDLE: a = mMiddleAmplitude; break; case BIG: // 小振幅 = 中等振幅 + 振幅差 a = mMiddleAmplitude + mAmplitudeDisparity; break; default: break; } Log.i(TAG, "---a = " + a + "---w = " + w + "--leaf.x = " + leaf.x); return (int) (a * Math.sin(w * leaf.x)) + mArcRadius * 2 / 3; }繪制葉子
/** * 繪制葉子 * * @param canvas */ private void drawLeafs(Canvas canvas) { long currentTime = System.currentTimeMillis(); for (int i = 0; i < mLeafInfos.size(); i++) { Leaf leaf = mLeafInfos.get(i); if (currentTime > leaf.startTime && leaf.startTime != 0) { // 繪制葉子--根據葉子的類型和當前時間得出葉子的(x,y) getLeafLocation(leaf, currentTime); // 根據時間計算旋轉角度 canvas.save(); // 通過Matrix控制葉子旋轉 Matrix matrix = new Matrix(); float transX = mLeftMargin + leaf.x; float transY = mLeftMargin + leaf.y; Log.i(TAG, "left.x = " + leaf.x + "--leaf.y=" + leaf.y); matrix.postTranslate(transX, transY); // 通過時間關聯旋轉角度,則可以直接通過修改LEAF_ROTATE_TIME調節葉子旋轉快慢 float rotateFraction = ((currentTime - leaf.startTime) % LEAF_ROTATE_TIME) / (float) LEAF_ROTATE_TIME; int angle = (int) (rotateFraction * 360); // 根據葉子旋轉方向確定葉子旋轉角度 int rotate = leaf.rotateDirection == 0 ? angle + leaf.rotateAngle : -angle + leaf.rotateAngle; matrix.postRotate(rotate, transX + mLeafWidth / 2, transY + mLeafHeight / 2); canvas.drawBitmap(mLeafBitmap, matrix, mBitmapPaint); canvas.restore(); } else { continue; } } }LeafLoadingView完整代碼
package com.example.csdnblog4;import java.util.LinkedList; import java.util.List; import java.util.Random;import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.util.AttributeSet; import android.util.Log; import android.view.View;public class LeafLoadingView extends View {private static final String TAG = "LeafLoadingView";// 淡白色private static final int WHITE_COLOR = 0xfffde399;// 橙色private static final int ORANGE_COLOR = 0xffffa800;// 中等振幅大小private static final int MIDDLE_AMPLITUDE = 13;// 不同類型之間的振幅差距private static final int AMPLITUDE_DISPARITY = 5;// 總進度private static final int TOTAL_PROGRESS = 100;// 葉子飄動一個周期所花的時間private static final long LEAF_FLOAT_TIME = 3000;// 葉子旋轉一周需要的時間private static final long LEAF_ROTATE_TIME = 2000;// 用于控制繪制的進度條距離左/上/下的距離private static final int LEFT_MARGIN = 9;// 用于控制繪制的進度條距離右的距離private static final int RIGHT_MARGIN = 25;private int mLeftMargin, mRightMargin;// 中等振幅大小private int mMiddleAmplitude = MIDDLE_AMPLITUDE;// 振幅差private int mAmplitudeDisparity = AMPLITUDE_DISPARITY;// 葉子飄動一個周期所花的時間private long mLeafFloatTime = LEAF_FLOAT_TIME;// 葉子旋轉一周需要的時間private long mLeafRotateTime = LEAF_ROTATE_TIME;private Resources mResources;private Bitmap mLeafBitmap;private int mLeafWidth, mLeafHeight;private Bitmap mOuterBitmap;private Rect mOuterSrcRect, mOuterDestRect;private int mOuterWidth, mOuterHeight;private int mTotalWidth, mTotalHeight;private Paint mBitmapPaint, mWhitePaint, mOrangePaint;private RectF mWhiteRectF, mOrangeRectF, mArcRectF;// 當前進度private int mProgress;// 所繪制的進度條部分的寬度private int mProgressWidth;// 當前所在的繪制的進度條的位置private int mCurrentProgressPosition;// 弧形的半徑private int mArcRadius;// arc的右上角的x坐標,也是矩形x坐標的起始點private int mArcRightLocation;// 用于產生葉子信息private LeafFactory mLeafFactory;// 產生出的葉子信息private List<Leaf> mLeafInfos;// 用于控制隨機增加的時間不抱團private int mAddTime;public LeafLoadingView(Context context, AttributeSet attrs) {super(context, attrs);mResources = getResources();mLeftMargin = UiUtils.dipToPx(context, LEFT_MARGIN);mRightMargin = UiUtils.dipToPx(context, RIGHT_MARGIN);mLeafFloatTime = LEAF_FLOAT_TIME;mLeafRotateTime = LEAF_ROTATE_TIME;initBitmap();initPaint();mLeafFactory = new LeafFactory();mLeafInfos = mLeafFactory.generateLeafs();}private void initPaint() {mBitmapPaint = new Paint();mBitmapPaint.setAntiAlias(true);mBitmapPaint.setDither(true);mBitmapPaint.setFilterBitmap(true);mWhitePaint = new Paint();mWhitePaint.setAntiAlias(true);mWhitePaint.setColor(WHITE_COLOR);mOrangePaint = new Paint();mOrangePaint.setAntiAlias(true);mOrangePaint.setColor(ORANGE_COLOR);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 繪制進度條和葉子// 之所以把葉子放在進度條里繪制,主要是層級原因drawProgressAndLeafs(canvas);// drawLeafs(canvas);canvas.drawBitmap(mOuterBitmap, mOuterSrcRect, mOuterDestRect, mBitmapPaint);postInvalidate();}private void drawProgressAndLeafs(Canvas canvas) {if (mProgress >= TOTAL_PROGRESS) {mProgress = 0;}// mProgressWidth為進度條的寬度,根據當前進度算出進度條的位置mCurrentProgressPosition = mProgressWidth * mProgress / TOTAL_PROGRESS;// 即當前位置在圖中所示1范圍內if (mCurrentProgressPosition < mArcRadius) {Log.i(TAG, "mProgress = " + mProgress + "---mCurrentProgressPosition = "+ mCurrentProgressPosition+ "--mArcProgressWidth" + mArcRadius);// 1.繪制白色ARC,繪制orange ARC// 2.繪制白色矩形// 1.繪制白色ARCcanvas.drawArc(mArcRectF, 90, 180, false, mWhitePaint);// 2.繪制白色矩形mWhiteRectF.left = mArcRightLocation;canvas.drawRect(mWhiteRectF, mWhitePaint);// 繪制葉子drawLeafs(canvas);// 3.繪制棕色 ARC// 單邊角度int angle = (int) Math.toDegrees(Math.acos((mArcRadius - mCurrentProgressPosition)/ (float) mArcRadius));// 起始的位置int startAngle = 180 - angle;// 掃過的角度int sweepAngle = 2 * angle;canvas.drawArc(mArcRectF, startAngle, sweepAngle, false, mOrangePaint);} else {Log.i(TAG, "mProgress = " + mProgress + "---transfer-----mCurrentProgressPosition = "+ mCurrentProgressPosition+ "--mArcProgressWidth" + mArcRadius);// 1.繪制white RECT// 2.繪制Orange ARC// 3.繪制orange RECT// 這個層級進行繪制能讓葉子感覺是融入棕色進度條中// 1.繪制white RECTmWhiteRectF.left = mCurrentProgressPosition;canvas.drawRect(mWhiteRectF, mWhitePaint);// 繪制葉子drawLeafs(canvas);// 2.繪制Orange ARCcanvas.drawArc(mArcRectF, 90, 180, false, mOrangePaint);// 3.繪制orange RECTmOrangeRectF.left = mArcRightLocation;mOrangeRectF.right = mCurrentProgressPosition;canvas.drawRect(mOrangeRectF, mOrangePaint);}}/*** 繪制葉子* * @param canvas*/private void drawLeafs(Canvas canvas) {mLeafRotateTime = mLeafRotateTime <= 0 ? LEAF_ROTATE_TIME : mLeafRotateTime;long currentTime = System.currentTimeMillis();for (int i = 0; i < mLeafInfos.size(); i++) {Leaf leaf = mLeafInfos.get(i);if (currentTime > leaf.startTime && leaf.startTime != 0) {// 繪制葉子--根據葉子的類型和當前時間得出葉子的(x,y)getLeafLocation(leaf, currentTime);// 根據時間計算旋轉角度canvas.save();// 通過Matrix控制葉子旋轉Matrix matrix = new Matrix();float transX = mLeftMargin + leaf.x;float transY = mLeftMargin + leaf.y;Log.i(TAG, "left.x = " + leaf.x + "--leaf.y=" + leaf.y);matrix.postTranslate(transX, transY);// 通過時間關聯旋轉角度,則可以直接通過修改LEAF_ROTATE_TIME調節葉子旋轉快慢float rotateFraction = ((currentTime - leaf.startTime) % mLeafRotateTime)/ (float) mLeafRotateTime;int angle = (int) (rotateFraction * 360);// 根據葉子旋轉方向確定葉子旋轉角度int rotate = leaf.rotateDirection == 0 ? angle + leaf.rotateAngle : -angle+ leaf.rotateAngle;matrix.postRotate(rotate, transX+ mLeafWidth / 2, transY + mLeafHeight / 2);canvas.drawBitmap(mLeafBitmap, matrix, mBitmapPaint);canvas.restore();} else {continue;}}}private void getLeafLocation(Leaf leaf, long currentTime) {long intervalTime = currentTime - leaf.startTime;mLeafFloatTime = mLeafFloatTime <= 0 ? LEAF_FLOAT_TIME : mLeafFloatTime;if (intervalTime < 0) {return;} else if (intervalTime > mLeafFloatTime) {leaf.startTime = System.currentTimeMillis()+ new Random().nextInt((int) mLeafFloatTime);}float fraction = (float) intervalTime / mLeafFloatTime;leaf.x = (int) (mProgressWidth - mProgressWidth * fraction);leaf.y = getLocationY(leaf);}// 通過葉子信息獲取當前葉子的Y值private int getLocationY(Leaf leaf) {// y = A(wx+Q)+hfloat w = (float) ((float) 2 * Math.PI / mProgressWidth);float a = mMiddleAmplitude;switch (leaf.type) {case LITTLE:// 小振幅 = 中等振幅 - 振幅差a = mMiddleAmplitude - mAmplitudeDisparity;break;case MIDDLE:a = mMiddleAmplitude;break;case BIG:// 小振幅 = 中等振幅 + 振幅差a = mMiddleAmplitude + mAmplitudeDisparity;break;default:break;}Log.i(TAG, "---a = " + a + "---w = " + w + "--leaf.x = " + leaf.x);return (int) (a * Math.sin(w * leaf.x)) + mArcRadius * 2 / 3;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);Log.i("test", "widthMeasureSpec=="+MeasureSpec.getSize(widthMeasureSpec)+",heightMeasureSpec=="+MeasureSpec.getSize(heightMeasureSpec));int w=MeasureSpec.getSize(widthMeasureSpec);int h=MeasureSpec.getSize(heightMeasureSpec);mTotalWidth = w;mTotalHeight = h;mProgressWidth = mTotalWidth - mLeftMargin - mRightMargin;mArcRadius = (mTotalHeight - 2 * mLeftMargin) / 2;mOuterSrcRect = new Rect(0, 0, mOuterWidth, mOuterHeight);mOuterDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);mWhiteRectF = new RectF(mLeftMargin + mCurrentProgressPosition, mLeftMargin, mTotalWidth- mRightMargin,mTotalHeight - mLeftMargin);mOrangeRectF = new RectF(mLeftMargin + mArcRadius, mLeftMargin,mCurrentProgressPosition, mTotalHeight - mLeftMargin);mArcRectF = new RectF(mLeftMargin, mLeftMargin, mLeftMargin + 2 * mArcRadius,mTotalHeight - mLeftMargin);mArcRightLocation = mLeftMargin + mArcRadius;}private void initBitmap() {mLeafBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.leaf)).getBitmap();mLeafWidth = mLeafBitmap.getWidth();mLeafHeight = mLeafBitmap.getHeight();mOuterBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.leaf_kuang)).getBitmap();mOuterWidth = mOuterBitmap.getWidth();mOuterHeight = mOuterBitmap.getHeight();}/* @Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mTotalWidth = w;mTotalHeight = h;Log.i("test", "mTotalWidth = " + w+",mTotalHeight=="+h+"getWidth()=="+getWidth()+",getHeight"+getHeight());mProgressWidth = mTotalWidth - mLeftMargin - mRightMargin;mArcRadius = (mTotalHeight - 2 * mLeftMargin) / 2;mOuterSrcRect = new Rect(0, 0, mOuterWidth, mOuterHeight);mOuterDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);mWhiteRectF = new RectF(mLeftMargin + mCurrentProgressPosition, mLeftMargin, mTotalWidth- mRightMargin,mTotalHeight - mLeftMargin);mOrangeRectF = new RectF(mLeftMargin + mArcRadius, mLeftMargin,mCurrentProgressPosition, mTotalHeight - mLeftMargin);mArcRectF = new RectF(mLeftMargin, mLeftMargin, mLeftMargin + 2 * mArcRadius,mTotalHeight - mLeftMargin);mArcRightLocation = mLeftMargin + mArcRadius;}*/private enum StartType {LITTLE, MIDDLE, BIG}/*** 葉子對象,用來記錄葉子主要數據* * @author Ajian_Studio*/private class Leaf {// 在繪制部分的位置float x, y;// 控制葉子飄動的幅度StartType type;// 旋轉角度int rotateAngle;// 旋轉方向--0代表順時針,1代表逆時針int rotateDirection;// 起始時間(ms)long startTime;}private class LeafFactory {private static final int MAX_LEAFS = 8;Random random = new Random();// 生成一個葉子信息public Leaf generateLeaf() {Leaf leaf = new Leaf();int randomType = random.nextInt(3);// 隨時類型- 隨機振幅StartType type = StartType.MIDDLE;switch (randomType) {case 0:break;case 1:type = StartType.LITTLE;break;case 2:type = StartType.BIG;break;default:break;}leaf.type = type;// 隨機起始的旋轉角度leaf.rotateAngle = random.nextInt(360);// 隨機旋轉方向(順時針或逆時針)leaf.rotateDirection = random.nextInt(2);// 為了產生交錯的感覺,讓開始的時間有一定的隨機性mLeafFloatTime = mLeafFloatTime <= 0 ? LEAF_FLOAT_TIME : mLeafFloatTime;mAddTime += random.nextInt((int) (mLeafFloatTime * 2));leaf.startTime = System.currentTimeMillis() + mAddTime;return leaf;}// 根據最大葉子數產生葉子信息public List<Leaf> generateLeafs() {return generateLeafs(MAX_LEAFS);}// 根據傳入的葉子數量產生葉子信息public List<Leaf> generateLeafs(int leafSize) {List<Leaf> leafs = new LinkedList<Leaf>();for (int i = 0; i < leafSize; i++) {leafs.add(generateLeaf());}return leafs;}}/*** 設置中等振幅* * @param amplitude*/public void setMiddleAmplitude(int amplitude) {this.mMiddleAmplitude = amplitude;}/*** 設置振幅差* * @param disparity*/public void setMplitudeDisparity(int disparity) {this.mAmplitudeDisparity = disparity;}/*** 獲取中等振幅* * @param amplitude*/public int getMiddleAmplitude() {return mMiddleAmplitude;}/*** 獲取振幅差* * @param disparity*/public int getMplitudeDisparity() {return mAmplitudeDisparity;}/*** 設置進度* * @param progress*/public void setProgress(int progress) {this.mProgress = progress;postInvalidate();}/*** 設置葉子飄完一個周期所花的時間* * @param time*/public void setLeafFloatTime(long time) {this.mLeafFloatTime = time;}/*** 設置葉子旋轉一周所花的時間* * @param time*/public void setLeafRotateTime(long time) {this.mLeafRotateTime = time;}/*** 獲取葉子飄完一個周期所花的時間*/public long getLeafFloatTime() {mLeafFloatTime = mLeafFloatTime == 0 ? LEAF_FLOAT_TIME : mLeafFloatTime;return mLeafFloatTime;}/*** 獲取葉子旋轉一周所花的時間*/public long getLeafRotateTime() {mLeafRotateTime = mLeafRotateTime == 0 ? LEAF_ROTATE_TIME : mLeafRotateTime;return mLeafRotateTime;} }activity
package com.example.csdnblog4;import java.util.Random;import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.Button; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView;public class LeafLoadingActivity extends Activity implements OnSeekBarChangeListener,OnClickListener {Handler mHandler = new Handler() {public void handleMessage(Message msg) {switch (msg.what) {case REFRESH_PROGRESS:if (mProgress < 40) {mProgress += 1;// 隨機800ms以內刷新一次mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS,new Random().nextInt(800));mLeafLoadingView.setProgress(mProgress);} else {mProgress += 1;// 隨機1200ms以內刷新一次mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS,new Random().nextInt(1200));mLeafLoadingView.setProgress(mProgress);}break;default:break;}};};private static final int REFRESH_PROGRESS = 0x10;private LeafLoadingView mLeafLoadingView;private SeekBar mAmpireSeekBar;private SeekBar mDistanceSeekBar;private TextView mMplitudeText;private TextView mDisparityText;private View mFanView;private Button mClearButton;private int mProgress = 0;private TextView mProgressText;private View mAddProgress;private SeekBar mFloatTimeSeekBar;private SeekBar mRotateTimeSeekBar;private TextView mFloatTimeText;private TextView mRotateTimeText;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.leaf_loading_layout);initViews();mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS, 3000);}private void initViews() {mFanView = findViewById(R.id.fan_pic);RotateAnimation rotateAnimation = AnimationUtils.initRotateAnimation(false, 1500, true,Animation.INFINITE);mFanView.startAnimation(rotateAnimation);mClearButton = (Button) findViewById(R.id.clear_progress);mClearButton.setOnClickListener(this);mLeafLoadingView = (LeafLoadingView) findViewById(R.id.leaf_loading);mMplitudeText = (TextView) findViewById(R.id.text_ampair);mMplitudeText.setText(getString(R.string.current_mplitude,mLeafLoadingView.getMiddleAmplitude()));mDisparityText = (TextView) findViewById(R.id.text_disparity);mDisparityText.setText(getString(R.string.current_Disparity,mLeafLoadingView.getMplitudeDisparity()));mAmpireSeekBar = (SeekBar) findViewById(R.id.seekBar_ampair);mAmpireSeekBar.setOnSeekBarChangeListener(this);mAmpireSeekBar.setProgress(mLeafLoadingView.getMiddleAmplitude());mAmpireSeekBar.setMax(50);mDistanceSeekBar = (SeekBar) findViewById(R.id.seekBar_distance);mDistanceSeekBar.setOnSeekBarChangeListener(this);mDistanceSeekBar.setProgress(mLeafLoadingView.getMplitudeDisparity());mDistanceSeekBar.setMax(20);mAddProgress = findViewById(R.id.add_progress);mAddProgress.setOnClickListener(this);mProgressText = (TextView) findViewById(R.id.text_progress);mFloatTimeText = (TextView) findViewById(R.id.text_float_time);mFloatTimeSeekBar = (SeekBar) findViewById(R.id.seekBar_float_time);mFloatTimeSeekBar.setOnSeekBarChangeListener(this);mFloatTimeSeekBar.setMax(5000);mFloatTimeSeekBar.setProgress((int) mLeafLoadingView.getLeafFloatTime());mFloatTimeText.setText(getResources().getString(R.string.current_float_time,mLeafLoadingView.getLeafFloatTime()));mRotateTimeText = (TextView) findViewById(R.id.text_rotate_time);mRotateTimeSeekBar = (SeekBar) findViewById(R.id.seekBar_rotate_time);mRotateTimeSeekBar.setOnSeekBarChangeListener(this);mRotateTimeSeekBar.setMax(5000);mRotateTimeSeekBar.setProgress((int) mLeafLoadingView.getLeafRotateTime());mRotateTimeText.setText(getResources().getString(R.string.current_float_time,mLeafLoadingView.getLeafRotateTime()));}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {if (seekBar == mAmpireSeekBar) {mLeafLoadingView.setMiddleAmplitude(progress);mMplitudeText.setText(getString(R.string.current_mplitude,progress));} else if (seekBar == mDistanceSeekBar) {mLeafLoadingView.setMplitudeDisparity(progress);mDisparityText.setText(getString(R.string.current_Disparity,progress));} else if (seekBar == mFloatTimeSeekBar) {mLeafLoadingView.setLeafFloatTime(progress);mFloatTimeText.setText(getResources().getString(R.string.current_float_time,progress));}else if (seekBar == mRotateTimeSeekBar) {mLeafLoadingView.setLeafRotateTime(progress);mRotateTimeText.setText(getResources().getString(R.string.current_rotate_time,progress));}}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {}@Overridepublic void onClick(View v) {if (v == mClearButton) {mLeafLoadingView.setProgress(0);mHandler.removeCallbacksAndMessages(null);mProgress = 0;} else if (v == mAddProgress) {mProgress++;mLeafLoadingView.setProgress(mProgress);mProgressText.setText(String.valueOf(mProgress));}} }layout
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#fed255"android:orientation="vertical" ><TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginTop="100dp"android:text="loading ..."android:textColor="#FFA800"android:textSize=" 30dp" /><RelativeLayout android:id="@+id/leaf_content"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="50dp" ><com.example.csdnblog4.LeafLoadingView android:id="@+id/leaf_loading"android:layout_width="302dp"android:layout_height="61dp"android:layout_centerHorizontal="true" /><ImageView android:id="@+id/fan_pic"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:layout_marginRight="35dp"android:src="@drawable/fengshan" /></RelativeLayout><ScrollView android:layout_width="match_parent"android:layout_height="match_parent" ><LinearLayout android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" ><LinearLayout android:id="@+id/seek_content_one"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="15dp"android:layout_marginRight="15dp"android:layout_marginTop="15dp" ><TextView android:id="@+id/text_ampair"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:textColor="#ffffa800"android:textSize="15dp" /><SeekBar android:id="@+id/seekBar_ampair"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginLeft="5dp"android:layout_weight="1" /></LinearLayout><LinearLayout android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="15dp"android:layout_marginRight="15dp"android:layout_marginTop="15dp"android:orientation="horizontal" ><TextView android:id="@+id/text_disparity"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:textColor="#ffffa800"android:textSize="15dp" /><SeekBar android:id="@+id/seekBar_distance"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginLeft="5dp"android:layout_weight="1" /></LinearLayout><LinearLayout android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="15dp"android:layout_marginRight="15dp"android:layout_marginTop="15dp"android:orientation="horizontal" ><TextView android:id="@+id/text_float_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:textColor="#ffffa800"android:textSize="15dp" /><SeekBar android:id="@+id/seekBar_float_time"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginLeft="5dp"android:layout_weight="1" /></LinearLayout><LinearLayout android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="15dp"android:layout_marginRight="15dp"android:layout_marginTop="15dp"android:orientation="horizontal" ><TextView android:id="@+id/text_rotate_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:textColor="#ffffa800"android:textSize="15dp" /><SeekBar android:id="@+id/seekBar_rotate_time"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginLeft="5dp"android:layout_weight="1" /></LinearLayout><Button android:id="@+id/clear_progress"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="15dp"android:text="去除進度條,玩轉弧線"android:textSize="18dp" /><LinearLayout android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="15dp"android:layout_marginRight="15dp"android:layout_marginTop="15dp"android:orientation="horizontal" ><Button android:id="@+id/add_progress"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="增加進度: "android:textSize="18dp" /><TextView android:id="@+id/text_progress"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:textColor="#ffffa800"android:textSize="15dp" /></LinearLayout></LinearLayout></ScrollView></LinearLayout>注意
本文中用到了onSizeChanged方法,這個方法在這是為了自適應界面,每次界面變化的時候都會被調用來重新計算一些參數,一般在剛進入與橫豎屏切換時調用
參考鏈接
Android自定義View初步 - 泡在網上的日子
本文中還用到了postInvalidate
invalidate()和postInvalidate() 的區別及使用,
Android提供了Invalidate方法實現界面刷新,但是Invalidate不能直接在線程中調用,因為他是違背了單線程模型:Android UI操作并不是線程安全的,并且這些操作必須在UI線程中調用。
invalidate()是用來刷新View的,必須是在UI線程中進行工作。比如在修改某個view的顯示時,調用invalidate()才能看到重新繪制的界面。invalidate()的調用是把之前的舊的view從主UI線程隊列中pop掉。 一個Android 程序默認情況下也只有一個進程,但一個進程下卻可以有許多個線程。
在這么多線程當中,把主要是負責控制UI界面的顯示、更新和控件交互的線程稱為UI線程,由于onCreate()方法是由UI線程執行的,所以也可以把UI線程理解為主線程。其余的線程可以理解為工作者線程。
invalidate()得在UI線程中被調動,在工作者線程中可以通過Handler來通知UI線程進行界面更新。
而postInvalidate()在工作者線程中被調用
參考鏈接
Android筆記:invalidate()和postInvalidate() 的區別及使用 - Mars2639——求知de路上 - 博客頻道 - CSDN.NET
本文還涉及到單位的轉化
參考鏈接
Android中dip、dp、sp、pt和px的區別 - 大氣象 - 博客園
最終效果如下
參考鏈接
一個絢麗的loading動效分析與實現! - Ajian_studio - 博客頻道 - CSDN.NET
源代碼下載
源代碼
完成
總結
以上是生活随笔為你收集整理的一个绚丽的loading动效分析与实现!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信息技术智库
- 下一篇: CV中的经典网络模型