今天主要給大家帶來一個在榮耀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);
}複製程式碼
該方案實現效果如下:
單從效果來說,似乎與第一種方案無異,一會兒我們們再進行比較,接下來看第三種方案;
三、動態計算刻度線長度:
首先我們們稍微整理一下思路:
- 除了突起的刻度線,其他刻度線長度一致,我們們不妨先將長度一致的先繪製;
- 經過觀察,突起部分中間長,兩邊短,呈對稱性,所以考慮一半即可,這樣就只需考慮len1 - len5;
- 長度變化是有規律的,具有周期性,週期為totalTime * perAngle / 360,也即轉一圈的時間(一分鐘),除以刻度線的條數;
我們有如下幾個長度的線,len1, len2, len3, len4, len5, 那麼在一個週期時間內,len1 變到 len2, len2變到 len3...... 我們就可以得到這樣如下公式:
上述公式中,len表示長度,factor表示歸一化時間因子,從0到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…