多角度實(shí)現(xiàn)華為榮耀時(shí)鐘效果!

今天主要給大家?guī)硪粋€(gè)在榮耀8上看到的一個(gè)小有意思的時(shí)鐘效果,這個(gè)效果比較簡單,俗話說,“人生在世,無非就是把復(fù)雜的事情整簡單,抑或把簡單的事情搞復(fù)雜”,既然比較簡單,那咱們就多用幾種方案來實(shí)現(xiàn),進(jìn)而開拓一下思路;
首先先上效果圖:


如果你想看 GAStudio Github主頁,請(qǐng)戳這里
如果你想看 GAStudio更多技術(shù)文章,請(qǐng)戳這里
github 源碼地址:https://github.com/Ajian-studio/GAHonorClock

從效果圖上看,和常見表盤一樣,每根線代表一條時(shí)間刻度,一個(gè)紅色小圈隨著時(shí)間的變化不斷的移動(dòng),而差異的點(diǎn)主要在于表盤有一個(gè)突起,并且這個(gè)突起隨著紅點(diǎn)的移動(dòng)而移動(dòng),現(xiàn)在針對(duì)這個(gè)效果,我們從以下三個(gè)思路來實(shí)現(xiàn):
一、使用切圖作為蒙板與刻度線進(jìn)行圖像混合;
二、自行勾勒對(duì)應(yīng)形狀Path與刻度線進(jìn)行圖像混合;
三、動(dòng)態(tài)計(jì)算刻度線長度;
有同學(xué)可能會(huì)認(rèn)為第一種和第二種核心原理一樣,都是用的混合模式(Xfermode),確實(shí)如此,但最終實(shí)現(xiàn)結(jié)果會(huì)有差異,值得考慮;

接下來咱們分別來看下這三種實(shí)現(xiàn);

一、使用切圖作為蒙板與刻度線進(jìn)行圖像混合:


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

無非就是用蒙版遮蓋掉我們不想進(jìn)行顯示的區(qū)域,思路整理起來就是下面的過程:
1.繪制表盤刻度;
2.使用遮罩圖與表盤刻度進(jìn)行混合;
3.不斷旋轉(zhuǎn)遮罩圖;

核心代碼整理如下:

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);
    }

該方案實(shí)現(xiàn)效果如下:


二、自行勾勒對(duì)應(yīng)形狀Path與刻度線進(jìn)行圖像混合:


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



先定義一個(gè)數(shù)組表示突起部分刻度線的相對(duì)長度關(guān)系:

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);
    }

該方案實(shí)現(xiàn)效果如下:



單從效果來說,似乎與第一種方案無異,一會(huì)兒咱們?cè)龠M(jìn)行比較,接下來看第三種方案;

三、動(dòng)態(tài)計(jì)算刻度線長度:


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

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

我們有如下幾個(gè)長度的線,len1, len2, len3, len4, len5, 那么在一個(gè)周期時(shí)間內(nèi),len1 變到 len2, len2變到 len3...... 我們就可以得到這樣如下公式:


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

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

經(jīng)過上面的分析,繪制的核心代碼如下:

 @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);
    }

該方案實(shí)現(xiàn)效果如下:



OK,到此為止,三種方案已經(jīng)實(shí)現(xiàn)完畢,最后,咱們一起從cpu占用、內(nèi)存占用、FPS這幾個(gè)方面進(jìn)行個(gè)簡單的比較:
測試機(jī)型為 moto 1085

孰好孰壞,咱們用數(shù)據(jù)說話,大家可自行評(píng)判;



最后,附上GAStudio技術(shù)交流群和Github,喜歡的話歡迎follow和star:

如果你想看 GAStudio Github主頁,請(qǐng)戳這里
如果你想看 GAStudio更多技術(shù)文章,請(qǐng)戳這里
github 源碼地址:https://github.com/Ajian-studio/GAHonorClock

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評(píng)論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容