今天主要給大家?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ì)算刻度線長度:
首先咱們稍微整理一下思路:
- 除了突起的刻度線,其他刻度線長度一致,咱們不妨先將長度一致的先繪制;
- 經(jīng)過觀察,突起部分中間長,兩邊短,呈對(duì)稱性,所以考慮一半即可,這樣就只需考慮len1 - len5;
- 長度變化是有規(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變化;
- 右邊的幾條線,只不過把左邊的變長改為變短即可,依舊能適應(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