Android實(shí)現(xiàn)雷達(dá)View效果的示例代碼
樣式效果
還是先來(lái)看效果:


這是一個(gè)仿雷達(dá)掃描的效果,是之前在做地圖sdk接入時(shí)就想實(shí)現(xiàn)的效果,但之前由于趕著畢業(yè)設(shè)計(jì),就沒(méi)有親手去實(shí)現(xiàn),不過(guò)現(xiàn)在自己擼一個(gè)發(fā)現(xiàn)還是挺簡(jiǎn)單的。
這里主要分享一下我的做法。
目錄
主體輪廓的實(shí)現(xiàn)(雷達(dá)的結(jié)構(gòu))
動(dòng)畫(huà)的實(shí)現(xiàn)(雷達(dá)掃描的效果)
目標(biāo)點(diǎn)的加入(圖片/點(diǎn))
主體輪廓實(shí)現(xiàn)

不難分析得出,這個(gè)View主要由外部的一個(gè)圓,中間的錨點(diǎn)圓以及扇形旋轉(zhuǎn)區(qū)域組成。而且每個(gè)部分理應(yīng)由不同的Paint去繪制,以方便去定制各部分的樣式。
外部圓以及錨點(diǎn)圓的繪制較為簡(jiǎn)單,主要的點(diǎn)還是要對(duì)整個(gè)View的寬高進(jìn)行一定的限制,例如寬高必須相等且在某種模式下,取小的那個(gè)值來(lái)限定整個(gè)RadarView的最大值。那么該如何去控制呢?
onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
由于我們繼承自View,在onMeasure方法中,我們可以根據(jù)兩個(gè)參數(shù)來(lái)獲取Mode,并且根據(jù)Mode來(lái)指定寬/高對(duì)應(yīng)的值,再通過(guò)setMeasuredDimension去指定控件主體的寬高即可。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val vWidth = measureDimension(widthMeasureSpec)
val vHeight = measureDimension(heightMeasureSpec)
val size = min(vWidth, vHeight)
setMeasuredDimension(size, size)
}
private fun measureDimension(spec: Int) = when (MeasureSpec.getMode(spec)) {
MeasureSpec.EXACTLY -> {
// exactly number or match_parent
MeasureSpec.getSize(spec)
}
MeasureSpec.AT_MOST -> {
// wrap_content
min(mDefaultSize, MeasureSpec.getSize(spec))
}
else -> {
mDefaultSize
}
}
測(cè)量工作完成了,我們自然可以去繪制了。為了不讓中間的小圓看起來(lái)那么突兀(偏大或偏?。?,這里設(shè)置了一個(gè)scaleFactor的縮放因子,使其能根據(jù)外圓的尺寸來(lái)進(jìn)行縮放。
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// draw outside circle (background)
canvas?.drawCircle(measuredWidth.toFloat() / 2, measuredHeight.toFloat() / 2, measuredWidth.toFloat() / 2, mOutlinePaint)
if (mBorderWidth > 0F && mOutlinePaint.shader == null) {
drawBorder(canvas)
}
// mOutlineRect = Rect(0, 0, measuredWidth, measuredHeight)
canvas?.drawArc(mOutlineRect.toRectF(), mStartAngle, mSweepAngle, true, mSweepPaint)
// draw center circle
// scaleFactor = 30F
canvas?.drawCircle(measuredWidth.toFloat() / 2, measuredHeight.toFloat() / 2, measuredWidth.toFloat() / 2 / mScaleFactor, mPaint)
}
private fun drawBorder(canvas: Canvas?) {
Log.i("RadarView", "drawBorder")
mOutlinePaint.style = Paint.Style.STROKE
mOutlinePaint.color = mBorderColor
mOutlinePaint.strokeWidth = mBorderWidth
canvas?.drawCircle(measuredWidth.toFloat() / 2, measuredHeight.toFloat() / 2,
(measuredWidth.toFloat() - mBorderWidth) / 2, mOutlinePaint)
// 還原
mOutlinePaint.style = Paint.Style.FILL_AND_STROKE
mOutlinePaint.color = mBackgroundColor
}
繪制了基準(zhǔn)圓以后,要實(shí)現(xiàn)雷達(dá)掃描時(shí)那種漸變的效果,我們可以通過(guò)SweepGradient來(lái)操作。通過(guò)指定中心點(diǎn),漸變顏色,以及顏色的分布,來(lái)定制掃描漸變的樣式,默認(rèn)的即時(shí)開(kāi)頭時(shí)gif展示的那種。由于這里是從第一象限開(kāi)始旋轉(zhuǎn),因此將旋轉(zhuǎn)的起點(diǎn)通過(guò)matrix逆時(shí)針旋轉(zhuǎn)90度,從而達(dá)到由淺入深的效果。
private fun setShader(size: Int) {
val shader = SweepGradient(size.toFloat() / 2, size.toFloat() / 2,
mScanColors?: mDefaultScanColors, // 可通過(guò)setScanColors()來(lái)定制顏色
floatArrayOf(0F, 0.5F, 1F)) // 這里默認(rèn)走平均分布
val matrix = Matrix()
// 逆時(shí)針旋轉(zhuǎn)90度
matrix.setRotate(-90F, size.toFloat() / 2, size.toFloat() / 2)
shader.setLocalMatrix(matrix)
mSweepPaint.shader = shader
}
這里完成了測(cè)量與繪制的工作,那么我們?cè)诓季掷镆靡院?,就?huì)看到這樣的效果:

這時(shí),由于我們之前在測(cè)量的時(shí)候,將寬高最小值作為繪制的基準(zhǔn)大小給予了RadarView,因此measuredWidth和measuredHeight是相等的,但是由于在布局中指定了match_parent屬性,那么實(shí)際的控件寬高還是和父布局一致(在這里即占滿屏幕寬高,由于寬比高小,所以看到繪制的圖形會(huì)偏向上方;如果設(shè)置了高比寬小,那么繪制的圖形就會(huì)位于左側(cè))。一般的雷達(dá)控件應(yīng)該都是居中顯示的,所以我在這里也重寫(xiě)了onLayout方法,來(lái)實(shí)現(xiàn)居中的效果。
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
// 設(shè)置默認(rèn)居中
var l = left
var r = right
var t = top
var b = bottom
when {
width > height -> {
// 寬度比高度大 那么要設(shè)置默認(rèn)居中就得把left往右移 right往左移
l = (width - measuredWidth) / 2
r = width - l
layout(l, t, r, b)
}
height > width -> {
// 高度比寬度大 那么要設(shè)置默認(rèn)居中就得把top往下移 bottom往上移
t = (height - measuredHeight) / 2
b = height - t
layout(l, t, r, b)
}
else -> super.onLayout(changed, left, top, right, bottom)
}
}
動(dòng)畫(huà)的實(shí)現(xiàn)
完成了繪制,接下來(lái)就是思考該如何讓他動(dòng)起來(lái)了。由繪制的代碼不難想到,我這里考慮的是通過(guò)mStartAngle的變化來(lái)控制繪制的角度旋轉(zhuǎn),而ValueAnimator則正好能獲取到每次更新時(shí)value的值,因此這里我選用了這個(gè)方案。
fun start() {
Log.i("RadarView", "animation start")
mIsAnimating = true
mAnimator.duration = 2000
mAnimator.repeatCount = ValueAnimator.INFINITE
mAnimator.addUpdateListener {
val angle = it.animatedValue as Float
mStartAngle = angle
// Log.i("RadarView", "mStartAngle = $mStartAngle and curValue = ${it.animatedValue}")
postInvalidate()
}
mAnimator.start()
}
坑
這里就需要注意一個(gè)點(diǎn),就是canvas在繪制時(shí),后繪制的會(huì)覆蓋在前繪制的圖像上,所以需要注意繪制的順序。當(dāng)然,這里也可以把mOutlineRect的寬高設(shè)置為measuredWidth - mBorderWidth,那么就能保證繪制填充角度時(shí),不會(huì)把邊界覆蓋。
至此,動(dòng)畫(huà)的效果便完成了。
目標(biāo)點(diǎn)的加入
首先,前兩點(diǎn)已經(jīng)能滿足大多的雷達(dá)掃描需求了。這里這個(gè)添加目標(biāo)點(diǎn)(target)純粹是我自己想加入的功能,因?yàn)橛X(jué)得可以結(jié)合地圖sdk的MapView來(lái)共同使用,目前也只是開(kāi)發(fā)階段,擴(kuò)展性可能考慮得還不是特別充足,也還沒(méi)應(yīng)用到具體項(xiàng)目中。但是,總覺(jué)得自己想的功能也該試著去實(shí)踐一下~
這里主要運(yùn)用的圓的計(jì)算公式:

由于Android的坐標(biāo)系的原點(diǎn)是在左上角,y軸過(guò)頂點(diǎn)向下延伸。由我們的繪制可知,此繪制圖像在坐標(biāo)系中的位置大概如下圖所示:

那么,對(duì)應(yīng)的公式就為:

要注意的是,這里r的計(jì)算會(huì)根據(jù)圖/點(diǎn)的設(shè)置來(lái)動(dòng)態(tài)計(jì)算,具體例子通過(guò)代碼來(lái)進(jìn)行分析。
// 隨機(jī)落點(diǎn)
fun addTarget(size: Int, type: TYPE = TYPE.RANDOM) {
val list = ArrayList<PointF>()
val r = measuredWidth.toFloat() / 2
val innerRect = Rect((r - r / mScaleFactor).toInt(), (r - r / mScaleFactor).toInt(),
(r + r / mScaleFactor).toInt(), (r + r / mScaleFactor).toInt())
// 圓的中心點(diǎn)
val circle = PointF(measuredWidth.toFloat() / 2, measuredHeight.toFloat() / 2)
while (list.size < size) {
val ranX = Random.nextDouble(0.0, r * 2.0).toFloat()
val ranY = Random.nextDouble(0.0, r * 2.0).toFloat()
val ranPointF = PointF(ranX, ranY)
if (innerRect.contains(ranPointF.toPoint())) {
continue
}
// 圓公式
if (!mNeedBitmap &&
(ranX - circle.x).pow(2) + (ranY - circle.y).pow(2) <
(r - mTargetRadius - mBorderWidth).toDouble().pow(2.0)) {
// 普通點(diǎn)
addTargetFromType(type, list, ranX, ranY, r, ranPointF)
} else if (mNeedBitmap &&
(ranX - circle.x).pow(2) + (ranY - circle.y).pow(2) <
(r - mBorderWidth - max(mBitmap.width, mBitmap.height) / 2).toDouble().pow(2)) {
// 圖
addTargetFromType(type, list, ranX, ranY, r, ranPointF)
} else {
continue
}
}
mTargetList = list
for (target in list) {
Log.i("RadarView", "target = [${target.x}, ${target.y}]")
}
invalidate()
}
可以看到,當(dāng)target為普通點(diǎn)時(shí),r的計(jì)算還要減去targetRadius,即目標(biāo)點(diǎn)的半徑,同時(shí)還要減去邊界的寬度,如圖所示:

當(dāng)target為圖時(shí),由于寬高不定,故除了邊界外,還要減去大的邊,那么r的計(jì)算則為:

同時(shí)為了避免圖片的尺寸過(guò)大,這里同樣采取了一個(gè)默認(rèn)值與一個(gè)縮放因子,從而保證圖的完整性以及避免過(guò)大而引起的視覺(jué)丑化。
關(guān)于落點(diǎn)的位置,目前采取的是隨機(jī)落點(diǎn),如果應(yīng)用到地圖掃點(diǎn)的話,可以通過(guò)地圖sdk內(nèi)的距離計(jì)算工具再與RadarView的坐標(biāo)做一個(gè)比例轉(zhuǎn)換,從而達(dá)到雷達(dá)內(nèi)顯示該點(diǎn)具體方位。
關(guān)于落點(diǎn)的分布,目前提供了5種類型:分別是全象限隨機(jī)、第一象限、第二象限、第三象限與第四象限隨機(jī)。
Github
若須直接調(diào)用,可移步至 https://github.com/CarsonWoo/RadarView
完整代碼
class RadarView : View {
enum class TYPE { RANDOM, FIRST, SECOND, THIRD, FOURTH }
private val mPaint by lazy { Paint(Paint.ANTI_ALIAS_FLAG) }
private val mSweepPaint by lazy { Paint(Paint.ANTI_ALIAS_FLAG) }
private val mOutlinePaint by lazy { Paint(Paint.ANTI_ALIAS_FLAG) }
private val mTargetPaint by lazy { Paint(Paint.ANTI_ALIAS_FLAG) }
private val mDefaultSize = 120// px
// limit the size of bitmap
private var mBitmapMaxSize = 0F
private var mBitmapWHRatio = 0F
private val mScaleFactor = 30F
private var mStartAngle = 0F
private val mSweepAngle = -60F
private var mScanColors: IntArray? = null
private val mDefaultScanColors = intArrayOf(Color.parseColor("#0F7F7F7F"),
Color.parseColor("#7F7F7F7F"),
Color.parseColor("#857F7F7F"))
private val mDefaultBackgroundColor = Color.WHITE
private var mBackgroundColor: Int = mDefaultBackgroundColor
private var mBorderColor: Int = Color.BLACK
private var mBorderWidth = 0F
private var mTargetColor: Int = Color.RED
private var mTargetRadius = 10F
private lateinit var mOutlineRect: Rect
private val mAnimator = ValueAnimator.ofFloat(0F, 360F)
private var mTargetList: ArrayList<PointF>? = null
private var mIsAnimating = false
private var mNeedBitmap = false
private var mBitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
constructor(context: Context): this(context, null)
constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet)
init {
mPaint.color = Color.GRAY
mPaint.strokeWidth = 10F
mPaint.style = Paint.Style.FILL_AND_STROKE
mPaint.strokeJoin = Paint.Join.ROUND
mPaint.strokeCap = Paint.Cap.ROUND
mSweepPaint.style = Paint.Style.FILL
mOutlinePaint.style = Paint.Style.FILL_AND_STROKE
mOutlinePaint.color = mBackgroundColor
mTargetPaint.style = Paint.Style.FILL
mTargetPaint.color = mTargetColor
mTargetPaint.strokeWidth = 10F
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val vWidth = measureDimension(widthMeasureSpec)
val vHeight = measureDimension(heightMeasureSpec)
val size = min(vWidth, vHeight)
setShader(size)
setMeasuredDimension(size, size)
setParamUpdate()
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
// 設(shè)置默認(rèn)居中
var l = left
var r = right
var t = top
var b = bottom
when {
width > height -> {
// 寬度比高度大 那么要設(shè)置默認(rèn)居中就得把left往右移 right往左移
l = (width - measuredWidth) / 2
r = width - l
layout(l, t, r, b)
}
height > width -> {
// 高度比寬度大 那么要設(shè)置默認(rèn)居中就得把top往下移 bottom往上移
t = (height - measuredHeight) / 2
b = height - t
layout(l, t, r, b)
}
else -> super.onLayout(changed, left, top, right, bottom)
}
}
private fun setShader(size: Int) {
val shader = SweepGradient(size.toFloat() / 2, size.toFloat() / 2,
mScanColors?: mDefaultScanColors,
floatArrayOf(0F, 0.5F, 1F))
val matrix = Matrix()
matrix.setRotate(-90F, size.toFloat() / 2, size.toFloat() / 2)
shader.setLocalMatrix(matrix)
mSweepPaint.shader = shader
}
fun setScanColors(colors: IntArray) {
this.mScanColors = colors
setShader(measuredWidth)
invalidate()
}
fun setRadarColor(@ColorInt color: Int) {
this.mBackgroundColor = color
this.mOutlinePaint.color = color
invalidate()
}
fun setRadarColor(colorString: String) {
if (!colorString.startsWith("#") || colorString.length != 7 || colorString.length != 9) {
Log.e("RadarView", "colorString parse error, please check your enter param")
return
}
val color = Color.parseColor(colorString)
setRadarColor(color)
}
fun setBorderColor(@ColorInt color: Int) {
this.mBorderColor = color
invalidate()
}
fun setBorderColor(colorString: String) {
if (!colorString.startsWith("#") || colorString.length != 7 || colorString.length != 9) {
Log.e("RadarView", "colorString parse error, please check your enter param")
return
}
val color = Color.parseColor(colorString)
setBorderColor(color)
}
fun setRadarGradientColor(colors: IntArray) {
val shader = SweepGradient(measuredWidth.toFloat() / 2,
measuredHeight.toFloat() / 2, colors, null)
mOutlinePaint.shader = shader
invalidate()
}
fun setBorderWidth(width: Float) {
this.mBorderWidth = width
invalidate()
}
private fun setParamUpdate() {
mOutlineRect = Rect(0, 0, measuredWidth, measuredHeight)
mBitmapMaxSize = measuredWidth.toFloat() / mScaleFactor
}
private fun measureDimension(spec: Int) = when (MeasureSpec.getMode(spec)) {
MeasureSpec.EXACTLY -> {
// exactly number or match_parent
MeasureSpec.getSize(spec)
}
MeasureSpec.AT_MOST -> {
// wrap_content
min(mDefaultSize, MeasureSpec.getSize(spec))
}
else -> {
mDefaultSize
}
}
override fun setBackground(background: Drawable?) {
// 取消傳統(tǒng)背景設(shè)置
// super.setBackground(background)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// draw outside circle (background)
canvas?.drawCircle(measuredWidth.toFloat() / 2, measuredHeight.toFloat() / 2, measuredWidth.toFloat() / 2, mOutlinePaint)
if (mBorderWidth > 0F && mOutlinePaint.shader == null) {
drawBorder(canvas)
}
canvas?.drawArc(mOutlineRect.toRectF(), mStartAngle, mSweepAngle, true, mSweepPaint)
if (!mTargetList.isNullOrEmpty() && !mIsAnimating) {
drawTarget(canvas)
}
// draw center circle
canvas?.drawCircle(measuredWidth.toFloat() / 2, measuredHeight.toFloat() / 2, measuredWidth.toFloat() / 2 / mScaleFactor, mPaint)
}
private fun drawBorder(canvas: Canvas?) {
Log.i("RadarView", "drawBorder")
mOutlinePaint.style = Paint.Style.STROKE
mOutlinePaint.color = mBorderColor
mOutlinePaint.strokeWidth = mBorderWidth
canvas?.drawCircle(measuredWidth.toFloat() / 2, measuredHeight.toFloat() / 2,
(measuredWidth.toFloat() - mBorderWidth) / 2, mOutlinePaint)
// 還原
mOutlinePaint.style = Paint.Style.FILL_AND_STROKE
mOutlinePaint.color = mBackgroundColor
}
private fun drawTarget(canvas: Canvas?) {
mTargetList?.let {
Log.e("RadarView", "draw target")
for (target in it) {
if (mNeedBitmap) {
canvas?.drawBitmap(mBitmap, target.x - mBitmap.width / 2,
target.y - mBitmap.height / 2, mTargetPaint)
} else {
canvas?.drawCircle(target.x, target.y, mTargetRadius, mTargetPaint)
}
}
}
}
fun setBitmapEnabled(enabled: Boolean, drawable: Drawable) {
// 這里是為了防止界面還未獲取到寬高時(shí) 會(huì)導(dǎo)致onMeasure走不到 那么maxSize就會(huì)為0
post {
this.mNeedBitmap = enabled
this.mBitmapWHRatio = drawable.intrinsicWidth.toFloat() / drawable.intrinsicHeight.toFloat()
mBitmap = if (mBitmapWHRatio >= 1) {
// 寬比高大
drawable.toBitmap(
width = min(mBitmapMaxSize, drawable.intrinsicWidth.toFloat()).toInt(),
height = (min(mBitmapMaxSize, drawable.intrinsicWidth.toFloat()) / mBitmapWHRatio).toInt(),
config = Bitmap.Config.ARGB_8888)
} else {
// 高比寬大
drawable.toBitmap(
height = min(mBitmapMaxSize, drawable.intrinsicHeight.toFloat()).toInt(),
width = (min(mBitmapMaxSize, drawable.intrinsicHeight.toFloat()) * mBitmapWHRatio).toInt(),
config = Bitmap.Config.ARGB_8888
)
}
}
}
// 隨機(jī)落點(diǎn)
fun addTarget(size: Int, type: TYPE = TYPE.RANDOM) {
val list = ArrayList<PointF>()
val r = measuredWidth.toFloat() / 2
val innerRect = Rect((r - r / mScaleFactor).toInt(), (r - r / mScaleFactor).toInt(),
(r + r / mScaleFactor).toInt(), (r + r / mScaleFactor).toInt())
// 圓的中心點(diǎn)
val circle = PointF(measuredWidth.toFloat() / 2, measuredHeight.toFloat() / 2)
while (list.size < size) {
val ranX = Random.nextDouble(0.0, r * 2.0).toFloat()
val ranY = Random.nextDouble(0.0, r * 2.0).toFloat()
val ranPointF = PointF(ranX, ranY)
if (innerRect.contains(ranPointF.toPoint())) {
continue
}
// 圓公式
if (!mNeedBitmap &&
(ranX - circle.x).pow(2) + (ranY - circle.y).pow(2) <
(r - mTargetRadius - mBorderWidth).toDouble().pow(2.0)) {
// 在圓內(nèi)
addTargetFromType(type, list, ranX, ranY, r, ranPointF)
} else if (mNeedBitmap &&
(ranX - circle.x).pow(2) + (ranY - circle.y).pow(2) <
(r - mBorderWidth - max(mBitmap.width, mBitmap.height) / 2).toDouble().pow(2)) {
addTargetFromType(type, list, ranX, ranY, r, ranPointF)
} else {
continue
}
}
mTargetList = list
for (target in list) {
Log.i("RadarView", "target = [${target.x}, ${target.y}]")
}
invalidate()
}
private fun addTargetFromType(type: TYPE, list: ArrayList<PointF>, ranX: Float, ranY: Float,
r: Float, ranPointF: PointF) {
when (type) {
TYPE.RANDOM -> {
list.add(ranPointF)
}
TYPE.FOURTH -> {
if (ranX in r.toDouble()..2 * r.toDouble() && ranY in r.toDouble()..2 * r.toDouble()) {
list.add(ranPointF)
}
}
TYPE.THIRD -> {
if (ranX in 0.0..r.toDouble() && ranY in r.toDouble()..2 * r.toDouble()) {
list.add(ranPointF)
}
}
TYPE.SECOND -> {
if (ranX in 0.0..r.toDouble() && ranY in 0.0..r.toDouble()) {
list.add(ranPointF)
}
}
TYPE.FIRST -> {
if (ranX in r.toDouble()..2 * r.toDouble() && ranY in 0.0..r.toDouble()) {
list.add(ranPointF)
}
}
}
}
fun start() {
Log.i("RadarView", "animation start")
mIsAnimating = true
mAnimator.duration = 2000
mAnimator.repeatCount = ValueAnimator.INFINITE
mAnimator.addUpdateListener {
val angle = it.animatedValue as Float
mStartAngle = angle
Log.i("RadarView", "mStartAngle = $mStartAngle and curValue = ${it.animatedValue}")
postInvalidate()
}
mAnimator.start()
}
fun start(startVal: Float, endVal: Float) {
mIsAnimating = true
mAnimator.setFloatValues(startVal, endVal)
mAnimator.duration = 2000
mAnimator.repeatCount = ValueAnimator.INFINITE
mAnimator.addUpdateListener {
mStartAngle = it.animatedValue as Float
Log.i("RadarView", "mStartAngle = $mStartAngle and curValue = ${it.animatedValue}")
postInvalidate()
}
mAnimator.start()
}
fun stop() {
mIsAnimating = false
if (mAnimator.isRunning) {
mAnimator.cancel()
mAnimator.removeAllListeners()
}
mStartAngle = 0F
}
}
調(diào)用方式
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
radar_view.setBorderWidth(5F)
radar_view.setRadarColor(Color.TRANSPARENT)
radar_view.setBitmapEnabled(true, resources.getDrawable(R.mipmap.ic_launcher_round))
// radar_view.setScanColors(intArrayOf(Color.RED, Color.LTGRAY, Color.CYAN))
// radar_view.setRadarGradientColor(intArrayOf(Color.RED, Color.GREEN, Color.BLUE))
btn_start.setOnClickListener {
radar_view.start()
// workThreadAndCallback()
}
btn_stop.setOnClickListener {
radar_view.stop()
radar_view.addTarget(7)
}
}
總結(jié)
到此這篇關(guān)于Android實(shí)現(xiàn)雷達(dá)View效果的文章就介紹到這了,更多相關(guān)android 雷達(dá)View效果內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android運(yùn)用onTouchEvent自定義滑動(dòng)布局
這篇文章主要為大家詳細(xì)介紹了Android運(yùn)用onTouchEvent寫(xiě)一個(gè)上下滑動(dòng)的布局,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Android上傳文件到Web服務(wù)器 PHP接收文件
這篇文章主要為大家詳細(xì)介紹了Android上傳文件到Web服務(wù)器,PHP接收文件的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
android自定義view實(shí)現(xiàn)推箱子小游戲
這篇文章主要為大家詳細(xì)介紹了android自定義view實(shí)現(xiàn)推箱子小游戲,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
Android Jetpack架構(gòu)組件Lifecycle詳解
這篇文章主要介紹了Android Jetpack架構(gòu)組件Lifecycle詳解,Lifecycle是Jetpack架構(gòu)組件中用來(lái)感知生命周期的組件,使用Lifecycles可以幫助我們寫(xiě)出和生命周期相關(guān)更簡(jiǎn)潔更易維護(hù)的代碼。對(duì)此感興趣的小伙伴可以來(lái)學(xué)習(xí)一下2020-07-07
Android RecyclerView實(shí)現(xiàn)數(shù)據(jù)列表展示效果
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView實(shí)現(xiàn)數(shù)據(jù)列表展示效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Android中使用PagerSlidingTabStrip實(shí)現(xiàn)導(dǎo)航標(biāo)題的示例
本篇文章主要介紹了Android中使用PagerSlidingTabStrip實(shí)現(xiàn)導(dǎo)航標(biāo)題的示例,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01

