Android 使用Kotlin来实现任务完成提醒效果

Posted Adan0520

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 使用Kotlin来实现任务完成提醒效果相关的知识,希望对你有一定的参考价值。

这篇文章比较简单,主要是记录一下任务完成提醒效果。
按照惯例,先来看看效果图

而、下面就是我们的代码实现
1、布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/cl_full_complete_task"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#CC000000"
        android:visibility="visible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/iv_bg_full_complete_task"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:gravity="center"
            android:src="@drawable/bg_full_complete_task"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.34" />

        <ImageView
            android:id="@+id/iv_badge_full_complete_task"
            android:layout_width="150dp"
            android:layout_height="116dp"
            android:gravity="center"
            android:src="@drawable/icon_full_complete_task_badge"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.34" />

        <TextView
            android:id="@+id/tv_full_complete_task_tip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp"
            android:text="恭喜你"
            android:textColor="#FFC53D"
            android:textSize="24dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/iv_bg_full_complete_task" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:gravity="center_horizontal"
            android:orientation="horizontal"
            android:text="又完成了一项任务"
            android:textColor="#E7EaEF"
            android:textSize="14dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_full_complete_task_tip" />

        <TextView
            android:id="@+id/tv_full_complete_task_iknow"
            android:layout_width="168dp"
            android:layout_height="44dp"
            android:background="#ff2244"
            android:gravity="center"
            android:text="我知道了"
            android:textColor="#E7EaEF"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.94" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <co.per.celebratedome.TaskFullCompleteView
        android:id="@+id/anim_full_complete_task"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="44dp"
        android:foreground="?selectableItemBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

2、MainActivity:

package co.per.celebratedome

import android.os.Bundle
import android.view.View
import android.view.animation.TranslateAnimation
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout

open class MainActivity : AppCompatActivity() {
    private lateinit var animFullCompleteTask: TaskFullCompleteView
    private lateinit var tvFullCompleteTaskIknow: TextView
    private lateinit var clFullCompleteTask: ConstraintLayout

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        animFullCompleteTask = findViewById(R.id.anim_full_complete_task)
        tvFullCompleteTaskIknow = findViewById(R.id.tv_full_complete_task_iknow)
        clFullCompleteTask = findViewById(R.id.cl_full_complete_task)
        tvFullCompleteTaskIknow.setOnClickListener {
            animFullCompleteTask.stopAnim()
        }
        show()
    }

    private fun show() {
        val ctrlAnimation = TranslateAnimation(
            TranslateAnimation.RELATIVE_TO_SELF, 0f, TranslateAnimation.RELATIVE_TO_SELF, 0f,
            TranslateAnimation.RELATIVE_TO_SELF, 1f, TranslateAnimation.RELATIVE_TO_SELF, 0f
        )
        ctrlAnimation.duration = 1000 //设置动画的过渡时间
        clFullCompleteTask.startAnimation(ctrlAnimation)
        animFullCompleteTask.startAnim()
    }
}

3、自定义View:TaskFullCompleteView

package co.per.celebratedome

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.TypeEvaluator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.os.Handler
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.View
import android.view.WindowManager
import android.view.animation.*
import android.view.animation.Interpolator
import android.widget.FrameLayout
import androidx.annotation.AttrRes
import java.util.*

/**
 * 任务完成提醒效果View
 * Created by juan on 2021/07/23
 */
class TaskFullCompleteView(
    private var mContext: Context,
    attrs: AttributeSet?,
    @AttrRes defStyleAttr: Int
) :
    FrameLayout(mContext, attrs, defStyleAttr) {
    private var mMaxWidth = 0
    private var mMaxHeight = 0
    private var rotateIndex = 0
    private val mLinearInterpolator: Interpolator = LinearInterpolator() //线性
    private val mAccelerateInterpolator: Interpolator = AccelerateInterpolator() //加速
    private val mDecelerateInterpolator: Interpolator = DecelerateInterpolator() //减速
    private val mAccelerateDecelerateInterpolator: Interpolator =
        AccelerateDecelerateInterpolator() //先加速后减速
    private var mInterpolatorArray: Array<Interpolator>? = null
    private var mHandler: Handler? = Handler()

    constructor(context: Context) : this(context, null) {
        mContext = context
    }

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) {
        mContext = context
    }

    /**
     * 初始化测量
     */
    private fun initMeasure() {
        val windowManager = mContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        val displayMetrics = DisplayMetrics()
        windowManager.defaultDisplay.getMetrics(displayMetrics)
        mMaxWidth = displayMetrics.widthPixels
        mMaxHeight = displayMetrics.heightPixels
    }

    /**
     * 初始化
     */
    private fun initView() {
        mInterpolatorArray = arrayOf(
            mLinearInterpolator,
            mAccelerateInterpolator,
            mDecelerateInterpolator,
            mAccelerateDecelerateInterpolator
        )
    }

    /**
     * 设置为和屏幕一样大
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(mMaxWidth, mMaxHeight)
    }

    /**
     * 开始动画
     */
    fun startAnim() {
        mHandler!!.post(starStartRunnable)
    }

    /**
     * 结束动画
     */
    fun stopAnim() {
        if (mHandler != null) {
            mHandler!!.removeCallbacksAndMessages(null)
            mHandler = null
        }
    }

    /**
     * 开始动画任务
     */
    private val starStartRunnable: Runnable = object : Runnable {
        override fun run() {
            val anomalyView = AnomalyView(mContext)
            addView(anomalyView)
            bezierAnim(anomalyView)
            mHandler!!.postDelayed(this, 200)
        }
    }

    /**
     * 动画实现
     */
    private fun bezierAnim(view: View) {
        rotateIndex++
        if (rotateIndex % 5 == 0) {
            val animationSet4 = AnimationSet(true)
            //旋转动画
            val rotateAnimation = RotateAnimation(
                0f,
                Random().nextInt(360).toFloat(), Animation.RELATIVE_TO_PARENT,
                (-1 + Math.random() * 3).toFloat(), Animation.RELATIVE_TO_PARENT,
                (-1 + Math.random() * 3).toFloat()
            )
            animationSet4.addAnimation(rotateAnimation)
            //设置动画变化的持续时间
            animationSet4.duration = 4000
            view.startAnimation(animationSet4)
        }
        val startPointF = PointF(Random().nextInt(mMaxWidth).toFloat(), -50f) //起始点
        val endPointF = PointF(
            Random().nextInt(mMaxWidth).toFloat(),
            mMaxHeight.toFloat()
        ) //结束点
        val valueAnimator = ValueAnimator.ofObject(
            BezierEvaluator(
                getScreenRandomPointF(1),
                getScreenRandomPointF(2)
            ), startPointF, endPointF
        )
        valueAnimator.addUpdateListener { animation ->
            val pointF = animation.animatedValue as PointF
            view.x = pointF.x
            view.y = pointF.y
        }
        valueAnimator.interpolator = mInterpolatorArray!![Random().nextInt(4)]
        valueAnimator.setTarget(view)
        valueAnimator.duration = 4000
        valueAnimator.addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                super.onAnimationEnd(animation)
                removeView(view)
            }
        })
        valueAnimator.start()
    }

    /**
     * 获取屏幕上的随机两个控制点
     *
     * @param number 第几个控制点
     * @return
     */
    private fun getScreenRandomPointF(number: Int): PointF {
        val pointF = PointF()
        pointF.x = (Random().nextInt(mMaxWidth * 2) - mMaxWidth / 2).toFloat() //尽量向左右两边扩散一点
        pointF.y = Random().nextInt(mMaxHeight / 2 * number).toFloat()
        return pointF
    }

    /**
     * 贝塞尔路径实现
     * 三阶公式 B(T) = p0((1-t)(1-t)(1-t))+3P1t((1-t)(1-t))+3p2(t*t)(1-t)+p3(t*t*t)
     * 二阶公式 B(T) = (1-t)*(1-t)*P0+2t(1-t)P1+t*t*P2
     * 一阶公式 B(T) = P0+(P1-P0)*t=(1-t)P0+t*P1
     */
    private inner class BezierEvaluator(
        private val mControlPointFOne: PointF,
        private val mControlPointFTwo: PointF
    ) :
        TypeEvaluator<PointF> {
        override fun evaluate(fraction: Float, startValue: PointF, endValue: PointF): PointF {
            val resultPointF = PointF() //结果值
            val v = (1 - fraction) * (1 - fraction) * (1 - fraction)
            resultPointF.x =
                startValue //开始控制点
                    .x * v + 3 * mControlPointFOne.x * fraction * ((1 - fraction) * (1 - fraction)) + 3 * mControlPointFTwo.x * (fraction * fraction) * (1 - fraction) + endValue //结束控制点
                    .x * (fraction * fraction * fraction)
            resultPointF.y =
                startValue //开始控制点
                    .y * v + 3 * mControlPointFOne.y * fraction * ((1 - fraction) * (1 - fraction)) + 3 * mControlPointFTwo.y * (fraction * fraction) * (1 - fraction) + endValue //结束控制点
                    .y * (fraction * fraction * fraction)
            return resultPointF
        }
    }

    /**
     * 不规则View
     */
    private inner class AnomalyView @JvmOverloads constructor(
        context: Context?,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) :
        View(context, attrs, defStyleAttr) {
        private val mMaxWidth = 25 //最大宽度
        private val mMaxHeight = 50 //最大高度
        private val mPadding = 10 //内边距
        private var mPaint: Paint? = null
        private var mRandomPoint: Array<PointF?>? = null //随机点  4个  顺序为  左上右下
        private var mLumpColorArray: Array<String>? = null
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
            setMeasuredDimension(mMaxWidth, mMaxHeight)
        }

        /**
         * 初始化
         */
        private fun init() {
            mLumpColorArray = arrayOf("#3799FF", "#F52245", "#F6B56A", "#35B951") //蓝、红、黄、绿
            mRandomPoint = arrayOfNulls(4)
            mRandomPoint!![0] = PointF(
                Random().nextInt(mPadding).toFloat(),
                Random().nextInt(mMaxHeight - mPadding).toFloat()
            ) //左
            mRandomPoint!![1] = PointF(
                (Random().nextInt(mMaxWidth - mPadding) + mPadding).toFloat(),
                Random().nextInt(mPadding).toFloat()
            ) //上
            mRandomPoint!![2] = PointF(
                (Random().nextInt(mPadding) + (mMaxWidth - mPadding)).toFloat(),
                (Random().nextInt(mMaxHeight - mPadding) + mPadding).toFloat()
            ) //右
            mRandomPoint!![3] = PointF(
                (Random().nextInt(mMaxWidth) - mPadding).toFloat(),
                (Random().nextInt(mPadding) + (mMaxHeight - mPadding)).toFloat()
            ) //下
            mPaint = Paint()
            mPaint!!.isDither = true
            mPaint!!.isAntiAlias = true
            mPaint!!.color = Color.parseColor(
                mLumpColorArray!!以上是关于Android 使用Kotlin来实现任务完成提醒效果的主要内容,如果未能解决你的问题,请参考以下文章

Android 使用Kotlin来实现任务完成提醒效果

Android(Kotlin) - 如何等待异步任务完成?

Kotlin + Flow 实现的 Android 应用初始化任务启动库

Android 在fragment中实现返回键单击提醒 双击退出

Kotlin实现定时任务(AlarmManager + BroadcastReceiver)

Kotlin实现定时任务(AlarmManager + BroadcastReceiver)