「做者:彭也
,連接:http://suo.im/6u1nbE」git
廢話很少說,先上效果程序員
效果酷炫,動畫豐富,效果爆炸web
boom~canvas
1、設計思路
看膩了市面上各類醜陋難看的時鐘控件,是時候來點新的東西了!將現實生活中的擺鐘圓形錶盤設計、電子手錶的數顯錶盤設計抽象出來,提取出「圓形」、「數顯」、「時光流逝感」等詞彙,融合這些詞彙特徵,把特徵賦予最終的UI設計,就這樣,一個炫酷的UI控件誕生了!api
撥動時鐘圓盤能夠調整時鐘,伴隨時間的流逝,撥動的圓盤還能自動回位,交互邏輯天然順暢。微信
設置不一樣的主題色便可體現更多的內涵,「靜謐」、「夜晚」、「月夜」、「純淨」等等,控件設計自己的可擴展性很是好。app
2、實現方式
設計思路清晰明確以後,就要考慮如何實現了。來人,上口號。編輯器
沒有人ide
比我函數
更懂
☝️實現
類設計
從UI圖中能夠觀察到,時鐘控件由四個大表盤組成,分別是上下午錶盤、小時錶盤、分鐘錶盤、秒鐘錶盤。在實現思路上首先考慮抽象出圓盤控件父類DiskView,其他錶盤均繼承自DiskView便可。有了各類各樣的錶盤,最後再用ViewGroup將其所有組裝好便可。
而DiskView做爲基類,須要承擔動畫、拖動、點擊等交互的邏輯,同時還要具有錶盤的公共屬性,例如錶盤半徑radius、錶盤旋轉角度degree等。
public class DiskView extends View {
private static final String TAG = "DiskView";
Context mContext;
/**
* 圓盤半徑
*/
int mRadius = 0;
/**
* 手指第一次按下時的座標
*/
float startX, startY;
/**
* 當前手指按下點的座標
*/
float curX, curY;
/**
* 第一次手指按下的點與初始位置造成的夾角
*/
int startDegree;
/**
* 手指按下的點與初始位置造成的夾角
*/
int curDegree;
/**
* 圓盤當前位置相對初始位置的角度,初始位置角度爲0度
*/
int degree = 0;
/**
* 手指擡起後是否須要迴歸原來的狀態
*/
boolean isNeedReturn = true;
ValueAnimator animator;
}
UI繪製
有了座標,有了角度,接下來考慮繪製。繪製採用canvas
的圖形繪製api,計算好各個圖形的位置,賦予對應的顏色。調用rotate
方法圍繞圓心繪製具備必定角度的文字。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//畫圓盤
mPaint.setColor(diskColor);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
//畫數字
mPaint.setColor(numColor);
Rect bounds = new Rect();
for (int i = 0; i < 60; i++) {
if (i == minute) {
mPaint.setColor(selectNumColor);
} else {
mPaint.setColor(numColor);
}
if (i % 10 != 0) {
if (i % 5 == 0) {
canvas.drawCircle(mRadius, 2 * mRadius - textHeight * 3 / 2, DisplayUtils.sp2px(mContext, 20) / 4, mPaint);
} else {
canvas.drawCircle(mRadius, 2 * mRadius - textHeight * 3 / 2, DisplayUtils.sp2px(mContext, 20) / 6, mPaint);
}
} else {
mPaint.getTextBounds(i + "", 0, (i + "").length(), bounds);
textHeight = bounds.height();
canvas.drawText(i + "", mRadius - bounds.width() / 2, mRadius * 2 - bounds.height(), mPaint);
}
canvas.rotate(-6, mRadius, mRadius);
}
}
交互邏輯
控件交互邏輯大部分都在onTouchEvent
的回調中進行處理,分別對用戶的點擊、移動、擡起動做作針對性處理,核心關鍵在於計算好各個狀況的圓盤角度,以後再經過animator
計算好對應的數值,實時刷新界面便可。須要注意的是用戶的起始落點不能超過圓盤的界限。
@Override
public boolean onTouchEvent(MotionEvent event) {
curX = event.getX();
curY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
startDegree = computeCurrentAngle(curX, curY);
//起始落點不能超過圓盤界限
if (Math.sqrt(
(startX - mRadius) * (startX - mRadius) + (startY - mRadius) * (startY - mRadius)
) > mRadius) {
startDegree = 0;
}
break;
case MotionEvent.ACTION_MOVE:
//起始落點不能超過圓盤界限
if (Math.sqrt(
(startX - mRadius) * (startX - mRadius) + (startY - mRadius) * (startY - mRadius)
) > mRadius) {
return false;
}
curDegree = computeCurrentAngle(curX, curY);
postInvalidate();
break;
case MotionEvent.ACTION_UP:
if (Math.sqrt(
(startX - mRadius) * (startX - mRadius) + (startY - mRadius) * (startY - mRadius)
) > mRadius) {
return false;
}
int tmpDegree = degree;//手指按下前的圓盤角度
degree = degree + curDegree - startDegree;
if (Math.abs(degree) > 360) {
degree %= 360;
}
startDegree = 0;
curDegree = 0;
startX = 0;
startY = 0;
//是否須要回位
if (isNeedReturn) {
animator = ValueAnimator.ofInt(degree, tmpDegree);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
degree = (int) animation.getAnimatedValue();
postInvalidate();
}
});
animator.setDuration(200);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
}
break;
}
return true;
}
3、後記
自定義控件開發做爲Android開發中的重要一環,如何利用好各個api實現功能是一方面,如何自頂向下進行設計也是一方面。在開發以前最關鍵的事情並非構思如何實現、如何設計,而是去發掘用戶的需求,從需求倒推應該具有哪些功能,再從功能角度考慮如何進行創意設計,最終呈現給用戶。如此一來,纔可能將產品作得更好。
控件放在了gitee上,地址在: https://gitee.com/null_077_5468/uidemos
---END---
更文不易,點個「在看」支持一下👇
本文分享自微信公衆號 - 技術最TOP(Tech-Android)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。