一、SpeedView
<com.github.anastr.speedviewlib.SpeedView
android:id="@+id/speedView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:sv_withIndicatorLight="true"/>
<Button
android:id="@+id/b_open_control"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="control" />
speedView = findViewById(R.id.speedView)
ok = findViewById(R.id.ok)
ok.setOnClickListener {
// 設(shè)置值
speedView.speedTo(25f)
}
// 數(shù)值回調(diào)監(jiān)聽
speedView.speedTextListener = { speed: Float? -> String.format(Locale.getDefault(), "lol%.0f", speed) }
speedView.clearSections()
// 設(shè)置外圓樣式,從0-0.1顏色為LTGRAY,寬度為30f,樣式默認(rèn)為BUTT,也可設(shè)置為Style.ROUND/Style.BUTT
// 源碼分析:
// class Section @JvmOverloads constructor(
// startOffset: Float, 開始的偏移量
// endOffset: Float, 結(jié)束的偏移量
// color: Int, 顏色
// width: Float = 0f, 寬度
// style: Style = Style.BUTT, 樣式
//): Parcelable {
speedView.addSections(
Section(0f, .1f, Color.LTGRAY, speedView.dpTOpx(30f)),
Section(.1f, .2f, Color.GRAY, speedView.dpTOpx(30f), Style.ROUND),
Section(.2f, .3f, Color.DKGRAY, speedView.dpTOpx(30f)),
Section(.3f, .4f, Color.BLACK, speedView.dpTOpx(30f)),
Section(.4f, .5f, Color.CYAN, speedView.dpTOpx(30f), Style.ROUND),
Section(.5f, .6f, Color.BLUE, speedView.dpTOpx(30f)),
Section(.6f, .7f, Color.GREEN, speedView.dpTOpx(30f)),
Section(.7f, .8f, Color.YELLOW, speedView.dpTOpx(30f)),
Section(.8f, .9f, Color.MAGENTA, speedView.dpTOpx(30f))
)
// Section的監(jiān)聽回調(diào)previousSection是指針滑動過的部分,newSection是被指定的部分
speedView.onSectionChangeListener = { previousSection: Section?, newSection: Section? ->
if (previousSection != null) {
// previousSection.setPadding(10);
previousSection.width = speedView.dpTOpx(3f)
}
if (newSection != null) {
// newSection.setPadding(0);
newSection.width = speedView.dpTOpx(15f)
}
Unit
}
setSpeedAt:該函數(shù) setSpeedAt 用于立即設(shè)置速度值,不帶動畫效果,具體邏輯如下:
限制速度范圍:將傳入的 speed 限制在 maxSpeed 和 minSpeed 之間;
判斷加速方向:更新 isSpeedIncrease 表示當(dāng)前是否為加速狀態(tài);
更新速度值:同時更新 speed 和 currentSpeed;
取消動畫:調(diào)用 cancelSpeedAnimator() 停止可能存在的動畫;
刷新視圖并抖動:調(diào)用 invalidate() 刷新界面,tremble() 執(zhí)行抖動效果。
speedPercentTo:該函數(shù) speedPercentTo 的功能是將指定的百分比值轉(zhuǎn)換為實際速度值,并在給定的動畫時長內(nèi)平滑過渡到該速度。
參數(shù)說明:
percent:百分比值(0 到 100)。
moveDuration:動畫持續(xù)時間,默認(rèn)為 2000 毫秒。
功能邏輯:
調(diào)用 getSpeedValue(percent.toFloat()) 將百分比轉(zhuǎn)為實際速度值。
再調(diào)用 speedTo(...) 執(zhí)行帶動畫的速度變化。
注解作用:
@JvmOverloads 支持在 Java 中調(diào)用時使用默認(rèn)參數(shù)。
speedTo:該函數(shù) speedTo 用于平滑地將速度從當(dāng)前值動畫過渡到目標(biāo)速度,并確保速度始終在 [minSpeed, maxSpeed] 范圍內(nèi)。
具體邏輯如下:
限制速度范圍:若傳入的 speed 超出 [minSpeed, maxSpeed],則自動修正為邊界值。
判斷是否變化:如果新速度與當(dāng)前速度相同,則直接返回不執(zhí)行動畫。
設(shè)置動畫方向:記錄是加速還是減速(isSpeedIncrease)。
取消舊動畫:調(diào)用 cancelSpeedAnimator() 停止正在進行的速度動畫。
創(chuàng)建并啟動動畫:使用 ValueAnimator 實現(xiàn)平滑過渡,使用 DecelerateInterpolator 實現(xiàn)減速效果,并通過監(jiān)聽器更新當(dāng)前速度和刷新界面。
stop():該 stop() 函數(shù)用于停止速度表動畫,并根據(jù)條件觸發(fā)震動效果。具體邏輯如下:
如果 speedAnimator 和 realSpeedAnimator 都不在運行,直接返回;
將當(dāng)前速度保存到 speed;
取消速度動畫;
調(diào)用 tremble() 方法產(chǎn)生震動效果。
realSpeedTo :該函數(shù) realSpeedTo 用于模擬速度變化的真實感動畫,具體功能如下:
限制速度范圍:確保目標(biāo)速度不超過最大/最小值。
判斷加速或減速:根據(jù)當(dāng)前速度與目標(biāo)速度比較,決定是加速還是減速。
創(chuàng)建屬性動畫:使用 ValueAnimator 實現(xiàn)平滑過渡效果。
動態(tài)更新當(dāng)前速度:
加速時:按 accelerate 值緩慢增加;
減速時:按 decelerate 值快速減少;
刷新界面并結(jié)束動畫:當(dāng)速度達到目標(biāo)值時停止動畫并重繪視圖。
speedView.clearSections(): 該函數(shù)用于移除所有儀表盤中的“section”區(qū)域,并刷新視圖。具體邏輯如下:
遍歷 _sections 列表,調(diào)用每個元素的 clearGauge() 方法,清除對應(yīng)區(qū)域的儀表數(shù)據(jù);
清空 _sections 列表;
調(diào)用 invalidateGauge() 方法,觸發(fā)視圖重繪。
其他設(shè)置
設(shè)置最大值:
speedView.maxSpeed = 50f
設(shè)置儀表圓外邊框的寬度:
speedView.speedometerWidth = 5
設(shè)置數(shù)值文本的字體大?。?speedView.speedTextSize = 5f
設(shè)置指針顏色:
speedView.indicator.color = Color.parseColor("#000000")
設(shè)置中間圓點的顏色:
speedView.centerCircleColor = Color.parseColor("#000000")
設(shè)置區(qū)間樣式:
private fun setLowSpeedColor() {
val lowSpeedColor = findViewById<EditText>(R.id.lowSpeedColor)
try {
speedView.sections[0].startOffset = 0f
speedView.sections[0].endOffset = .1f
speedView.sections[0].color = Color.parseColor(lowSpeedColor.text.toString())
} catch (e: Exception) {
lowSpeedColor.error = e.message
}
}
private fun setMediumSpeedColor() {
val mediumSpeedColor = findViewById<EditText>(R.id.mediumSpeedColor)
try {
speedView.sections[1].startOffset = .1f
speedView.sections[1].endOffset = 0.8f
speedView.sections[1].color = Color.parseColor(mediumSpeedColor.text.toString())
} catch (e: Exception) {
mediumSpeedColor.error = e.message
}
}
private fun setHighSpeedColor() {
val highSpeedColor = findViewById<EditText>(R.id.highSpeedColor)
try {
speedView.sections[2].startOffset = .8f
speedView.sections[2].endOffset = 1f
speedView.sections[2].color = Color.parseColor(highSpeedColor.text.toString())
} catch (e: Exception) {
highSpeedColor.error = e.message
}
}
是否啟用抖動效果:
speedView.withTremble = true
是否啟用其他特效:
speedView.isWithEffects = true
設(shè)置刻度線密度(數(shù)量):
speedView.setDegreeBetweenMark(10)
設(shè)置刻度線寬度:
speedView.rayMarkWidth = 10f
二、Pointer Speedometer

<com.github.anastr.speedviewlib.PointerSpeedometer
android:id="@+id/pointerSpeedometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:sv_unitTextSize="15sp"
app:sv_indicatorLightColor="@android:color/white"
app:sv_withIndicatorLight="true"
app:sv_speedTextTypeface="fonts/good-times.ttf" />
// 監(jiān)聽回調(diào)
pointerSpeedometer.onSpeedChangeListener =
{ gauge: Gauge, _: Boolean?, _: Boolean? ->
textSpeedChange.text = String.format(
Locale.getDefault(), "onSpeedChange %d", gauge.currentIntSpeed
)
}
三、Tube Speedometer

<com.github.anastr.speedviewlib.TubeSpeedometer
android:id="@+id/speedometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:sv_speedTextSize="22sp"
app:sv_speedTextTypeface="fonts/James-Almacen.ttf"/>
四、Image Speedometer

<com.github.anastr.speedviewlib.ImageSpeedometer
android:id="@+id/imageSpeedometer"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:sv_image="@drawable/for_image_speedometer"
app:sv_indicator="LineIndicator"
app:sv_indicatorColor="#a750bcff"
app:sv_speedTextColor="#fff"
app:sv_unitTextColor="#fff"/>
四、Progressive Gauge

<com.github.anastr.speedviewlib.ProgressiveGauge
android:id="@+id/gauge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:sv_speedTextPosition="BOTTOM_RIGHT"
app:sv_unitUnderSpeedText="false"
app:sv_speedTextSize="25dp"
app:sv_unitTextSize="18dp"
app:sv_speedTextColor="#fff"
app:sv_unitTextColor="#fff"
app:sv_speedometerBackColor="#b3b3b3"
app:sv_speedometerColor="#dc4ae1" />
五、Image Linear Gauge


<com.github.anastr.speedviewlib.ImageLinearGauge
android:id="@+id/gauge"
android:layout_width="wrap_content"
android:layout_height="300dp"
android:layout_gravity="center_horizontal"
app:sv_orientation="HORIZONTAL"
app:sv_speedTextPosition="BOTTOM_CENTER"
app:sv_unitUnderSpeedText="false"
app:sv_speedometerBackColor="#9e9e9e"
app:sv_image="@drawable/fire"/>
checkBoxOrientation.setOnCheckedChangeListener { _, b ->
if (b) imageLinearGauge.orientation =
LinearGauge.Orientation.VERTICAL else imageLinearGauge.orientation =
LinearGauge.Orientation.HORIZONTAL
}
六、指針設(shè)置

<com.github.anastr.speedviewlib.SpeedView
android:id="@+id/speedometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:sv_speedTextSize="11sp"
app:sv_unitTextSize="8sp"
app:sv_textSize="8sp"
app:sv_startDegree="110"
app:sv_endDegree="430"
app:sv_speedometerWidth="45dp"
app:sv_indicator="NormalIndicator"
app:sv_indicatorWidth="20dp"
app:sv_centerCircleColor="#0000"
app:sv_indicatorColor="#df4346" />
設(shè)置指針樣式:
speedometer.setIndicator(Indicator.Indicators.NoIndicator)
設(shè)置指針寬度:
speedometer.indicator.width = 10f
設(shè)置自定義指針樣式:
val imageIndicator = ImageIndicator(applicationContext, ContextCompat.getDrawable(this, R.drawable.image_indicator1)!! )
speedometer.indicator = imageIndicator
七、刻度線設(shè)置

<com.github.anastr.speedviewlib.SpeedView
android:id="@+id/speedometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:sv_markColor="#414141"
app:sv_markWidth="3dp"/>
設(shè)置刻度線樣式:
speedometer.markStyle = if(isChecked) Style.ROUND else Style.BUTT
設(shè)置刻度線數(shù)量:
speedometer.marksNumber = 10
設(shè)置刻度線高度:
speedometer.markHeight = 10f
設(shè)置刻度線寬度:
speedometer.markWidth = 10f
八、引導(dǎo)提示

<com.github.anastr.speedviewlib.SpeedView
android:id="@+id/speedView"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="20dp"
app:sv_speedTextSize="23dp"
app:sv_indicatorColor="#03A9F4"
app:sv_centerCircleColor="#795548"/>
findViewById<View>(R.id.b_center).setOnClickListener { noteCenter() }
findViewById<View>(R.id.b_center_indicator).setOnClickListener { noteCenterIndicator() }
findViewById<View>(R.id.b_top_center).setOnClickListener { noteTopIndicator() }
findViewById<View>(R.id.b_image).setOnClickListener { noteImageNote() }
findViewById<View>(R.id.b_spannable).setOnClickListener { noteSpannableString() }
private fun noteCenter() {
val type = Typeface.createFromAsset(assets, "fonts/lhandw.ttf")
val note = TextNote(applicationContext, "Wait !")
.setPosition(Note.Position.CenterSpeedometer)
.setTextTypeFace(type)
.setTextSize(speedView.dpTOpx(20f))
speedView.addNote(note, 2000)
}
private fun noteCenterIndicator() {
val note = TextNote(applicationContext, "Stop !!")
.setPosition(Note.Position.CenterIndicator)
.setTextTypeFace(Typeface.create(Typeface.DEFAULT, Typeface.BOLD))
.setTextSize(speedView.dpTOpx(13f))
speedView.addNote(note, 2000)
}
private fun noteTopIndicator() {
val note = TextNote(applicationContext, "TOP")
.setPosition(Note.Position.TopIndicator)
.setAlign(Note.Align.Bottom)
.setTextSize(speedView.dpTOpx(13f))
speedView.addNote(note, 2000)
}
private fun noteImageNote() {
val imageNote = ImageNote(
applicationContext, R.mipmap.ic_launcher
)
.setPosition(Note.Position.BottomIndicator)
speedView.addNote(imageNote, 2000)
}
private fun noteSpannableString() {
val s = SpannableString("Speedometer")
s.setSpan(RelativeSizeSpan(1.2f), 0, 11, 0)
s.setSpan(RelativeSizeSpan(1.7f), 0, 1, 0)
s.setSpan(ForegroundColorSpan(Color.parseColor("#64DD17")), 0, 1, 0)
s.setSpan(ForegroundColorSpan(Color.parseColor("#FF1744")), 1, 5, 0)
s.setSpan(ForegroundColorSpan(Color.parseColor("#AA00FF")), 5, 6, 0)
s.setSpan(RelativeSizeSpan(1.4f), 5, 6, 0)
s.setSpan(ForegroundColorSpan(Color.parseColor("#2196F3")), 6, 11, 0)
val note = TextNote(applicationContext, s)
.setBackgroundColor(Color.parseColor("#EEFF41"))
.setPosition(Note.Position.QuarterSpeedometer)
.setTextSize(speedView.dpTOpx(10f))
speedView.addNote(note, 2000)
}
九、刻度數(shù)值

<com.github.anastr.speedviewlib.SpeedView
android:id="@+id/speedometer"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:sv_tickNumber="6"/>
設(shè)置刻度數(shù)值是否跟隨指針旋轉(zhuǎn):
speedometer.isTickRotation = true
設(shè)置刻度數(shù)值顯示數(shù)量:
speedometer.tickNumber = 10
設(shè)置刻度數(shù)值位置:
speedometer.tickPadding = 10f
十、偏移量X,Y

<com.github.anastr.speedviewlib.SpeedView
android:id="@+id/speedometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:sv_centerCircleRadius="2dp"
app:sv_withTremble="false"
app:sv_indicator="NeedleIndicator" />
//該函數(shù)用于設(shè)置指示器旋轉(zhuǎn)的支點位置,參數(shù) xOffset 和 yOffset 表示在 X 和 Y 軸上的比例位置(范圍為 [0f, 1f])
speedometer.setFulcrum(fulcrumX, fulcrumY)
speedometer.setFulcrum(.5f, .55f);
其他
設(shè)置圓邊框部分樣式(圓角還是平角):
app:sv_sectionStyle="ROUND"
設(shè)置圓邊框部分樣式顏色:
speedView.doOnSections { it.color = Color.rgb((0..255).random(), (0..255).random(), (0..255).random()) }
demo:

<RelativeLayout
android:layout_width="@dimen/dp_300"
android:layout_height="@dimen/dp_300">
<ImageView
android:layout_width="@dimen/dp_300"
android:layout_height="@dimen/dp_300"
android:src="@mipmap/icon_speed_panel"/>
<com.zn.core.panel.SpeedView
android:id="@+id/svSpeed"
app:sv_speedometerWidth="0dp"
app:sv_centerCircleColor="#123456"
app:sv_centerCircleRadius="@dimen/dp_25"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:sv_endDegree="380"
app:sv_indicator="KiteIndicator"
app:sv_indicatorColor="#eb1912"
app:sv_indicatorWidth="@dimen/dp_5"
app:sv_marksNumber="0"
app:sv_maxSpeed="40"
app:sv_minSpeed="0"
app:sv_speedTextColor="@color/white"
app:sv_speedTextFormat="INTEGER"
app:sv_speedTextSize="@dimen/dp_20"
app:sv_startDegree="160"
app:sv_textColor="@color/white"
app:sv_tickNumber="0"
app:sv_unit="x100 r/min"
app:sv_speedTextPadding="@dimen/dp_60"
app:sv_unitTextColor="@color/white"
app:sv_unitTextSize="@dimen/dp_20"
app:sv_unitUnderSpeedText="true"
app:sv_withTremble="false" />
</RelativeLayout>

<com.zn.core.panel.ImageSpeedometer
android:id="@+id/svSpeed"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:sv_endDegree="380"
app:sv_image="@mipmap/icon_speed_panel"
app:sv_indicator="KiteIndicator"
app:sv_indicatorColor="#eb1912"
app:sv_indicatorWidth="@dimen/dp_5"
app:sv_marksNumber="0"
app:sv_maxSpeed="40"
app:sv_minSpeed="0"
app:sv_speedTextColor="@color/white"
app:sv_speedTextFormat="INTEGER"
app:sv_speedTextPosition="CENTER_BOTTOM"
app:sv_speedTextSize="@dimen/dp_20"
app:sv_startDegree="160"
app:sv_textColor="@color/white"
app:sv_tickNumber="0"
app:sv_unit="x100 r/min"
app:sv_unitTextColor="@color/white"
app:sv_unitTextSize="@dimen/dp_20"
app:sv_unitUnderSpeedText="false"
app:sv_withTremble="false" />