Android自定義View初探(二)——仿360垃圾清理
明天就是五一勞動節了,在這裡先祝各位程式猿勞動節快樂,別在加班了!
自從嘗試過寫自定義View(Android自定義View初探(一)——餅圖)之後,每當看到別人的應用時,總是在想別人的實現方式,或許,這就是程式猿的悲哀吧O(∩_∩)O~。
前兩天就想嘗試去用自定義View實現360的垃圾清理介面了,只是最近一直在忙dicuz自定義修改,所以就先放下了。不過馬上放五一了,沒太多事,今天就來做一些新的嘗試吧。
我這裡既然寫到了是“仿”360,所以大家千萬別在看完後丟磚頭哈,畢竟我只看到了它的表象,誰知道它是個神馬呢?
其實當時看完它的介面後覺得應該就是簡單的背景色切換隨機元素的繪製:
1.剛進介面的時候是一種顏色,比如深綠,點選“開始掃描”(360可木有,他是自動掃描);
2.當垃圾的數量達到一定值後,比如100MB,背景色變色,比如藍色,;當垃圾值達到200MB的時候再變色,比如紅色;
3.垃圾值的增量(就是那些飛來飛去的小垃圾塊)的位置是隨機的,只不過蒐集垃圾時,座標是從四面往中間移動;釋放垃圾時,座標是從中間往四面移動(效果可自己隨意定);
4.釋放垃圾時,按鈕變成了進度條(這個可以實現,但我沒做^_^)。
5.還有些細節有待發現。。。
說了這麼多,大家也不知道我在說什麼^_^,我就不嘮叨了,程式碼有註釋。
照例先上效果圖:
初始狀態
蒐集狀態-小於100MB
蒐集狀態-大於100MB小於300MB
蒐集狀態-大於300MB
蒐集完成狀態
釋放狀態和蒐集狀態的顏色變化是相同的,不同的是小垃圾的移動方式,這裡就不貼圖片了,太多了。
下面就貼大家最喜歡大程式碼了^_^:
/**
* @author MR.yan
*
*/
public class ShaderView extends View
{
/**
* 繪畫初始狀態
*/
private static final int STATE_DRAW_INIT = 1;
/**
* 繪畫垃圾蒐集中狀態
*/
private static final int STATE_DRAW_COLLECTING_GABAGE = STATE_DRAW_INIT + 1;
/**
* 繪畫垃圾蒐集完成狀態
*/
private static final int STATE_DRAW_COLLECTED_GABAGE = STATE_DRAW_INIT + 2;
/**
* 繪畫垃圾搜釋放中狀態
*/
private static final int STATE_DRAW_RELEASE_GABAGE = STATE_DRAW_INIT + 3;
/**
* 預設水平間距(dp)
*/
private static final float DEFAULT_WIDTH_PADDING = 15;
/**
* 預設垂直間距(dp)
*/
private static final float DEFAULT_HEIGHT_PADDING = 5;
/**
* 畫筆
*/
private Paint paint;
/**
* 螢幕密度
*/
private float density;
/**
* 螢幕寬度px
*/
private float width;
/**
* 螢幕高度px
*/
private float height;
/**
* 當前繪畫狀態
*/
private int mState = STATE_DRAW_INIT;
/**
* 圓角矩形
*/
private RectF rectF;
/**
* 矩形
*/
private Rect rect;
/**
* 隨機數器
*/
private Random random = new Random();
/**
* 已蒐集的垃圾總數
*/
private float gabageSize = 0.0f;
/**
* 垃圾邏輯處理的執行緒開關
*/
private boolean isRun;
/**
* 小垃圾的x座標(px)
*/
private float littleGabageX;
/**
* 小垃圾的y座標(px)
*/
private float littleGabageY;
/**
* 每次隨機產生的小垃圾數量
*/
private float tempgabage;
/**
* 已清理的總的垃圾數量
*/
private float allGabages;
/**
* 小數格式化工具
*/
private DecimalFormat decimalFormat;
/**
* 初始化標識
*/
private boolean isInit;
/**
* 背景色
*/
private int bgColor = Color.parseColor("#29A600");
public ShaderView(Context context)
{
this(context, null);
}
public ShaderView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public ShaderView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init(context);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
super.onLayout(changed, l, t, r, b);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 初始化
*
* @param c 上下文
*/
private void init(Context c)
{
density = c.getResources().getDisplayMetrics().density;
paint = new Paint();
rectF = new RectF();
rect = new Rect();
decimalFormat = new DecimalFormat();
decimalFormat.setMaximumFractionDigits(2);
}
/**
* 畫背景色
*
* @param canvas
*/
private void drawBackground(Canvas canvas)
{
if (!isInit)
{
isInit = !isInit;
width = getWidth();
height = getHeight();
}
paint.setColor(bgColor);
canvas.drawRect(0, 0, width, height, paint);
}
/**
* 畫位置不變的部分
*
* @param canvas 畫布
* @param gabages 蒐集的垃圾數量
* @param title 垃圾數量上面的標題
* @param endDescription 垃圾數量右下角的描述
* @param uinTg 垃圾的單位(MB、GB)
* @param btnDescription 按鈕文字
*/
private void drawStationaryChildren(Canvas canvas, String gabages, String title, String endDescription, String uinTg, String btnDescription)
{
resetPaint();
paint.setColor(Color.WHITE);
paint.setTextSize(15 * density);
canvas.drawText(title, width / 5, height / 3, paint);
paint.setTextSize(75 * density);
paint.setTypeface(Typeface.create("System", Typeface.BOLD));
paint.getTextBounds(gabages, 0, gabages.length(), rect);
canvas.drawText(gabages, width / 4, height / 3 + 2 * rect.height(), paint);
paint.setTextSize(18 * density);
paint.setTypeface(Typeface.create("System", Typeface.NORMAL));
canvas.drawText(uinTg, width / 4 + rect.width() + DEFAULT_WIDTH_PADDING / 5 * density, height / 3 + rect.height(), paint);
canvas.drawText(endDescription, width / 4 + rect.width() + DEFAULT_WIDTH_PADDING / 5 * density, height / 3 + 2 * rect.height()
+ DEFAULT_HEIGHT_PADDING * density, paint);
paint.setColor(Color.parseColor("#50B62E"));
rectF.set(width / 4, height - DEFAULT_HEIGHT_PADDING * 24 * density, width - width / 4, height - DEFAULT_HEIGHT_PADDING * 15 * density);
canvas.drawRoundRect(rectF, 55f, 55f, paint);
//想畫進度條的可以在這裡再畫一個矩形覆蓋在原來矩形上面,新矩形的寬度是已清理的垃圾數量
paint.setColor(Color.WHITE);
paint.setTextSize(25 * density);
paint.getTextBounds(btnDescription, 0, btnDescription.length(), rect);
canvas.drawText(btnDescription, width / 2 - rect.width() / 2, height - DEFAULT_HEIGHT_PADDING * 15 * density - rect.height() / 2, paint);
}
/**
* 畫不固定的小垃圾
*
* @param canvas 畫布
*
*/
private void drawNotFixChildren(Canvas canvas)
{
if (mState != STATE_DRAW_COLLECTING_GABAGE && mState != STATE_DRAW_RELEASE_GABAGE)
return;
resetPaint();
String tempGabageStr = tempgabage + "MB";
Rect ry = new Rect();
paint.setColor(bgColor);
paint.setTextSize(16 * density);
paint.getTextBounds(tempGabageStr, 0, tempGabageStr.length(), ry);
RectF rx = new RectF(littleGabageX, littleGabageY - 2 * ry.height(), littleGabageX + ry.width(), littleGabageY);//不建議在這裡new矩形,如果放在onDraw方法裡,會有警告
canvas.drawRoundRect(rx, 2, 2, paint);
paint.setColor(Color.WHITE);
canvas.drawText(tempGabageStr, littleGabageX, littleGabageY, paint);
}
/**
* 畫筆重置
*/
private void resetPaint()
{
paint.reset();
paint.setAntiAlias(true);
}
/**
* 繪畫的邏輯處理器
*/
private Runnable drawRunnable = new Runnable()
{
public void run()
{
// 執行緒暫停或當蒐集垃圾時蒐集的數量大於500或當釋放垃圾時剩餘的垃圾數量小於0
if (!isRun || (mState == STATE_DRAW_COLLECTING_GABAGE && gabageSize > 500) || (mState == STATE_DRAW_RELEASE_GABAGE && gabageSize < 0))
{
switch (mState)
{
case STATE_DRAW_COLLECTING_GABAGE:
mState = STATE_DRAW_COLLECTED_GABAGE;
allGabages += gabageSize;
break;
case STATE_DRAW_RELEASE_GABAGE:
mState = STATE_DRAW_INIT;
break;
default:
break;
}
postInvalidate();
return;
}
switch (mState)
{
case STATE_DRAW_COLLECTING_GABAGE:
dealCollectGabage();
break;
case STATE_DRAW_RELEASE_GABAGE:
dealRelaseGabage();
break;
default:
break;
}
// 改變背景色的判斷
if (gabageSize <= 100)
bgColor = Color.GREEN;
else if (gabageSize <= 300)
bgColor = Color.MAGENTA;
else bgColor = Color.RED;
postInvalidate();
postDelayed(this, 5);
}
};
/**
* 處理垃圾蒐集
*/
private void dealCollectGabage()
{
if (littleGabageX >= (width / 2 - 50) && littleGabageX <= (width / 2 + 50) && littleGabageY >= (height / 2 - 50)
&& littleGabageY <= (height / 2 + 50))
{
resetCollectDatas();
}
else
{
if (littleGabageX > width / 2)
littleGabageX -= 43.000024;
else if (littleGabageX < width / 2)
littleGabageX += 33.000024;
if (littleGabageY > height / 2)
littleGabageY -= 33.000024;
else if (littleGabageY < height / 2)
littleGabageY += 33.000024;
}
}
/**
* 處理垃圾釋放
*/
private void dealRelaseGabage()
{
if ((littleGabageX <= 50 || littleGabageX >= width - 50) && (littleGabageY <= 50 || littleGabageY >= height - 50))
{
resetReleaseDatas();
}
else
{
if (littleGabageX > width / 2)
littleGabageX += 63.000024;
else if (littleGabageX < width / 2)
littleGabageX -= 63.000024;
if (littleGabageY > height / 2)
littleGabageY += 63.000024;
else if (littleGabageY < height / 2)
littleGabageY -= 63.000024;
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
float dX = event.getX();
float dY = event.getY();
// 比對當前按下的座標和上面畫的按鈕(圓角矩形)的座標範圍,在按鈕座標範圍內則觸發
if ((dX >= width / 4 && dX <= width - width / 4)
&& (dY <= height - DEFAULT_HEIGHT_PADDING * 15 * density && dY >= height - DEFAULT_HEIGHT_PADDING * 24 * density))
{
switch (mState)
{
case STATE_DRAW_INIT:
mState = STATE_DRAW_COLLECTING_GABAGE;
startCollectGabage();
break;
case STATE_DRAW_COLLECTED_GABAGE:
mState = STATE_DRAW_RELEASE_GABAGE;
startReleaseGabage();
break;
default:
break;
}
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
/**
* 開始處理垃圾蒐集
*/
private void startCollectGabage()
{
gabageSize = 0;
resetCollectDatas();
postDelayed(drawRunnable, 10);
if (!isRun)
isRun = true;
}
/**
* 開始處理垃圾釋放
*/
private void startReleaseGabage()
{
resetReleaseDatas();
postDelayed(drawRunnable, 10);
if (!isRun)
isRun = true;
}
/**
* 重置釋放垃圾時小垃圾座標和垃圾值等
*/
private void resetReleaseDatas()
{
int c = random.nextInt(4);
switch (c)
{
case 0:
littleGabageX = width / 2 + 5;
littleGabageY = height / 2 + 5;
break;
case 1:
littleGabageX = width / 2 - 5;
littleGabageY = height / 2 - 5;
break;
case 2:
littleGabageX = width / 2 + 5;
littleGabageY = height / 2 - 5;
break;
case 3:
littleGabageX = width / 2 - 5;
littleGabageY = height / 2 + 5;
break;
default:
break;
}
tempgabage = -5 - (float) random.nextInt(15);
gabageSize += tempgabage;
}
/**
* 重置蒐集垃圾時小垃圾座標和垃圾值等
*/
private void resetCollectDatas()
{
littleGabageX = 100 + (float) random.nextInt((int) width - 200);
littleGabageY = 100 + (float) random.nextInt((int) height - 200);
tempgabage = 5 + (float) random.nextInt(15);
gabageSize += tempgabage;
}
@Override
protected void onDraw(Canvas canvas)
{
resetPaint();
String gabage = "", title = "", endTag = "", btnDescription = "";
float gabg = 0;
switch (mState)
{
case STATE_DRAW_INIT:
bgColor = Color.parseColor("#29A600");
gabg = allGabages;
title = "累計清理 : ";
endTag = "垃圾檔案";
btnDescription = "開始掃描";
break;
case STATE_DRAW_COLLECTING_GABAGE:
gabg = gabageSize;
title = "";
btnDescription = "正在掃描...";
endTag = "建議清理";
break;
case STATE_DRAW_COLLECTED_GABAGE:
gabg = gabageSize;
title = "";
endTag = "建議清理";
btnDescription = "一鍵清理";
break;
case STATE_DRAW_RELEASE_GABAGE:
gabg = gabageSize;
title = "";
endTag = "建議清理";
btnDescription = "正在清理...";
break;
}
gabage = gabg >= 1000 ? decimalFormat.format(gabg / 1024) : String.valueOf(gabg);
drawBackground(canvas);
drawNotFixChildren(canvas);
drawStationaryChildren(canvas, gabage, title, endTag, gabg >= 1000 ? "GB" : "MB", btnDescription);
}
}
整個檢視的大致思路、效果和程式碼就是這樣子了,我依然還是處於嘗試的過程中,肯定有很多地方有不足之處,希望大家交流討論,共同進步!