前言
閑來無事做,于是就自己寫了個仿魅藍安全中心,檢測時進度元件。首先啥也不說,貼圖來吸引注意力。
呃。用的是模拟器進行錄的屏,是以這個分辨率有點讓我尴尬,效果不是很好。。。
正文
首先重寫onMeasure方法進行測量這個元件的大小,貼下代碼。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int resWidth = ;
int resHeight = ;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//寬高模式是否精度模式
if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
resWidth = getSuggestedMinimumWidth();
resWidth = resWidth == ? getDefaultWidth() : resWidth;
resHeight = getSuggestedMinimumHeight();
resHeight = resHeight == ? getDefaultWidth() : resHeight;
} else {
resWidth = resHeight = Math.min(widthSize, heightSize);
}
//高是寬的1/2倍
setMeasuredDimension(resWidth, resHeight / );
}
流程就是通過穿過來的一個測量資料進行擷取,這個元件的寬高的模式和寬高的大小,然後判斷寬高是否為寫死的數值。如果是寫死的數值,那麼就直接看高度和寬度的最小值。然後指派給寬高。如果不是定死的數值,那麼直接擷取螢幕的寬度。使其高度等于寬度。最後調用setMeasuredDimension方法進行設定元件的寬高。在這裡高度我取的是寬度的二分之一。
ok,我們來看下一鍵檢測時那個跟雷達一樣,逐漸擴散效果是怎麼實作的。先說下思路,擴散,從上面的動圖可以看出來,是兩個圓一前一後的向外逐漸變大後逐漸消失,是以我們可以先繪制兩個圓出來,然後不停的改變其半徑的大小這樣是不是就可以實作其擴散的效果了呢?
看代碼:
/**
* 初始狀态
*
* @param width 元件寬
* @param height 元件高
* @param canvas 畫布
*/
public void initialState(int width, int height, Canvas canvas) {
String color = null;
//下面的判斷是用于進行顔色逐漸透明
int colorSie = - i;
if (colorSie < ) {
color = "#0" + colorSie + "0FCA41";
} else {
color = "#" + colorSie + "0FCA41";
}
paint.setColor(Color.parseColor(color));
paint.setStrokeWidth();
//繪制兩個逐漸變大的圓
canvas.drawCircle(width / , height / , width / + i, paint);
canvas.drawCircle(width / , height / , width / + j, paint);
j = j + f;
i++;
if (i > || j > ) {
i = ;
j = ;
}
//如果進行循環到一就讓他阻塞一段時間,模拟呼吸節奏
if (i == ) {
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//繪制中間的圓
paint.setColor(Color.parseColor("#FF0FCA41"));
paint.setStrokeWidth();
canvas.drawCircle(width / , height / , width / , paint);
//圓中間的字
paint.setColor(Color.WHITE);
//字型大小,算法為整個寬度/5*2=圓的直徑;圓的直徑/2=字型總長度;字型總長度/4=每個字型大小;
int textSize = width / * / / ;
paint.setTextSize(textSize);
//整個寬度/5*2=圓的直徑;圓的直徑/4=距離圓左邊頂點距離;然後整個寬度一半減去頂點距離就為X軸坐标
int x = width / - width / * / ;
//整個高度/2+字型高度的一半為Y軸坐标
int y = height / + textSize / ;
canvas.drawText("一鍵檢測", x, y, paint);
//20毫秒重新整理一次
postInvalidateDelayed();
}
從上面前幾行代碼可以看出,兩個圓的顔色是逐漸透明的,半徑也是逐漸變大的,然後大到一定程度就傳回為0然後睡眠500毫秒再進行變大。從上面的代碼可以看出最大的圓比中間高亮圓大了60,小的圓比高亮圓大了30.要想這兩個圓在變大過程中不重合,那麼增速是要一樣的,是以大的每次繪制+1f,小的每次0.5f.通過調用postInvalidateDelayed(20);這個方法來進行沒20毫秒進行重新整理元件,重新繪制。
ok,這個向外擴散的功能是做完了。那麼再來看下第二個界面,圓弧圍繞着圓進行旋轉,且尾巴是逐漸透明的。圓弧旋轉有個很淡的軌迹。
實作思路,中間高亮圓和文字沒什麼變化,一個套路就是文字大小,位置有點變化,且文字動态改變,這沒什麼可說的把,改變的文字弄個變量就行了。
先說圓弧軌迹,跟上面功能一樣,隻是繪制了顔色比高亮圓淡一些,且比高亮圓半徑大了20左右。
帶着逐漸透明尾巴的旋轉圓弧,實作思路,繪制多個圓弧且第一個末尾跟第二個開頭銜接,如第一個圓弧從0度開始繪制20度,第二個圓弧則從20度繪制20度,第三個圓弧從40繪制20度。這樣推下去。然後就動态的改變他們繪制的起始點就可以實作旋轉了。
代碼如下:
/**
* 進度狀态
*
* @param width 元件寬
* @param height 元件高
* @param canvas 畫布
*/
public void progressState(int width, int height, Canvas canvas) {
//繪制圓弧軌道
paint.setColor(Color.parseColor("#250FCA41"));
paint.setStrokeWidth();
canvas.drawCircle(width / , height / , width / + , paint);
//繪制旋轉的圓弧
paint.setStrokeWidth();
int xMin = width / - width / - ;
int xMax = width / + width / + ;
int yMin = height / - width / - ;
int yMax = height / + width / + ;
rectF.right = xMax;
rectF.left = xMin;
rectF.top = yMin;
rectF.bottom = yMax;
//繪制多個圓弧隻用于顔色逐漸變淡
angle = angle + ;
paint.setColor(Color.parseColor("#200FCA41"));
canvas.drawArc(rectF, angle, , true, paint);
paint.setColor(Color.parseColor("#350FCA41"));
canvas.drawArc(rectF, angle + , , true, paint);
paint.setColor(Color.parseColor("#500FCA41"));
canvas.drawArc(rectF, angle + , , true, paint);
paint.setColor(Color.parseColor("#650FCA41"));
canvas.drawArc(rectF, angle + , , true, paint);
if (angle > ) {
angle = ;
}
//繪制中間的高亮圓
paint.setColor(Color.parseColor("#FF0FCA41"));
paint.setStrokeWidth();
canvas.drawCircle(width / , height / , width / , paint);
//圓中間的字
paint.setColor(Color.WHITE);
//字型大小,算法為整個寬度/5*2=圓的直徑;圓的直徑/1=字型總長度;字型總長度/4=每個字型大小;
int textSize = width / * / / ;
paint.setTextSize(textSize);
String text = progress + "分";
//這個地方沒辦法算法太差了是以這樣來适配下文字居中(别嘲笑我)
int len = text.length();
if (len == ) {
len = ;
} else {
len = ;
}
//整個寬度/5*2=圓的直徑;圓的直徑/text.length()=距離圓左邊頂點距離;然後整個寬度一半減去頂點距離就為X軸坐标
int x = width / - width / * / len;
//整個高度/2+字型高度的一半為Y軸坐标
int y = height / + textSize / ;
canvas.drawText(text, x, y, paint);
//50毫秒重新整理一次
postInvalidateDelayed();
}
從上面代碼可以看出,我繪制了4個圓弧,重新整理時間為50毫秒。
兩個界面都給繪制好了,那麼,上面動圖點選進行切換界面的效果是怎麼實作的呢?先來看下onDraw方法是怎麼弄的吧。
@Override
protected void onDraw(Canvas canvas) {
int width = getWidth();
int height = getHeight();
//判斷是否為進度狀态
if (flag) {
progressState(width, height, canvas);
} else {
initialState(width, height, canvas);
}
}
從代碼可以看出就是定義了個flag,進行判斷繪制那個界面就可以了。很簡單,但是這個點選事件可不簡單了,因為,點選高亮圓以外不會響應點選事件,從高亮圓以外按下移動到高亮圓以内擡起也不會響應點選事件,點選高亮圓以内移動到高亮圓以外也不會響應點選事件。
是以,解決思路就來了,判斷按下擡起時坐标位置也就可以實作了。是的,首先按下擡起的取值範圍就是高亮圓的上下左右四個頂點所構成的矩形。這就是響應點選事件的範圍。
代碼比較有說服力,不那麼空洞。
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (flag) {
return false;
}
int action = event.getAction();
//判斷是擡起還是按下,這樣判斷就能使如果是範圍外按下到範圍内就不會響應點選事件,如果是範圍内按下,移除範圍外也不會響應點選事件
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP) {
int width = getWidth();
int height = getHeight();
//元件中心點加減圓的半徑就是點選範圍
int xMin = width / - width / ;
int xMax = width / + width / ;
int yMin = height / - width / ;
int yMax = height / + width / ;
//按下時相對于元件原點的坐标
float x = event.getX();
float y = event.getY();
//判斷在中間圓形範圍内才響應點選事件否則不傳遞事件
if (xMax > x && x > xMin && yMax > y && y > yMin) {
//調用super使得點選事件會響應
super.dispatchTouchEvent(event);
//判斷是否為擡起事件,此處判斷是否為擡起事件,表示為可以響應點選事件了,是以,我們将重新繪制布局了
if (action == MotionEvent.ACTION_UP) {
flag = true;
}
//傳回true表示事件可以向下傳遞,且會響應其他事件如擡起或者滑動事件
return true;
}
}
//傳回false表示事件将不會向下傳遞,如按下後傳回false。移動和擡起事件将不會被響應
return false;
}
流程,判斷如果是在進度界面,那麼點選事件不往下傳遞return false。如果是雷達界面,那麼判斷按下位置,和擡起位置是否在響應的範圍内,如果按下不在範圍内直接不傳遞事件return false,如果在範圍内,判斷是否是擡起事件,不是則不改變flag繪制進度界面且return true,即可以向下傳遞事件,即響應move和up事件。
那麼判斷擡起時候是否在範圍内,如果不在,return false,如果在,再判斷是否擡起事件,是擡起事件改變flag 進行改變成進度界面。
為什麼要在這個地方加個是否是擡起事件呢?因為onclick響應事件是在擡起事件響應後面。
這裡說明下在響應點選事件前一定要調用 super.dispatchTouchEvent(event);不然你不管return false還是true。都不會響應點選事件。
ok,主要功能都說完了,補全下代碼吧。
//第一層擴散的圓的多餘的半徑
private int i = ;
//第二層擴散的圓的多餘的半徑
private float j = ;
//圓弧角度
private int angle = ;
//畫筆
private Paint paint;
//是否開始繪制進度條
private boolean flag = false;
//得分
private int progress = ;
//繪制圓弧所需要存儲矩形的上下左右坐标
private RectF rectF;
public MyView(Context context) {
super(context);
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 獲得預設該layout的尺寸
*
* @return
*/
private int getDefaultWidth() {
WindowManager wm = (WindowManager) getContext().getSystemService(
Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return Math.min(outMetrics.widthPixels, outMetrics.heightPixels);
}
/**
* 初始化畫筆
*/
private void init() {
paint = new Paint();
rectF = new RectF();
}
/**
* 設定進度值
*
* @param progress
*/
public void setProgress(int progress) {
this.progress = progress;
}
/**
* 關閉進度狀态
*/
public void close() {
flag = false;
}
以上是全局變量,以及一些方法。複制以上全部代碼可跑起來,此類繼承的是View。