多角度實現有趣的時鐘效果!

GAStudio發表於2017-01-24

今天主要給大家帶來一個在榮耀8上看到的一個小有意思的時鐘效果,這個效果比較簡單,俗話說,“人生在世,無非就是把複雜的事情整簡單,抑或把簡單的事情搞複雜”,既然比較簡單,那我們們就多用幾種方案來實現,進而開拓一下思路;
首先先上效果圖:

多角度實現有趣的時鐘效果!

如果你想看 GAStudio Github主頁,請戳這裡
如果你想看 GAStudio更多技術文章,請戳這裡
QQ技術交流群:277582728;
github 原始碼地址:github.com/Ajian-studi…

從效果圖上看,和常見錶盤一樣,每根線代表一條時間刻度,一個紅色小圈隨著時間的變化不斷的移動,而差異的點主要在於錶盤有一個突起,並且這個突起隨著紅點的移動而移動,現在針對這個效果,我們從以下三個思路來實現:
一、使用切圖作為蒙板與刻度線進行影像混合;
二、自行勾勒對應形狀Path與刻度線進行影像混合;
三、動態計算刻度線長度;
有同學可能會認為第一種和第二種核心原理一樣,都是用的混合模式(Xfermode),確實如此,但最終實現結果會有差異,值得考慮;

接下來我們們分別來看下這三種實現;

一、使用切圖作為蒙板與刻度線進行影像混合:


使用切圖蒙版方案可以概括為如圖的過程:

多角度實現有趣的時鐘效果!

無非就是用蒙版遮蓋掉我們不想進行顯示的區域,思路整理起來就是下面的過程:
1.繪製錶盤刻度;
2.使用遮罩圖與錶盤刻度進行混合;
3.不斷旋轉遮罩圖;

核心程式碼整理如下:

protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Save a layer
        int layerCount = canvas.saveLayer(mClockViewRectF, mPaint, Canvas.ALL_SAVE_FLAG);

        // Draw the DEFAULT_TOTAL_CLOCK_SCALE_LINE_NUM clock scale lines
        mPaint.setColor(mClockScaleLineColor);

        // Because the picture is not perfect, we need mAdjustClockScaleLineStartX.
        float clockScaleLineStartY = mAdjustClockScaleLineStartX + mClockViewRectF.top;
        float clockScaleLineEndY = clockScaleLineStartY + mClockScaleLineHeight;
        for (int i = 0; i < DEFAULT_TOTAL_CLOCK_SCALE_LINE_NUM; i++) {
            canvas.drawLine(mClockViewCenterX, clockScaleLineStartY,
                    mClockViewCenterX, clockScaleLineEndY, mPaint);
            canvas.rotate(ANGLE_PER_SCALE, mClockViewCenterX, mClockViewCenterY);
        }
        mPaint.setXfermode(mXfermode);
        canvas.rotate(mNowClockAngle, mClockViewCenterX, mClockViewCenterY);
        canvas.drawBitmap(mClockMaskBitmap, null, mClockViewRectF, mPaint);
        mPaint.setXfermode(null);

        // Draw clock point
        mPaint.setColor(mClockPointColor);
        canvas.drawCircle(mClockPointCenterX, mClockPointCenterY, mClockPointRadius, mPaint);

        canvas.restoreToCount(layerCount);
        updateTimeText(canvas);
    }複製程式碼

該方案實現效果如下:

多角度實現有趣的時鐘效果!

二、自行勾勒對應形狀Path與刻度線進行影像混合:


用path勾勒對應形狀Path,可以將蒙版圖分為如下圖所示兩部分。一是除了突起部分的圓環部分,二是突起部分,這個突起部分可以使用貝塞爾曲線進行擬合,也可以使用線性擬合(即採用直線連線每個刻度線的頂端),本次選擇採用線性擬合的方式,有興趣的同學可以嘗試貝塞爾曲線方式;

多角度實現有趣的時鐘效果!

先定義一個陣列表示突起部分刻度線的相對長度關係:

private static final float[] CLOCK_SCALE_LINE_BASE_LEN_ARRAY = new float[]{
            1F, 1.1F, 1.21F, 1.32F, 1.452F,
            1.551F, 1.6827F, 1.75F, 1.6827F, 1.551F,
            1.452F, 1.32F, 1.21F, 1.1F, 1F};複製程式碼

生成Path蒙版的程式碼如下:

private void generateMaskPath() {
        Point point = new Point(mClockViewCenterX, mClockViewCenterY - mClockMaskRadius - mClockScaleLineHeight);
        mClockMaskPath.moveTo(point.x, point.y);

        // Generate contour of the special clock scale lines
        int arrayLen = CLOCK_SCALE_LINE_BASE_LEN_ARRAY.length;
        for (int index = 0; index < arrayLen; index++) {
            calculateNextPoint(point, CLOCK_SCALE_LINE_BASE_LEN_ARRAY[index],
                    (float) Math.toRadians(ANGLE_PER_SCALE * (index + 1)));
            mClockMaskPath.lineTo(point.x, point.y);
        }

        // Generate contour of the normal clock scale lines
        int insertLen = mClockScaleLineMaxHeight - mClockScaleLineHeight;
        RectF cycleRectF = new RectF(mClockViewRectF);
        cycleRectF.inset(insertLen, insertLen);
        mClockMaskPath.arcTo(cycleRectF, arrayLen * ANGLE_PER_SCALE - 90,
                (DEFAULT_TOTAL_CLOCK_SCALE_LINE_NUM - arrayLen) * ANGLE_PER_SCALE);
    }複製程式碼

核心繪製邏輯如下:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Save layer
        int layerOne = canvas.saveLayer(mClockViewRectF, mPaint, Canvas.ALL_SAVE_FLAG);

        // Draw clock scale lines
        mPaint.setColor(mClockScaleLineColor);
        float clockScaleLineStartY = mAdjustClockScaleLineStartX + mClockViewRectF.top;
        float clockScaleLineEndY = clockScaleLineStartY + mClockScaleLineMaxHeight;
        for (int i = 0; i < DEFAULT_TOTAL_CLOCK_SCALE_LINE_NUM; i++) {
            canvas.drawLine(mClockViewCenterX, clockScaleLineStartY,
                    mClockViewCenterX, clockScaleLineEndY, mPaint);
            canvas.rotate(ANGLE_PER_SCALE, mClockViewCenterX, mClockViewCenterY);
        }

        mPaint.setXfermode(mXfermode);
        canvas.rotate(mNowClockAngle - mClockMaskAdjustAngle, mClockViewCenterX, mClockViewCenterY);
        // Generate a mask by path
        int layerTwo = canvas.saveLayer(mClockViewRectF, mPaint, Canvas.ALL_SAVE_FLAG);
        mPaint.setXfermode(null);
        canvas.drawOval(mClockViewRectF, mPaint);
        mPaint.setXfermode(mXfermode);
        canvas.drawPath(mClockMaskPath, mPaint);
        canvas.restoreToCount(layerTwo);

        mPaint.setXfermode(null);
        // Draw clock point
        mPaint.setColor(mClockPointColor);
        canvas.rotate(mClockMaskAdjustAngle, mClockViewCenterX, mClockViewCenterY);
        canvas.drawCircle(mClockPointCenterX, mClockPointCenterY, mClockPointRadius, mPaint);

        canvas.restoreToCount(layerOne);
        updateTimeText(canvas);
    }複製程式碼

該方案實現效果如下:

多角度實現有趣的時鐘效果!

單從效果來說,似乎與第一種方案無異,一會兒我們們再進行比較,接下來看第三種方案;

三、動態計算刻度線長度:


首先我們們稍微整理一下思路:

  1. 除了突起的刻度線,其他刻度線長度一致,我們們不妨先將長度一致的先繪製;
  2. 經過觀察,突起部分中間長,兩邊短,呈對稱性,所以考慮一半即可,這樣就只需考慮len1 - len5;
  3. 長度變化是有規律的,具有周期性,週期為totalTime * perAngle / 360,也即轉一圈的時間(一分鐘),除以刻度線的條數;

我們有如下幾個長度的線,len1, len2, len3, len4, len5, 那麼在一個週期時間內,len1 變到 len2, len2變到 len3...... 我們就可以得到這樣如下公式:

多角度實現有趣的時鐘效果!

上述公式中,len表示長度,factor表示歸一化時間因子,從0到1變化;

  1. 右邊的幾條線,只不過把左邊的變長改為變短即可,依舊能適應上述公式;

經過上面的分析,繪製的核心程式碼如下:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Normalization the angle
        float normalizedTimePeriod = mRemainderOfNowClockAngle / ANGLE_PER_SCALE;
        int clockScaleLineStartY = mClockViewRect.top + mClockScaleLineMaxHeight;

        canvas.save();

        // Rotate the canvas to now clock angle
        canvas.rotate(mNowClockAngle, mClockViewCenterX, mClockViewCenterY);

        // Draw the point
        mPaint.setColor(mClockPointColor);
        canvas.drawCircle(mClockPointCenterX, mClockPointCenterY, mClockPointRadius, mPaint);

        // The follow adjustArrayLen indicate the special clock scale num
        int adjustArrayLen = CLOCK_SCALE_LINE_BASE_LEN_ARRAY.length - 1;

        // Rotate the canvas to ensure that the longest scale line points to now scale line
        canvas.rotate(-mRemainderOfNowClockAngle - (adjustArrayLen - 2) / 2f * ANGLE_PER_SCALE,
                mClockViewCenterX, mClockViewCenterY);
        mPaint.setColor(mClockScaleLineColor);

        // Draw the special lines
        // First draw the rightmost clock scale line, so you need to start with index = adjustArrayLen - 1;
        for (int index = adjustArrayLen - 1; index >= 0; index--) {
            // The follow function is mean that Length 1 changes to length 2 within a certain period.
            // The formula can be expressed as follows, changeLen1 = (len2 - len1) * timeFactor + len1.
            float specialLineNowLen = (mClockScaleLineHeight * (CLOCK_SCALE_LINE_BASE_LEN_ARRAY[index]
                    + normalizedTimePeriod * (CLOCK_SCALE_LINE_BASE_LEN_ARRAY[index + 1]
                    - CLOCK_SCALE_LINE_BASE_LEN_ARRAY[index])));
            float specialClockEndY = clockScaleLineStartY - specialLineNowLen;
            canvas.drawLine(mClockViewCenterX, clockScaleLineStartY, mClockViewCenterX, specialClockEndY, mPaint);
            canvas.rotate(ANGLE_PER_SCALE, mClockViewCenterX, mClockViewCenterY);
        }

        // Draw the normal lines
        int clockScaleLineEndY = mClockScaleLineMaxHeight + mClockViewRect.top - mClockScaleLineHeight;
        for (int other = 0; other < (DEFAULT_TOTAL_CLOCK_SCALE_LINE_NUM - adjustArrayLen); other++) {
            canvas.drawLine(mClockViewCenterX, clockScaleLineStartY, mClockViewCenterX,
                    clockScaleLineEndY, mPaint);
            canvas.rotate(ANGLE_PER_SCALE, mClockViewCenterX, mClockViewCenterY);
        }

        canvas.restore();
        updateDigitalTimeText(canvas);
    }複製程式碼

該方案實現效果如下:

多角度實現有趣的時鐘效果!

OK,到此為止,三種方案已經實現完畢,最後,我們們一起從cpu佔用、記憶體佔用、FPS這幾個方面進行個簡單的比較:
測試機型為 moto 1085

孰好孰壞,我們們用資料說話,大家可自行評判;

多角度實現有趣的時鐘效果!


最後,附上GAStudio技術交流群和Github,喜歡的話歡迎follow和star:

如果你想看 GAStudio Github主頁,請戳這裡
如果你想看 GAStudio更多技術文章,請戳這裡
QQ技術交流群:277582728;
github 原始碼地址:github.com/Ajian-studi…

多角度實現有趣的時鐘效果!

相關文章