1. 配置文件添加色值, colors.xml
<color name="colorYellow">#FFFF00</color>
2. 自定义View, MyView.kt
class MyView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr), LifecycleObserver {
private val mTAG = "MyTag"
private var sineWaveSamplesPath = Path()
private var rotatingJob: Job? = null
private var mAngle = 10f
private var mRadius = 0f
private var mWidth = 0f
private var mHeight = 0f
private val filledCirclePaint = Paint().apply {
style = Paint.Style.FILL
color = ContextCompat.getColor(context, R.color.white)
}
private val solidLinePaint = Paint().apply {
style = Paint.Style.STROKE //表示线条,没有填充
strokeWidth = 5f
color = ContextCompat.getColor(context, R.color.white)
}
private val vectorLinePaint = Paint().apply {
style = Paint.Style.STROKE //表示线条,没有填充
strokeWidth = 5f
color = ContextCompat.getColor(context, R.color.teal_200)
}
private val textPaint = Paint().apply {
textSize = 35f
typeface = Typeface.DEFAULT_BOLD
color = ContextCompat.getColor(context, R.color.white)
}
//虚线画笔
private val dashedLinePaint = Paint().apply {
style = Paint.Style.STROKE
pathEffect = DashPathEffect(floatArrayOf(10f, 10f), 0f)
color = ContextCompat.getColor(context, R.color.colorYellow)
strokeWidth = 5f
}
//在 xml 文件中调用此View 加载完毕之后调用此方法
//从代码里创建 不会调用此方法
override fun onFinishInflate() {
super.onFinishInflate()
//Log.i(mTAG,"onFinishInflate:")
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
//Log.i(mTAG,"onAttachedToWindow:")
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//Log.i(mTAG,"onMeasure:")
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
// Log.i(mTAG,"onSizeChanged:")
mWidth = w.toFloat()
mHeight = h.toFloat()
mRadius = if (w < h * 0.5) w * 0.5f else h / 4.toFloat()
mRadius -= 20f
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
//Log.i(mTAG,"onLayout:")
}
//数学的子数函数 / 欧拉公式
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// Log.i(mTAG,"onDraw:")
canvas?.apply {
drawAxises(this)
drawLabel(this)
drawDashedCircle(this)
drawVector(this)
drawProjections(this)
drawSineWave(this)
}
}
//绘制线条
private fun drawAxises(canvas: Canvas) {
canvas.withTranslation(mWidth * 0.5f, mHeight * 0.5f) {
drawLine(-mWidth * 0.5f, 0f, mWidth * 0.5f, 0f, solidLinePaint)
drawLine(0f, -mHeight * 0.5f, 0f, mHeight * 0.5f, solidLinePaint)
}
canvas.withTranslation(mWidth * 0.5f, mHeight / 4 * 3) {
drawLine(-mWidth * 0.5f, 0f, mWidth * 0.5f, 0f, solidLinePaint)
}
}
//绘制文字
private fun drawLabel(canvas: Canvas) {
canvas.apply {
//drawRect(100f, 100f, 460f, 250f, solidLinePaint)
drawText("指定函数与旋转矢量", mWidth - 330, mHeight - 35, textPaint)
}
}
//绘制虚线圆
private fun drawDashedCircle(canvas: Canvas) {
canvas.withTranslation(mWidth * 0.5f, mHeight / 4 * 3) {
drawCircle(0f, 0f, mRadius, dashedLinePaint)
}
}
//绘制旋转角度
private fun drawVector(canvas: Canvas) {
canvas.withTranslation(mWidth * 0.5f, mHeight / 4 * 3) {
withRotation(-mAngle) {
drawLine(0f, 0f, mRadius, 0f, vectorLinePaint)
}
}
}
//线条投影
private fun drawProjections(canvas: Canvas) {
canvas.withTranslation(mWidth * 0.5f, mHeight * 0.5f) {
drawCircle(mRadius * cos(mAngle.toRadians()), 0f, 16f, filledCirclePaint)
}
canvas.withTranslation(mWidth * 0.5f, mHeight / 4 * 3) {
drawCircle(mRadius * cos(mAngle.toRadians()), 0f, 16f, filledCirclePaint)
}
canvas.withTranslation(mWidth * 0.5f, mHeight / 4 * 3) {
//移动一个长度sin值和cos值
val x = mRadius * cos(mAngle.toRadians())
val y = mRadius * sin(mAngle.toRadians())
withTranslation(x, -y) {
//实线
drawLine(0f, 0f, 0f, y, solidLinePaint)
//虚线
drawLine(0f, 0f, 0f, -mHeight / 4 + y, dashedLinePaint)
}
}
}
//绘制曲线
private fun drawSineWave(canvas: Canvas) {
canvas.withTranslation(mWidth * 0.5f, mHeight * 0.5f) {
val samplesCount = 60
val dy = mHeight * 0.5 / samplesCount
sineWaveSamplesPath.reset()
sineWaveSamplesPath.moveTo(mRadius * cos(mAngle.toRadians()), 0f)
repeat(samplesCount) {
val x = mRadius * cos(it * -0.15 + mAngle.toRadians())
val y = -dy * it
sineWaveSamplesPath.quadTo(x.toFloat(), y.toFloat(), x.toFloat(), y.toFloat())
}
//绘制路径
drawPath(sineWaveSamplesPath, vectorLinePaint)
drawTextOnPath("Hello world", sineWaveSamplesPath, 1000f, -20f, textPaint)
}
}
//开始旋转角度
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun startRotating() {
rotatingJob = CoroutineScope(Dispatchers.Main).launch {
while (true) {
delay(80)
mAngle += 5f
invalidate()
}
}
}
//暂停旋转
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun pauseRotating() {
rotatingJob?.cancel()
}
//退出View
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
}
//角度转弧度
private fun Float.toRadians() = this / 180 * PI.toFloat()
}
3. 布局文件引用自定义View, activity_main.xml
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.customdraw.MyView
android:id="@+id/myView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
4. 调用布局, MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val myView: MyView = findViewById(R.id.myView)
lifecycle.addObserver(myView)
}
}
5. 效果图