安卓实现显示太阳位置动画(仿华为天气预报)

实现效果

实现方案

自定义视图

主要代码

  1. SunriseSunset.kt
class SunriseSunset constructor(
    private val mContext: Context,
    attrs: AttributeSet
) : View(mContext, attrs) {
    private val DEFAULT_SIZE = 300f
    private val mDefaultSize: Int
    private val sweepAngle = 140
    private val starAngle = 200 - (sweepAngle - 140) / 2
    private val halfRemainAngle = (180 - sweepAngle) / 2 / 180 * PI
    private var mWidth: Int = 0
    private var mHeight: Int = 0
    private lateinit var mCenterPoint: Point

    private lateinit var mArcPaint: Paint
    private lateinit var mRectF: RectF
    private var mRadius: Float = 0.toFloat()
    private var lineYLocate: Float = 0.toFloat()
    private lateinit var mAnimator: ObjectAnimator
    private lateinit var mSunPaint: Paint
    private lateinit var mTimePaint: Paint

    private var lineColor: Int = 0
    private var sunColor: Int = 0
    private var timeTextColor: Int = 0
    private var timeTextSize: Float = 0.toFloat()
    private var mSunriseTime = "6:00"
    private var mSunsetTime = "19:00"

    private lateinit var mSunriseBitmap: Bitmap
    private lateinit var mSunsetBitmap: Bitmap
    private var percent = 0f
    private var mTotalTime: Int = 0
    private var isSetTime = false
    private var mNowTime: Int = 0

    init {
        mDefaultSize = dipToPx(mContext, DEFAULT_SIZE)//默认300
        init(attrs)
    }

    private fun init(attrs: AttributeSet?) {
        if (attrs != null) {
            val typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.SunriseSunset)
            lineColor = typedArray.getColor(R.styleable.SunriseSunset_lineColor, Color.WHITE)
            sunColor = typedArray.getColor(R.styleable.SunriseSunset_sunColor, Color.YELLOW)
            timeTextColor = typedArray.getColor(R.styleable.SunriseSunset_timeTextColor, Color.GRAY)
            timeTextSize = typedArray.getDimension(R.styleable.SunriseSunset_timeTextSize, 40f)
            typedArray.recycle()
        }
        mCenterPoint = Point()
        mArcPaint = Paint()
        mArcPaint.isAntiAlias = true
        mArcPaint.color = lineColor
        mArcPaint.style = Paint.Style.STROKE
        mArcPaint.strokeWidth = 3f
        mArcPaint.pathEffect = DashPathEffect(floatArrayOf(20f, 15f), 20f)
        mArcPaint.strokeCap = Paint.Cap.ROUND
        mRectF = RectF()


        mSunPaint = Paint()
        mSunPaint.isAntiAlias = true
        mSunPaint.color = sunColor
        mSunPaint.style = Paint.Style.STROKE
        mSunPaint.strokeWidth = 3f
        mSunPaint.pathEffect = DashPathEffect(floatArrayOf(20f, 15f), 20f)
        mSunriseBitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_sunrise)
        mSunsetBitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_sunset)
        mSunPaint.colorFilter =
            PorterDuffColorFilter(sunColor, PorterDuff.Mode.SRC_ATOP)//设置后bitmap才会填充颜色

        mTimePaint = Paint()
        mTimePaint.color = timeTextColor
        mTimePaint.textSize = timeTextSize
        mTimePaint.textAlign = Paint.Align.CENTER
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        setMeasuredDimension(
            internalMeasure(widthMeasureSpec, mDefaultSize),
            internalMeasure(heightMeasureSpec, (mDefaultSize * 0.55).toInt())
        )
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mWidth = w - paddingLeft - paddingRight
        mHeight = h - paddingBottom - paddingTop
        mRadius = ((if (mWidth / 2 < mHeight) mWidth / 2 else mHeight) - dipToPx(
            mContext,
            15f
        )).toFloat()//留出一定空间
        mCenterPoint.x = mWidth / 2
        mCenterPoint.y = mHeight
        mRectF.left = mCenterPoint.x - mRadius
        mRectF.top = mCenterPoint.y - mRadius
        mRectF.right = mCenterPoint.x + mRadius
        mRectF.bottom = mCenterPoint.y + mRadius
        lineYLocate =
            (mCenterPoint.y - mRadius * sin(PI / 180 * (180 - sweepAngle) / 2)).toFloat()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        canvas.save()
        drawArc(canvas)
        drawText(canvas)
        canvas.restore()
    }

    private fun drawText(canvas: Canvas) {
        canvas.drawText(
            mSunriseTime,
            mCenterPoint.x - mRadius + timeTextSize,
            lineYLocate + timeTextSize + 15f,
            mTimePaint
        )//y轴可能需要微调一下
        canvas.drawText(
            mSunsetTime,
            mCenterPoint.x + mRadius - timeTextSize,
            lineYLocate + timeTextSize + 15f,
            mTimePaint
        )
        mTimePaint.textSize = 40f
        canvas.drawText(
            "日出日落",
            mCenterPoint.x.toFloat(),
            lineYLocate - timeTextSize,
            mTimePaint
        )
    }

    private fun drawArc(canvas: Canvas) {
        val nowAngle: Float
        if (percent == 0f) {
            nowAngle = 0f
            canvas.drawBitmap(
                mSunsetBitmap,
                (mCenterPoint.x - mRadius * kotlin.math.cos(halfRemainAngle)).toFloat(),
                lineYLocate - mSunsetBitmap.height,
                mSunPaint
            )//太阳
        } else if (percent == 1f) {
            nowAngle = sweepAngle.toFloat()
            canvas.drawBitmap(
                mSunsetBitmap,
                (mCenterPoint.x + mRadius * kotlin.math.cos(halfRemainAngle)).toFloat() - mSunsetBitmap.width,
                lineYLocate - mSunsetBitmap.height,
                mSunPaint
            )
        } else {
            nowAngle = sweepAngle * percent

            canvas.drawBitmap(
                mSunriseBitmap,
                (mCenterPoint.x - mRadius * kotlin.math.cos((nowAngle + 18) * (PI / 180) + halfRemainAngle)).toFloat() - mSunriseBitmap.width / 2,
                (mCenterPoint.y - mRadius * kotlin.math.sin((nowAngle + 18) * (PI / 180) + halfRemainAngle)).toFloat() - mSunriseBitmap.height / 2,
                mSunPaint
            )//18这个数字是我测试出来计算过程中损失的各种度数
        }
        canvas.drawArc(
            mRectF,
            starAngle + nowAngle,
            sweepAngle - nowAngle,
            false,
            mArcPaint
        )//弧
        canvas.drawLine(0f, lineYLocate, mWidth.toFloat(), lineYLocate, mArcPaint)//线
        canvas.drawArc(
            mRectF,
            starAngle.toFloat(),
            nowAngle - 3.5f,
            false,
            mSunPaint
        )//留出3度让小太阳背景空白
    }

    fun startAnimation() {

        if (isSetTime) {
            val nowPercent = mNowTime.toFloat() / mTotalTime.toFloat()//这个float一定要转啊,不然会一致都是0
            if (nowPercent < 0.02) {//如果太阳只升起来一点就不画动画了,没出也不画
                setPercent(0f)
            } else if (nowPercent > 0.98) {
                setPercent(1f)
            } else {
                mAnimator = ObjectAnimator.ofFloat(this, "percent", 0f, nowPercent)
                mAnimator.duration = (2000 + 3000 * nowPercent).toLong()
                mAnimator.interpolator = LinearInterpolator()
                mAnimator.start()

            }
        }
    }

    fun stopAnimator() {
        clearAnimation()
    }

    fun setTime(mSunriseTime: String, mSunsetTime: String, nowTime: String) {
        this.mSunriseTime = mSunriseTime
        this.mSunsetTime = mSunsetTime
        mTotalTime = transToMinuteTime(mSunsetTime) - transToMinuteTime(mSunriseTime)
        this.mNowTime = transToMinuteTime(nowTime) - transToMinuteTime(mSunriseTime)
        isSetTime = true
    }

    fun setPercent(percent: Float) {
        this.percent = percent
        invalidate()
    }

    private fun transToMinuteTime(time: String): Int {//"00:00"
        val s = time.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        return Integer.parseInt(s[0]) * 60 + Integer.parseInt(s[1])
    }

    private fun dipToPx(context: Context, dip: Float): Int {
        val density = context.resources.displayMetrics.density
        return (dip * density + 0.5f * if (dip >= 0) 1 else -1).toInt()
    }

    private fun internalMeasure(measureSpec: Int, defaultSize: Int): Int {
        var result = defaultSize
        val specMode = MeasureSpec.getMode(measureSpec)
        val specSize = MeasureSpec.getSize(measureSpec)
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize
        } else if (specMode == MeasureSpec.AT_MOST) {
            result = result.coerceAtMost(specSize)
        }
        return result
    }

}
2. 调用样例

<com.cxyzy.library.SunriseSunset
android:id="@+id/sunriseSunset"
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_margin=“10dp”
android:layout_marginTop=“20dp”
app:lineColor="#ffffff"
app:sunColor="#ff9800"
app:timeTextColor="#ffffff"
app:timeTextSize=“11sp” />

var sunriseTime = “08:00”
var sunsetTime = “19:00”
val dateFormat = SimpleDateFormat(“HH:mm”, Locale.CHINA)
val nowDate = Date(System.currentTimeMillis())
val nowTime = dateFormat.format(nowDate)
sunriseSunset.setTime(sunriseTime, sunsetTime, nowTime)
sunriseSunset.startAnimation()

# lib库集成方法
1. 在工程根目录下build.gradle文件中增加jitpack仓库地址:

allprojects {
repositories {

maven { url ‘https://jitpack.io’ }
}
}

2. 在app工程下build.gradle文件中增加依赖:

dependencies {
implementation ‘com.github.cxyzy1:sunriseSunset:1.0.0’
}

# 源代码
https://github.com/cxyzy1/sunriseSunset
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值