html时钟自动刷新抖音,抖音上炫酷的网红文字时钟
原標(biāo)題:抖音上炫酷的網(wǎng)紅文字時(shí)鐘
本文作者
作者:二娃_
https://juejin.im/post/5cb53e93e51d456e55623b07
1
起源
源碼地址
https://github.com/drawf/SourceSet/blob/master/app/src/main/java/me/erwa/sourceset/view/TextClockView.kt
周末在家刷抖音的時(shí)候看到了這款網(wǎng)紅時(shí)鐘,都是Android平臺(tái)的,想來何不自己實(shí)現(xiàn)一把。看抖音里大家發(fā)的視頻,這款時(shí)鐘基本分兩類,一類是展示在「壁紙」上,一類是展示在「鎖屏」上。
展示到「壁紙」通過LiveWallPaper相關(guān)API可以做到,這也是本專題要實(shí)現(xiàn)的方式。
展示到「鎖屏」目測是使用各ROM廠商的相關(guān)API,開發(fā)鎖屏主題可以做到。
然而實(shí)現(xiàn)兩者的基礎(chǔ)便是拿起Canvas Paint等把它繪制出來,所以「上篇」我先用自定View的方式把時(shí)鐘畫出來,在Activity中展示效果。「下篇」的時(shí)候再把該View結(jié)合LiveWallPaper設(shè)置到壁紙。
2
思考分析
這是我當(dāng)時(shí)截圖下來的參考,先分析下涉及到的元素及樣式表現(xiàn):
「圓中信息」圓中心的數(shù)字時(shí)間+數(shù)字日期+文字星期幾,始終為白色
「時(shí)圈」一圈文字小時(shí),一點(diǎn)、二點(diǎn)..十二點(diǎn),當(dāng)前點(diǎn)數(shù)為白色,其它為白色+透明度,如圖中十點(diǎn)就是白色。
「分圈」一圈文字分鐘,一分、二分..五十九分,六十分顯示為空,同理,當(dāng)前分鐘為白色,其它白色+透明度。
「秒圈」一圈文字秒,一秒、二秒..五十九秒,六十秒顯示為空,也是同理。
然后分析下動(dòng)畫效果:
每秒鐘「秒圈」走一下,這一下的旋轉(zhuǎn)角度為360°/60=6°,并且走這一下的時(shí)候有個(gè)線性旋轉(zhuǎn)過去的動(dòng)畫效果。
每分鐘「分圈」走一下,旋轉(zhuǎn)角度和動(dòng)畫效果跟「秒圈」相同。
每小時(shí)「時(shí)圈」走一下,旋轉(zhuǎn)角度為360°/12=30°,動(dòng)畫效果同上。
3
繪制靜態(tài)圖
1. 畫布準(zhǔn)備
基本是將畫布背景填充黑色,然后將畫布的原點(diǎn)移動(dòng)到View大小的中心,這樣方便思維理解與繪制。
//在onLayout方法中計(jì)算View去除padding后的寬高
overridefunonLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int){
super.onLayout(changed, left, top, right, bottom)
mWidth = (measuredWidth - paddingLeft - paddingRight).toFloat
mHeight = (measuredHeight - paddingTop - paddingBottom).toFloat
//后文會(huì)涉及到
//統(tǒng)一用View寬度*系數(shù)來處理大小,這樣可以聯(lián)動(dòng)適配樣式
mHourR = mWidth * 0.143f
mMinuteR = mWidth * 0.35f
mSecondR = mWidth * 0.35f
}
//在onDraw方法將畫布原點(diǎn)平移到中心位置
overridefunonDraw(canvas: Canvas?){
super.onDraw(canvas)
if(canvas == null) return
canvas.drawColor(Color.BLACK) //填充背景
canvas.save
canvas.translate(mWidth / 2, mHeight / 2) //原點(diǎn)移動(dòng)到中心
//繪制各元件,后文會(huì)涉及到
drawCenterInfo(canvas)
drawHour(canvas, mHourDeg)
drawMinute(canvas, mMinuteDeg)
drawSecond(canvas, mSecondDeg)
//從原點(diǎn)處向右畫一條輔助線,之后要處理文字與x軸的對(duì)齊問題,稍后再說
canvas.drawLine( 0f, 0f, mWidth, 0f, mHelperPaint)
canvas.restore
}
2. 畫「圓中信息」
經(jīng)過第一步,可以在AS的Xml Preview中看到一屏黑色+一條從屏幕中心到右邊界的紅線。(一眼望去,還是挺美的)
/**
* 繪制圓中信息
*/
privatefundrawCenterInfo(canvas: Canvas){
Calendar.getInstance.run {
//繪制數(shù)字時(shí)間
valhour = get(Calendar.HOUR_OF_DAY)
valminute = get(Calendar.MINUTE)
mPaint.textSize = mHourR * 0.4f //字體大小根據(jù)「時(shí)圈」半徑來計(jì)算
mPaint.alpha = 255
mPaint.textAlign = Paint.Align.CENTER
canvas.drawText( "$hour:$minute", 0f, mPaint.getBottomedY, mPaint)
//繪制月份、星期
valmonth = ( this. get(Calendar.MONTH) + 1).let {
if(it < 10) "0$it"else"$it"
}
valday = this. get(Calendar.DAY_OF_MONTH)
valdayOfWeek = ( get(Calendar.DAY_OF_WEEK) - 1).toText //私有的擴(kuò)展方法,將Int數(shù)字轉(zhuǎn)換為 一、十一、二十等,后文繪制三個(gè)文字圈都會(huì)用該方法
mPaint.textSize = mHourR * 0.16f //字體大小根據(jù)「時(shí)圈」半徑來計(jì)算
mPaint.alpha = 255
mPaint.textAlign = Paint.Align.CENTER
canvas.drawText( "$month.$day星期$dayOfWeek", 0f, mPaint.getTopedY, mPaint)
}
}
/**
* 擴(kuò)展獲取繪制文字時(shí)在x軸上 垂直居中的y坐標(biāo)
*/
privatefunPaint.getCenteredY: Float{
returnthis.fontSpacing / 2- this.fontMetrics.bottom
}
/**
* 擴(kuò)展獲取繪制文字時(shí)在x軸上 貼緊x軸的上邊緣的y坐標(biāo)
*/
privatefunPaint.getBottomedY: Float{
return- this.fontMetrics.bottom
}
/**
* 擴(kuò)展獲取繪制文字時(shí)在x軸上 貼近x軸的下邊緣的y坐標(biāo)
*/
privatefunPaint.getToppedY: Float{
return- this.fontMetrics.ascent
}
其中要說一下mPaint.getBottomedY mPaint.getToppedY,這是兩個(gè)擴(kuò)展到Paint畫筆上的兩個(gè)kotlin方法。
他們的作用是為了處理繪制文字時(shí)與x軸的對(duì)齊關(guān)系。canvas.drawText方法的第三個(gè)參數(shù)是y坐標(biāo),但這個(gè)指的是文字的Baseline的y坐標(biāo),所以寫了工具方法來得到矯正后的y坐標(biāo)。(這里就只拋出這個(gè)點(diǎn)吧,具體實(shí)現(xiàn)原理可先查閱Paint類的相關(guān)API就會(huì)明白,文末會(huì)貼出我拜讀的文章鏈接)
拿繪制數(shù)字時(shí)間舉例,展示下不同效果:
把mPaint.getBottomedY替換成0f(y坐標(biāo)為0,就是文字的Baseline坐標(biāo)為0),文字使用15:67 abc jqk,可以看到兩者區(qū)別。(紅線就是前文畫的那條好美的輔助線)
canvas.drawText("15 :67測試文字 abcjqk", 0 f, 0 f, mPaint)
canvas.drawText("15 :67測試文字 abcjqk", 0 f, mPaint.getBottomedY, mPaint)
ok,「圓中信息」繪制后長這個(gè)樣子:
3. 畫「時(shí)圈」「分圈」「秒圈」
繪制思路就是for循環(huán)12次,每次將畫布旋轉(zhuǎn)30°乘以i,然后在指定位置繪制文字,12次后剛好一個(gè)圓圈。
該方法接收一個(gè)degrees: Float參數(shù),是控制「時(shí)圈」整體的旋轉(zhuǎn)的,后文就是不斷改變?cè)撝?#xff0c;而產(chǎn)生動(dòng)畫效果的。并且因?yàn)槿齻€(gè)圈的動(dòng)畫方向都是逆時(shí)針,所以這個(gè)degrees是個(gè)始終會(huì)是個(gè)負(fù)數(shù)。
/**
* 繪制小時(shí)
*/
privatefundrawHour(canvas: Canvas, degrees: Float){
mPaint.textSize = mHourR * 0.16f
//處理整體旋轉(zhuǎn)
canvas.save
canvas.rotate(degrees)
for(i in0until 12) {
canvas.save
//從x軸開始旋轉(zhuǎn),每30°繪制一下「幾點(diǎn)」,12次就畫完了「時(shí)圈」
valiDeg = 360/ 12f * i
canvas.rotate(iDeg)
//這里處理當(dāng)前時(shí)間點(diǎn)的透明度,因?yàn)閐egrees控制整體逆時(shí)針旋轉(zhuǎn)
//iDeg控制繪制時(shí)順時(shí)針,所以兩者和為0時(shí),剛好在x正半軸上,也就是起始繪制位置。
mPaint.alpha = if(iDeg + degrees == 0f) 255else( 0.6f * 255).toInt
mPaint.textAlign = Paint.Align.LEFT
canvas.drawText( "${(i + 1).toText}點(diǎn)", mHourR, mPaint.getCenteredY, mPaint)
canvas.restore
}
canvas.restore
}
同理繪制「分圈」「秒圈」
/**
* 繪制分鐘
*/
privatefundrawMinute(canvas: Canvas, degrees: Float){
mPaint.textSize = mHourR * 0.16f
//處理整體旋轉(zhuǎn)
canvas.save
canvas.rotate(degrees)
for(i in0until 60) {
canvas.save
valiDeg = 360/ 60f * i
canvas.rotate(iDeg)
mPaint.alpha = if(iDeg + degrees == 0f) 255else( 0.6f * 255).toInt
mPaint.textAlign = Paint.Align.RIGHT
if(i < 59) {
canvas.drawText( "${(i + 1).toText}分", mMinuteR, mPaint.getCenteredY, mPaint)
}
canvas.restore
}
canvas.restore
}
/**
* 繪制秒
*/
privatefundrawSecond(canvas: Canvas, degrees: Float){
mPaint.textSize = mHourR * 0.16f
//處理整體旋轉(zhuǎn)
canvas.save
canvas.rotate(degrees)
for(i in0until 60) {
canvas.save
valiDeg = 360/ 60f * i
canvas.rotate(iDeg)
mPaint.alpha = if(iDeg + degrees == 0f) 255else( 0.6f * 255).toInt
mPaint.textAlign = Paint.Align.LEFT
if(i < 59) {
canvas.drawText( "${(i + 1).toText}秒", mSecondR, mPaint.getCenteredY, mPaint)
}
canvas.restore
}
canvas.restore
}
DuangDuang!!效果出來啦~
4. 讓時(shí)鐘轉(zhuǎn)起來
那么如何可以讓時(shí)鐘轉(zhuǎn)起來呢?我們?cè)倏匆幌耾nDraw中的代碼,繪制三個(gè)圈的方法都會(huì)接收一個(gè)相應(yīng)的degrees: Float參數(shù),這個(gè)是控制一個(gè)圈的整體旋轉(zhuǎn)的,而且要逆時(shí)針轉(zhuǎn),所以始終得是負(fù)數(shù)。
這樣一來就好說了,只要控制這三個(gè)角度變化,就能讓時(shí)鐘動(dòng)起來。
overridefunonDraw(canvas: Canvas?){
super.onDraw(canvas)
... //省略
//繪制各元件,后文會(huì)涉及到
drawCenterInfo(canvas)
drawHour(canvas, mHourDeg)
drawMinute(canvas, mMinuteDeg)
drawSecond(canvas, mSecondDeg)
... //省略
}
那么首先定義三個(gè)角度的全局變量,并把他們與實(shí)際的時(shí)間關(guān)聯(lián)起來,然后每隔一秒觸發(fā)一次View的重繪即可。
//定義三個(gè)角度的全局變量
privatevarmHourDeg: FloatbyDelegates.notNull
privatevarmMinuteDeg: FloatbyDelegates.notNull
privatevarmSecondDeg: FloatbyDelegates.notNull
/**
* 繪制方法
*/
fundoInvalidate{
Calendar.getInstance.run {
valhour = get(Calendar.HOUR)
valminute = get(Calendar.MINUTE)
valsecond = get(Calendar.SECOND)
//這里將三個(gè)角度與實(shí)際時(shí)間關(guān)聯(lián)起來,當(dāng)前幾點(diǎn)幾分幾秒,就把相應(yīng)的圈逆時(shí)針旋轉(zhuǎn)多少
mHourDeg = -360/ 12f * (hour - 1)
mMinuteDeg = -360/ 60f * (minute - 1)
mSecondDeg = -360/ 60f * (second - 1)
invalidate
}
}
然后只需在Activity中使用timer每秒鐘刷新一次View即可。效果如下圖,會(huì)發(fā)現(xiàn)轉(zhuǎn)是轉(zhuǎn)起來的,但是卻每秒一跳。再看一下咱們當(dāng)時(shí)的分析:
每秒鐘「秒圈」走一下,這一下的旋轉(zhuǎn)角度為360°/60=6°,并且走這一下的時(shí)候有個(gè)線性旋轉(zhuǎn)過去的動(dòng)畫效果。
所以是還差一個(gè)線性旋轉(zhuǎn)的效果。
//Activity中的代碼
privatevarmTimer: Timer? = null
privatefuncaseTextClock{
setContentView(R.layout.activity_stage_text_clock)
mTimer = timer(period = 1000) {
runOnUiThread {
stage_textClock.doInvalidate
}
}
}
overridefunonDestroy{
super.onDestroy
mTimer?.cancel
}
5. 讓時(shí)鐘轉(zhuǎn)的優(yōu)雅點(diǎn)
基于我們已經(jīng)知道了,時(shí)鐘動(dòng)起來的本質(zhì)就是在一段時(shí)間內(nèi)(比如150ms)不斷的改變參數(shù)degrees: Float的值并觸發(fā)重繪方法,這樣就產(chǎn)生了人眼看到的動(dòng)畫效果。
所以,我們想讓「秒圈」(三個(gè)圈的代表)轉(zhuǎn)的更線性更優(yōu)雅一點(diǎn),就可以在要開始繪制新的一秒的時(shí)候,在前150ms線性的旋轉(zhuǎn)6°。
init {
//處理動(dòng)畫,聲明全局的處理器
mAnimator = ValueAnimator.ofFloat( 6f, 0f) //由6降到1
mAnimator.duration = 150
mAnimator.interpolator = LinearInterpolator //插值器設(shè)為線性
doInvalidate
}
/**
* 開始繪制
*/
fundoInvalidate{
Calendar.getInstance.run {
valhour = get(Calendar.HOUR)
valminute = get(Calendar.MINUTE)
valsecond = get(Calendar.SECOND)
mHourDeg = -360/ 12f * (hour - 1)
mMinuteDeg = -360/ 60f * (minute - 1)
mSecondDeg = -360/ 60f * (second - 1)
//記錄當(dāng)前角度,然后讓秒圈線性的旋轉(zhuǎn)6°
valhd = mHourDeg
valmd = mMinuteDeg
valsd = mSecondDeg
//處理動(dòng)畫
mAnimator.removeAllUpdateListeners //需要移除先前的監(jiān)聽
mAnimator.addUpdateListener {
valav = (it.animatedValue asFloat)
if(minute == 0&& second == 0) {
mHourDeg = hd + av * 5//時(shí)圈旋轉(zhuǎn)角度是分秒的5倍,線性的旋轉(zhuǎn)30°
}
if(second == 0) {
mMinuteDeg = md + av //線性的旋轉(zhuǎn)6°
}
mSecondDeg = sd + av //線性的旋轉(zhuǎn)6°
invalidate
}
mAnimator.start
}
}
就用這美麗優(yōu)雅的時(shí)鐘結(jié)尾吧~
文末
個(gè)人能力有限,如有不正之處歡迎大家批評(píng)指出,我會(huì)虛心接受并第一時(shí)間修改,以不誤導(dǎo)大家。
https://github.com/drawf/SourceSet/blob/master/app/src/main/java/me/erwa/sourceset/view/TextClockView.kt返回搜狐,查看更多
責(zé)任編輯:
總結(jié)
以上是生活随笔為你收集整理的html时钟自动刷新抖音,抖音上炫酷的网红文字时钟的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pycharm中使用scrapy命命
- 下一篇: IDEA快捷键的使用成就手速之旅(要想手