android自定义view绘制日出日落根据当前时间段更新进度

本文介绍了如何在Android中使用SunriseSunsetView类实现一个自定义视图,展示日出和日落时太阳在轨迹上的移动,包括轨迹颜色、宽度、太阳大小等属性,并支持动画效果。
摘要由CSDN通过智能技术生成
class SunriseSunsetView : View {


    private val DEFAULT_SUN_STROKE_WIDTH_PX = 4f

    /**
     * 当前日出日落比率, mRatio < 0: 未日出, mRatio > 1 已日落
     */
    private var mRatio = 0f

    private var mTrackPaint: Paint? = null // 绘制半圆轨迹的Paint

    private var mTrackColor = Color.WHITE // 轨迹的颜色

    private var mTrackWidth = 4 // 轨迹的宽度

    // 轨迹的PathEffect
    private var mTrackPathEffect: PathEffect = DashPathEffect(floatArrayOf(15f, 15f), 1f)

    // 轨迹圆的半径
    private var mTrackRadius = 0f

    private var mSunPaint: Paint? = null // 绘制太阳的Paint

    private var mSunColor = Color.parseColor("#FFCD3C")// 太阳颜色 // 太阳颜色

    private var mSunRadius = 50f// 太阳半径

    private var mSunPaintStyle = Paint.Style.FILL // 太阳Paint样式,默认FILL


    private val MINIMAL_TRACK_RADIUS_PX = 300 // 半圆轨迹最小半径

    /**
     * 日出时间
     */
    private var mSunriseTime: SunTime? = null

    /**
     * 日落时间
     */
    private var mSunsetTime: SunTime? = null

    /**
     * 绘图区域
     */
    private val mBoardRectF = RectF()

    //太阳高度和整体view高度除
    private var mViewAndSun = 2


    constructor(context: Context) : super(context) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context, attrs, defStyleAttr
    ) {
        val a: TypedArray =
            context.obtainStyledAttributes(attrs, R.styleable.SunriseSunsetView, defStyleAttr, 0)
        mTrackColor = a.getColor(R.styleable.SunriseSunsetView_ssv_track_color, Color.WHITE)
        mTrackWidth = a.getDimensionPixelSize(R.styleable.SunriseSunsetView_ssv_track_width, 4)
        mSunColor = a.getColor(R.styleable.SunriseSunsetView_ssv_sun_color, Color.YELLOW)
        mSunRadius =
            a.getDimensionPixelSize(R.styleable.SunriseSunsetView_ssv_sun_radius, 20).toFloat()
        a.recycle()
        init()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val paddingRight = this.paddingRight
        val paddingLeft = this.paddingLeft
        val paddingTop = this.paddingTop
        val paddingBottom = this.paddingBottom
        val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)
        var widthSpecSize = MeasureSpec.getSize(widthMeasureSpec)
        if (widthSpecMode == MeasureSpec.AT_MOST) {
            widthSpecSize =
                paddingLeft + paddingRight + MINIMAL_TRACK_RADIUS_PX * 2 + mSunRadius.toInt() * 2
        }
        mTrackRadius = 1.0f * (widthSpecSize - paddingLeft - paddingRight - 2 * mSunRadius) / 2
        val expectedHeight =
            (mTrackRadius / mViewAndSun + mSunRadius + paddingBottom + paddingTop).toInt()
        mBoardRectF[paddingLeft + mSunRadius, paddingTop + mSunRadius, widthSpecSize - paddingRight - mSunRadius] =
            (expectedHeight - paddingBottom).toFloat()
        setMeasuredDimension(widthSpecSize, expectedHeight)
    }

    private fun init() {
        // 初始化半圆轨迹的画笔
        mTrackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            style = Paint.Style.STROKE
            color = mTrackColor
            strokeWidth = mTrackWidth.toFloat()
            pathEffect = mTrackPathEffect
        }
        prepareTrackPaint()
        // 初始化太阳的Paint
        mSunPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            color = mSunColor
            strokeWidth = DEFAULT_SUN_STROKE_WIDTH_PX
            style = mSunPaintStyle
        }
        prepareSunPaint()
    }

    // 半圆轨迹的画笔
    private fun prepareTrackPaint() {
        mTrackPaint!!.color = mTrackColor
        mTrackPaint!!.strokeWidth = mTrackWidth.toFloat()
        mTrackPaint!!.pathEffect = null;//半圆虚线
    }


    // 太阳的画笔
    private fun prepareSunPaint() {
        mSunPaint!!.color = mSunColor
        mSunPaint!!.strokeWidth = DEFAULT_SUN_STROKE_WIDTH_PX
        mSunPaint!!.style = mSunPaintStyle
    }


    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawSunTrack(canvas)
        drawSun(canvas)
        drawSubLine(canvas)
    }

    // 绘制太阳轨道(半圆)
    private fun drawSunTrack(canvas: Canvas) {
        prepareTrackPaint()
        canvas.save()
        val rectF = RectF(
            mBoardRectF.left,
            mBoardRectF.top,
            mBoardRectF.right,
            mBoardRectF.bottom + mBoardRectF.height()
        )
        val startAngle = 180f
        val sweepAngle = 180f
        val ratioAngle = sweepAngle * mRatio // 根据比率计算角度
        // 绘制左侧部分
        val leftColor = Color.parseColor("#FFCD3C") // 橙色
        val leftPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            style = Paint.Style.STROKE
            color = leftColor
            strokeWidth = mTrackWidth.toFloat()
        }
        canvas.drawArc(rectF, startAngle, ratioAngle, false, leftPaint)
        // 绘制右侧部分
        val rightColor = Color.parseColor("#E8E8E8")// 灰色
        val rightPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            style = Paint.Style.STROKE
            color = rightColor
            strokeWidth = mTrackWidth.toFloat()
        }
        canvas.drawArc(rectF, startAngle + ratioAngle, sweepAngle - ratioAngle, false, rightPaint)
        canvas.restore()
    }

    private var mSunCenterX = 0f// 太阳中心点X坐标
    private var mSunCenterY = 0f // 太阳中心点Y坐标

    // 绘制太阳
    private fun drawSun(canvas: Canvas) {
        prepareSunPaint()
        canvas.save()
        val curPointX =
            mBoardRectF.left + mTrackRadius - mTrackRadius * cos(Math.PI * mRatio).toFloat()
        val curPointY =
            mBoardRectF.bottom - mTrackRadius / mViewAndSun * sin(Math.PI * mRatio).toFloat()
        canvas.drawCircle(curPointX, curPointY, mSunRadius, mSunPaint!!)
        canvas.restore()
        mSunCenterX = curPointX
        mSunCenterY = curPointY
    }

    // 绘制太阳周围线条
    private fun drawSubLine(canvas: Canvas) {
        mSunPaint?.style = mSunPaintStyle
        mSunPaint?.color = mSunColor
        mSunPaint?.strokeWidth = 8f // 设置线条宽度
        mSunPaint?.strokeCap = Paint.Cap.ROUND // 设置线条端点为圆滑
        val angleDelta = 360f / 8
        val distanceToSun = mSunRadius + 8f//太阳周围线条到太阳的距离
        val lineLength = 8f//太阳周围线条的长度
        for (i in 0 until 8) {
            val angle = 180f + angleDelta * i
            val radian = Math.toRadians(angle.toDouble())
            val startX = mSunCenterX + distanceToSun * cos(radian).toFloat()
            val startY = mSunCenterY + distanceToSun * sin(radian).toFloat()
            val endX = startX + lineLength * cos(radian).toFloat()
            val endY = startY + lineLength * sin(radian).toFloat()
            canvas.drawLine(startX, startY, endX, endY, mSunPaint!!)
        }
    }

    fun startAnimate(sunriseTime: SunTime, sunsetTime: SunTime) {
        // 检查日出和日落时间是否为空
        if (sunriseTime == null || sunsetTime == null) {
            return
        }
        // 计算日出和日落的分钟数
        val sunriseMinutes = sunriseTime.transformToMinutes()
        val sunsetMinutes = sunsetTime.transformToMinutes()
        // 获取当前时间的分钟数
        val calendar = Calendar.getInstance(Locale.getDefault())
        val currentTime =
            calendar[Calendar.HOUR_OF_DAY] * SunTime.MINUTES_PER_HOUR + calendar[Calendar.MINUTE]
        // 计算比率并确保在 [0, 1] 范围内
        var ratio = 1.0f * (currentTime - sunriseMinutes) / (sunsetMinutes - sunriseMinutes)
        ratio = ratio.coerceIn(0f, 1.0f)
        // 设置 mRatio 的值并刷新视图
        mRatio = ratio
        invalidate()
    }
}
class SunTime(val hour: Int, val minute: Int) {
    companion object {
        const val MINUTES_PER_HOUR = 60
    }

    constructor(hourStr: String, minuteStr: String) : this(
        hourStr.toIntOrNull() ?: 0,
        minuteStr.toIntOrNull() ?: 0
    )

    fun transformToMinutes(): Int {
        return hour * MINUTES_PER_HOUR + minute
    }

    override fun toString(): String {
        val formattedHour = hour.toString().padStart(2, '0')
        val formattedMinute = minute.toString().padStart(2, '0')
        return "$formattedHour:$formattedMinute"
    }
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple_200">#FFBB86FC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_700">#FF018786</color>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
    <color name="red">#F44336</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
    <color name="grey">#616161</color>
    <color name="pink">#F50057</color>
    <color name="purple">#D500F9</color>
    <color name="indigo">#3D5AFE</color>
    <color name="teal">#00BFA5</color>
    <color name="green">#00E676</color>
    <color name="amber">#FFC400</color>
    <color name="brown">#3E2723</color>
    <color name="lightGreen">#809CCC65</color>
    <color name="Orange_sun">#FFCD3C</color>


    <declare-styleable name="progressView">
        <attr name="outerColor" format="color" />
        <attr name="innerColor" format="color" />
        <attr name="borderWidth" format="dimension" />
        <attr name="stepTextSize" format="dimension" />
        <attr name="stepTextColor" format="color" />
    </declare-styleable>
</resources>
<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.MyVIewLine" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SunAnimationView">
        <attr name="sun_circle_color" format="color"></attr>
        <attr name="sun_font_color" format="color"></attr>
        <attr name="sun_font_size" format="dimension"></attr>
        <attr name="sun_circle_radius" format="integer"></attr>
    </declare-styleable>

    <declare-styleable name="SunriseSunsetView">
        <!-- 轨道颜色 -->
        <attr name="ssv_track_color" format="color|reference" />
        <!-- 轨道宽度 -->
        <attr name="ssv_track_width" format="dimension" />
        <!--太阳半径 -->
        <attr name="ssv_sun_radius" format="dimension" />
        <!-- 太阳的颜色 -->
        <attr name="ssv_sun_color" format="color|reference" />
    </declare-styleable>
</resources>
    <com.example.myviewline.view.SunriseSunsetView
        android:id="@+id/sun"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:paddingTop="10dp"
        app:ssv_sun_color="@color/Orange_sun"
        app:ssv_sun_radius="10dp"
        app:ssv_track_width="3dp" />
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        initSun()

    }

    private fun initSun() {
        val (sunriseHour, sunriseMinute) = "08:19".split(":")//日出时间
        val (sunsetHour, sunsetMinute) = "19:55".split(":")//日落时间
        binding.sun.startAnimate(
            SunTime(sunriseHour, sunriseMinute), SunTime(sunsetHour, sunsetMinute)
        )
    }
}

效果图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值