效果爆炸的Android時鐘UI控件!

「做者:彭也,連接: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---


推薦閱讀:
Kotlin 1.4 正式版發佈,專一於質量和性能!
最全面的44個Java 性能調優細節!
這個項目太屌了吧!(續)
這個項目太屌了吧!
H5秒開方案思考與實踐
在 View 上使用掛起函數
【譯】Medium 2w+贊,「高效」程序員擁有的 7 項技能!
深度好文:Linux系統內存知識
Android WebView常見問題梳理優化總結!


更文不易,點個「在看」支持一下👇

本文分享自微信公衆號 - 技術最TOP(Tech-Android)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索