feat: 优化代码

pull/106/head
kunfei 5 years ago
parent 205504d223
commit ad30cb55e3
  1. 338
      app/src/main/java/io/legado/app/ui/widget/checkbox/SmoothCheckBox.kt

@ -0,0 +1,338 @@
package io.legado.app.ui.widget.checkbox
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
import android.widget.Checkable
import io.legado.app.R
import io.legado.app.lib.theme.ThemeStore
import io.legado.app.utils.dp
import io.legado.app.utils.getCompatColor
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.sqrt
class SmoothCheckBox @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs), Checkable {
private var mPaint: Paint
private var mTickPaint: Paint
private var mFloorPaint: Paint
private var mTickPoints: Array<Point>
private var mCenterPoint: Point
private var mTickPath: Path
private var mLeftLineDistance = 0f
private var mRightLineDistance = 0f
private var mDrewDistance = 0f
private var mScaleVal = 1.0f
private var mFloorScale = 1.0f
private var mWidth = 0
private var mAnimDuration = 0
private var mStrokeWidth = 0
private var mCheckedColor = 0
private var mUnCheckedColor = 0
private var mFloorColor = 0
private var mFloorUnCheckedColor = 0
private var mChecked = false
private var mTickDrawing = false
private var mListener: OnCheckedChangeListener? = null
init {
val ta = context.obtainStyledAttributes(attrs, R.styleable.SmoothCheckBox)
var tickColor = ThemeStore.accentColor(context)
mCheckedColor = context.getCompatColor(R.color.background_card)
mUnCheckedColor = context.getCompatColor(R.color.background_menu)
mFloorColor = context.getCompatColor(R.color.transparent30)
tickColor = ta.getColor(R.styleable.SmoothCheckBox_color_tick, tickColor)
mAnimDuration = ta.getInt(R.styleable.SmoothCheckBox_duration, DEF_ANIM_DURATION)
mFloorColor = ta.getColor(R.styleable.SmoothCheckBox_color_unchecked_stroke, mFloorColor)
mCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_color_checked, mCheckedColor)
mUnCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_color_unchecked, mUnCheckedColor)
mStrokeWidth = ta.getDimensionPixelSize(R.styleable.SmoothCheckBox_stroke_width, 0)
ta.recycle()
mFloorUnCheckedColor = mFloorColor
mTickPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mTickPaint.style = Paint.Style.STROKE
mTickPaint.strokeCap = Paint.Cap.ROUND
mTickPaint.color = tickColor
mFloorPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mFloorPaint.style = Paint.Style.FILL
mFloorPaint.color = mFloorColor
mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mPaint.style = Paint.Style.FILL
mPaint.color = mCheckedColor
mTickPath = Path()
mCenterPoint = Point()
mTickPoints = arrayOf(Point(), Point(), Point())
setOnClickListener {
toggle()
mTickDrawing = false
mDrewDistance = 0f
if (isChecked) {
startCheckedAnimation()
} else {
startUnCheckedAnimation()
}
}
}
override fun isChecked(): Boolean {
return mChecked
}
override fun setChecked(checked: Boolean) {
mChecked = checked
reset()
invalidate()
if (mListener != null) {
mListener!!.onCheckedChanged(this@SmoothCheckBox, mChecked)
}
}
override fun toggle() {
this.isChecked = !isChecked
}
/**
* checked with animation
*
* @param checked checked
* @param animate change with animation
*/
fun setChecked(checked: Boolean, animate: Boolean) {
if (animate) {
mTickDrawing = false
mChecked = checked
mDrewDistance = 0f
if (checked) {
startCheckedAnimation()
} else {
startUnCheckedAnimation()
}
if (mListener != null) {
mListener!!.onCheckedChanged(this@SmoothCheckBox, mChecked)
}
} else {
this.isChecked = checked
}
}
private fun reset() {
mTickDrawing = true
mFloorScale = 1.0f
mScaleVal = if (isChecked) 0f else 1.0f
mFloorColor = if (isChecked) mCheckedColor else mFloorUnCheckedColor
mDrewDistance = if (isChecked) mLeftLineDistance + mRightLineDistance else 0f
}
private fun measureSize(measureSpec: Int): Int {
val defSize: Int = DEF_DRAW_SIZE.dp
val specSize = MeasureSpec.getSize(measureSpec)
val specMode = MeasureSpec.getMode(measureSpec)
var result = 0
when (specMode) {
MeasureSpec.UNSPECIFIED, MeasureSpec.AT_MOST -> result = min(defSize, specSize)
MeasureSpec.EXACTLY -> result = specSize
}
return result
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(measureSize(widthMeasureSpec), measureSize(heightMeasureSpec))
}
override fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
) {
mWidth = measuredWidth
mStrokeWidth = if (mStrokeWidth == 0) measuredWidth / 10 else mStrokeWidth
mStrokeWidth =
if (mStrokeWidth > measuredWidth / 5) measuredWidth / 5 else mStrokeWidth
mStrokeWidth = if (mStrokeWidth < 3) 3 else mStrokeWidth
mCenterPoint.x = mWidth / 2
mCenterPoint.y = measuredHeight / 2
mTickPoints[0].x = (measuredWidth.toFloat() / 30 * 7).roundToInt()
mTickPoints[0].y = (measuredHeight.toFloat() / 30 * 14).roundToInt()
mTickPoints[1].x = (measuredWidth.toFloat() / 30 * 13).roundToInt()
mTickPoints[1].y = (measuredHeight.toFloat() / 30 * 20).roundToInt()
mTickPoints[2].x = (measuredWidth.toFloat() / 30 * 22).roundToInt()
mTickPoints[2].y = (measuredHeight.toFloat() / 30 * 10).roundToInt()
mLeftLineDistance = sqrt(
(mTickPoints[1].x - mTickPoints[0].x.toDouble()).pow(2.0) +
(mTickPoints[1].y - mTickPoints[0].y.toDouble()).pow(2.0)
).toFloat()
mRightLineDistance = sqrt(
(mTickPoints[2].x - mTickPoints[1].x.toDouble()).pow(2.0) +
(mTickPoints[2].y - mTickPoints[1].y.toDouble()).pow(2.0)
).toFloat()
mTickPaint.strokeWidth = mStrokeWidth.toFloat()
}
override fun onDraw(canvas: Canvas) {
drawBorder(canvas)
drawCenter(canvas)
drawTick(canvas)
}
private fun drawCenter(canvas: Canvas) {
mPaint.color = mUnCheckedColor
val radius = (mCenterPoint.x - mStrokeWidth) * mScaleVal
canvas.drawCircle(mCenterPoint.x.toFloat(), mCenterPoint.y.toFloat(), radius, mPaint)
}
private fun drawBorder(canvas: Canvas) {
mFloorPaint.color = mFloorColor
val radius = mCenterPoint.x
canvas.drawCircle(
mCenterPoint.x.toFloat(),
mCenterPoint.y.toFloat(),
radius * mFloorScale,
mFloorPaint
)
}
private fun drawTick(canvas: Canvas) {
if (mTickDrawing && isChecked) {
drawTickPath(canvas)
}
}
private fun drawTickPath(canvas: Canvas) {
mTickPath.reset()
// draw left of the tick
if (mDrewDistance < mLeftLineDistance) {
val step: Float = if (mWidth / 20.0f < 3) 3f else mWidth / 20.0f
mDrewDistance += step
val stopX =
mTickPoints[0].x + (mTickPoints[1].x - mTickPoints[0].x) * mDrewDistance / mLeftLineDistance
val stopY =
mTickPoints[0].y + (mTickPoints[1].y - mTickPoints[0].y) * mDrewDistance / mLeftLineDistance
mTickPath.moveTo(mTickPoints[0].x.toFloat(), mTickPoints[0].y.toFloat())
mTickPath.lineTo(stopX, stopY)
canvas.drawPath(mTickPath, mTickPaint)
if (mDrewDistance > mLeftLineDistance) {
mDrewDistance = mLeftLineDistance
}
} else {
mTickPath.moveTo(mTickPoints[0].x.toFloat(), mTickPoints[0].y.toFloat())
mTickPath.lineTo(mTickPoints[1].x.toFloat(), mTickPoints[1].y.toFloat())
canvas.drawPath(mTickPath, mTickPaint)
// draw right of the tick
if (mDrewDistance < mLeftLineDistance + mRightLineDistance) {
val stopX =
mTickPoints[1].x + (mTickPoints[2].x - mTickPoints[1].x) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance
val stopY =
mTickPoints[1].y - (mTickPoints[1].y - mTickPoints[2].y) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance
mTickPath.reset()
mTickPath.moveTo(mTickPoints[1].x.toFloat(), mTickPoints[1].y.toFloat())
mTickPath.lineTo(stopX, stopY)
canvas.drawPath(mTickPath, mTickPaint)
val step: Float = if (mWidth / 20f < 3) 3f else mWidth / 20f
mDrewDistance += step
} else {
mTickPath.reset()
mTickPath.moveTo(mTickPoints[1].x.toFloat(), mTickPoints[1].y.toFloat())
mTickPath.lineTo(mTickPoints[2].x.toFloat(), mTickPoints[2].y.toFloat())
canvas.drawPath(mTickPath, mTickPaint)
}
}
// invalidate
if (mDrewDistance < mLeftLineDistance + mRightLineDistance) {
postDelayed({ this.postInvalidate() }, 10)
}
}
private fun startCheckedAnimation() {
val animator = ValueAnimator.ofFloat(1.0f, 0f)
animator.duration = mAnimDuration / 3 * 2.toLong()
animator.interpolator = LinearInterpolator()
animator.addUpdateListener { animation: ValueAnimator ->
mScaleVal = animation.animatedValue as Float
mFloorColor = getGradientColor(
mUnCheckedColor,
mCheckedColor,
1 - mScaleVal
)
postInvalidate()
}
animator.start()
val floorAnimator = ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f)
floorAnimator.duration = mAnimDuration.toLong()
floorAnimator.interpolator = LinearInterpolator()
floorAnimator.addUpdateListener { animation: ValueAnimator ->
mFloorScale = animation.animatedValue as Float
postInvalidate()
}
floorAnimator.start()
drawTickDelayed()
}
private fun startUnCheckedAnimation() {
val animator = ValueAnimator.ofFloat(0f, 1.0f)
animator.duration = mAnimDuration.toLong()
animator.interpolator = LinearInterpolator()
animator.addUpdateListener { animation: ValueAnimator ->
mScaleVal = animation.animatedValue as Float
mFloorColor = getGradientColor(
mCheckedColor,
mFloorUnCheckedColor,
mScaleVal
)
postInvalidate()
}
animator.start()
val floorAnimator = ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f)
floorAnimator.duration = mAnimDuration.toLong()
floorAnimator.interpolator = LinearInterpolator()
floorAnimator.addUpdateListener { animation: ValueAnimator ->
mFloorScale = animation.animatedValue as Float
postInvalidate()
}
floorAnimator.start()
}
private fun drawTickDelayed() {
postDelayed({
mTickDrawing = true
postInvalidate()
}, mAnimDuration.toLong())
}
fun setOnCheckedChangeListener(l: OnCheckedChangeListener?) {
mListener = l
}
interface OnCheckedChangeListener {
fun onCheckedChanged(checkBox: SmoothCheckBox?, isChecked: Boolean)
}
companion object {
private const val DEF_DRAW_SIZE = 25
private const val DEF_ANIM_DURATION = 300
private fun getGradientColor(startColor: Int, endColor: Int, percent: Float): Int {
val startA = Color.alpha(startColor)
val startR = Color.red(startColor)
val startG = Color.green(startColor)
val startB = Color.blue(startColor)
val endA = Color.alpha(endColor)
val endR = Color.red(endColor)
val endG = Color.green(endColor)
val endB = Color.blue(endColor)
val currentA = (startA * (1 - percent) + endA * percent).toInt()
val currentR = (startR * (1 - percent) + endR * percent).toInt()
val currentG = (startG * (1 - percent) + endG * percent).toInt()
val currentB = (startB * (1 - percent) + endB * percent).toInt()
return Color.argb(currentA, currentR, currentG, currentB)
}
}
}
Loading…
Cancel
Save