= arrayListOf()
+ for (i in 0..3)
+ list.add(getVoteOptionData(index, i))
+ return list
+
+}
+
+private fun getVoteOptionData(index: Int, i: Int): VoteOption {
+
+ val voteContent: String = when (i) {
+ 0 -> if (index == 0) "当然可以" else "蔡徐坤"
+ 1 -> if (index == 0) "估计不能" else "肖战"
+ 2 -> if (index == 0) "拭目以待" else "李现"
+ 3 -> if (index == 0) "保持中立" else "邓伦"
+ else -> ""
+ }
+ return VoteOption(i, voteContent, i, Random.nextInt(6666,8888), false)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/ArcView.kt b/app/src/main/java/com/allen/androidcustomview/widget/ArcView.kt
new file mode 100644
index 0000000..ef06f5f
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/ArcView.kt
@@ -0,0 +1,87 @@
+package com.allen.androidcustomview.widget
+
+import android.content.Context
+import android.graphics.*
+import android.util.AttributeSet
+import android.view.View
+import com.allen.androidcustomview.R
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019年07月07日
+ * desc :
+ *
+ */
+class ArcView : View {
+ private var mWidth = 0
+ private var mHeight = 0
+ /**
+ * 弧形高度
+ */
+ private var mArcHeight = 0
+ /**
+ * 背景颜色
+ */
+ private var mBgColor = Color.WHITE
+ private var mPaint = Paint()
+ private var mContext: Context? = null
+
+ private var mPath = Path()
+
+ constructor(context: Context) : this(context, null)
+ constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ initView(context, attrs, defStyleAttr)
+ }
+
+ private fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ArcView)
+ mArcHeight = typedArray.getDimensionPixelSize(R.styleable.ArcView_arcHeight, 0)
+ mBgColor = typedArray.getColor(R.styleable.ArcView_bgColor, Color.WHITE)
+ typedArray.recycle()
+
+ mContext = context
+ mPaint.style = Paint.Style.FILL_AND_STROKE
+ mPaint.color = mBgColor
+ mPaint.isAntiAlias = true
+ mPaint.strokeWidth = 1f
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ mWidth = w
+ mHeight = h
+ resetPath()
+ }
+
+ private fun resetPath() {
+ mPath.reset()
+ mPath.moveTo(0f, 0f)
+ mPath.lineTo(0f, (mHeight - mArcHeight).toFloat())
+ mPath.quadTo((mWidth / 2).toFloat(), (mHeight + mArcHeight).toFloat(), mWidth.toFloat(), (mHeight - mArcHeight).toFloat())
+ mPath.lineTo(mWidth.toFloat(), 0f)
+ mPath.close()
+ }
+
+ override fun onDraw(canvas: Canvas?) {
+ super.onDraw(canvas)
+ canvas?.drawPath(mPath, mPaint)
+ }
+
+ fun setArcViewBgColor(color: Int) {
+ mBgColor = color
+ mPaint.color = color
+ invalidate()
+ }
+
+ fun getArcViewBgColor() = mBgColor
+
+ fun setArcViewHeight(height: Int) {
+ if (height == mHeight) return
+ mHeight = height
+ resetPath()
+ invalidate()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/CarMoveView.kt b/app/src/main/java/com/allen/androidcustomview/widget/CarMoveView.kt
new file mode 100644
index 0000000..d88eeee
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/CarMoveView.kt
@@ -0,0 +1,159 @@
+package com.allen.androidcustomview.widget
+
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import com.allen.androidcustomview.R
+import kotlin.math.atan2
+
+
+/**
+ *
+ * @author : Allen
+ * date : 2019年07月23日
+ * desc : 小汽车跟随轨迹运动
+ *
+ */
+class CarMoveView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
+
+ private var mWidth = 0
+ private var mHeight = 0
+
+ /**
+ * 背景颜色
+ */
+ private var mPathColor = Color.RED
+ private var mPaint = Paint()
+ private var mCarPaint = Paint()
+
+ private var mRect = Rect()
+ private var mMovePath = Path()
+
+ private var mStartX = 0f
+ private var mStartY = 0f
+
+ private var pathMeasure: PathMeasure = PathMeasure()
+
+ private val pos = FloatArray(2)
+ private val tan = FloatArray(2)
+
+ private var isMoveCar = false
+
+ private var mRectWidth = 30
+
+ private var mDuration = 5
+ private var mCarDrawableRes: Drawable? = null
+ private var mCarBitmapRes: Bitmap? = null
+
+ init {
+ initView(context, attrs, defStyleAttr)
+ initPaint()
+ mCarBitmapRes = (mCarDrawableRes as? BitmapDrawable)?.bitmap
+ }
+
+ private fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarMoveView)
+ mPathColor = typedArray.getColor(R.styleable.CarMoveView_carMovePathColor, mPathColor)
+ mDuration = typedArray.getInt(R.styleable.CarMoveView_carMoveDuration, mDuration)
+ mCarDrawableRes = typedArray.getDrawable(R.styleable.CarMoveView_carMoveDrawableRes)
+ typedArray.recycle()
+ }
+
+ private fun initPaint() {
+ mPaint.color = mPathColor
+ mPaint.style = Paint.Style.STROKE
+ mPaint.isAntiAlias = true
+
+ mCarPaint.color = mPathColor
+ mCarPaint.style = Paint.Style.FILL
+ mCarPaint.isAntiAlias = true
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ mWidth = w
+ mHeight = h
+ }
+
+ override fun onDraw(canvas: Canvas?) {
+ super.onDraw(canvas)
+ drawPath(canvas)
+ drawMoveCar(canvas)
+ }
+
+ private fun drawPath(canvas: Canvas?) {
+ canvas?.drawPath(mMovePath, mPaint)
+ }
+
+ private fun drawMoveCar(canvas: Canvas?) {
+ pathMeasure.setPath(mMovePath, false)
+ if (isMoveCar) {
+ // 计算图片旋转角度
+ val degrees = (atan2(tan[1].toDouble(), tan[0].toDouble()) * 180.0 / Math.PI).toFloat()
+ canvas?.rotate(degrees, pos[0], pos[1])
+ //小车中心点在运行轨道上
+ mRect.set((pos[0] - mRectWidth).toInt(), (pos[1] - mRectWidth).toInt(), (pos[0] + mRectWidth).toInt(), (pos[1] + mRectWidth).toInt())
+ //小车轮子在运行轨道上
+ //mRect.set((pos[0] - mRectWidth).toInt(), (pos[1] - mRectWidth * 2).toInt(), (pos[0] + mRectWidth).toInt(), (pos[1]).toInt())
+ if (mCarBitmapRes == null) {
+ canvas?.drawRect(mRect, mCarPaint)
+ } else {
+ canvas?.drawBitmap(mCarBitmapRes, null, mRect, mPaint)
+ }
+ }
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ mStartX = event.x
+ mStartY = event.y
+ mMovePath.reset()
+ mMovePath.moveTo(mStartX, mStartY)
+ return true
+ }
+ MotionEvent.ACTION_MOVE -> {
+ val endX = (mStartX + event.x) / 2
+ val endY = (mStartY + event.y) / 2
+ mMovePath.quadTo(mStartX, mStartY, endX, endY)
+ mStartX = event.x
+ mStartY = event.y
+ invalidate()
+ return true
+ }
+ }
+ return super.onTouchEvent(event)
+ }
+
+ fun startAnim() {
+ isMoveCar = true
+ val valueAnimator = ValueAnimator.ofFloat(0f, pathMeasure.length)
+ valueAnimator.duration = mDuration * 1000L
+ valueAnimator.addUpdateListener {
+ val distance: Float = it.animatedValue as Float
+ pathMeasure.getPosTan(distance, pos, tan)
+ invalidate()
+ }
+ valueAnimator.addListener(object : Animator.AnimatorListener {
+ override fun onAnimationRepeat(animation: Animator?) {}
+
+ override fun onAnimationEnd(animation: Animator?) {
+ isMoveCar = false
+ }
+
+ override fun onAnimationCancel(animation: Animator?) {
+ isMoveCar = false
+ }
+
+ override fun onAnimationStart(animation: Animator?) {}
+
+ })
+ valueAnimator.start()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/ClearScreenView.kt b/app/src/main/java/com/allen/androidcustomview/widget/ClearScreenView.kt
new file mode 100644
index 0000000..3b5e0bf
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/ClearScreenView.kt
@@ -0,0 +1,371 @@
+package com.allen.androidcustomview.widget
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import kotlin.math.abs
+
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2021年01月28日
+ * desc : 滑动清屏view
+ *
+ */
+enum class ClearScreenType {
+ LEFT_TO_RIGHT,//从左滑到右清屏
+ RIGHT_TO_LEFT //从右滑到左清屏
+}
+
+enum class ClearScreenStatus {
+ NORMAL,//正常状态
+ CLEARED//已经清屏状态
+}
+
+enum class ClearScreenMode {
+ QUICK_SCROLL,//快速滑动才触发清屏
+ SLOW_SCROLL//滑动出发清屏
+}
+
+class ClearScreenView @JvmOverloads constructor(private val mContext: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(mContext, attrs, defStyleAttr) {
+
+ companion object {
+ /**
+ * 最小移动距离
+ */
+ private const val MIN_SCROLL_SIZE = 50
+
+ /**
+ * 水平方向最小滑动速度
+ */
+ private const val MIN_X_VELOCITY = 10
+
+ /**
+ * 清屏动画时长
+ */
+ private const val DURATION = 300L
+ }
+
+ /**
+ * 手指按下的x轴位置
+ */
+ private var mDownX = 0
+
+ /**
+ * 手指按下的y轴位置
+ */
+ private var mDownY = 0
+
+ /**
+ * 清屏view清屏时需要在x轴的偏移量
+ */
+ private var translateX = 0
+
+ /**
+ * 清屏view起始偏移量(例如:从无到有迁移量从-width到0)
+ */
+ private var startTranslateX = 0
+
+ /**
+ * 滑动速度对象
+ */
+ private var mVelocityTracker: VelocityTracker? = null
+
+ /**
+ * 清屏动画对象
+ */
+ private var mAnimator: ValueAnimator? = null
+
+ /**
+ * 需要清除的Views
+ */
+ private var listClearViews: ArrayList = ArrayList()
+
+ /**
+ * 清屏事件
+ */
+ private var clearScreenListener: OnClearScreenListener? = null
+
+ /**
+ * 清屏类型 左滑清屏 or 右滑清屏 (默认从左滑到右清屏)
+ */
+ private var clearScreenType = ClearScreenType.LEFT_TO_RIGHT
+
+ /**
+ * 当前清屏状态
+ */
+ private var clearScreenStatus = ClearScreenStatus.NORMAL
+
+ /**
+ * 清屏模式
+ */
+ var clearScreenMode = ClearScreenMode.QUICK_SCROLL
+
+ /**
+ * 是否正在处在滑动清屏状态
+ */
+ private var isScrolling = false
+
+ init {
+ initView()
+ initAnim()
+ }
+
+ private fun initView() {
+ val view = View(mContext)
+ view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ view.isClickable = true
+ addView(view, 0)
+ }
+
+ private fun initAnim() {
+ mVelocityTracker = VelocityTracker.obtain()
+ mAnimator = ValueAnimator.ofFloat(0f, 1.0f).setDuration(DURATION)
+ mAnimator?.addUpdateListener {
+ val value = it.animatedValue as Float
+ val translate = startTranslateX + value * translateX
+ translateChild(translate)
+ }
+ mAnimator?.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ when (clearScreenStatus) {
+ ClearScreenStatus.CLEARED -> {
+ clearScreenStatus = ClearScreenStatus.NORMAL
+ clearScreenListener?.onRestored()
+ }
+ ClearScreenStatus.NORMAL -> {
+ clearScreenStatus = ClearScreenStatus.CLEARED
+ clearScreenListener?.onCleared()
+ }
+ }
+ isScrolling = false
+ }
+ })
+ }
+
+ override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
+ if (clearScreenListener?.isForbidClearScreen() == true) {
+ return super.onInterceptTouchEvent(ev)
+ }
+ val x = ev.x.toInt()
+ val y = ev.y.toInt()
+ when (ev.action) {
+ MotionEvent.ACTION_DOWN -> {
+ mDownX = x
+ mDownY = y
+ }
+ }
+ return isInterceptClearScreenEvent(x, y)
+ }
+
+ private fun isInterceptClearScreenEvent(x: Int, y: Int): Boolean {
+ return when (clearScreenMode) {
+ ClearScreenMode.QUICK_SCROLL -> {
+ val isIntercept = isMoveForHorizontal(x, y) && !isAnimRunning() && isGreaterThanMinSize(mDownX, x)
+ requestDisallowInterceptTouchEvent(isIntercept)
+ isIntercept
+ }
+ ClearScreenMode.SLOW_SCROLL -> {
+ val isIntercept = isMoveForHorizontal(x, y) && !isAnimRunning() || isScrolling
+ requestDisallowInterceptTouchEvent(isIntercept)
+ isIntercept
+ }
+ }
+ }
+
+ /**
+ * 是否水平方向滑动
+ */
+ private fun isMoveForHorizontal(x: Int, y: Int): Boolean {
+ return abs(x - mDownX)> abs(y - mDownY)
+ }
+
+ private fun isAnimRunning(): Boolean {
+ return mAnimator?.isRunning == true
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (isAnimRunning()) return true
+ when (clearScreenMode) {
+ ClearScreenMode.QUICK_SCROLL -> {
+ handleQuickScrollMode(event)
+ }
+ ClearScreenMode.SLOW_SCROLL -> {
+ handleSlowScrollMode(event)
+ }
+ }
+ return true
+ }
+
+ private fun handleQuickScrollMode(event: MotionEvent) {
+ mVelocityTracker?.addMovement(event)
+ when (event.action) {
+ MotionEvent.ACTION_UP -> {
+ mVelocityTracker?.computeCurrentVelocity(10)
+ val xVelocity = mVelocityTracker?.xVelocity ?: 0f
+ when {
+ //从左往右滑动(是正数)速度大于阈值
+ xVelocity> MIN_X_VELOCITY -> {
+ setLeft2RightMoveTranslateX()
+ }
+ //从右往左滑动(是负数)速度大于阈值
+ xVelocity < -MIN_X_VELOCITY -> {
+ setRight2LeftMoveTranslateX()
+ }
+ else -> {
+ translateX = 0
+ }
+ }
+ if (translateX != 0) {
+ mAnimator?.start()
+ }
+ }
+ }
+ }
+
+ private fun handleSlowScrollMode(event: MotionEvent) {
+ val x = event.x.toInt()
+ when (event.action) {
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ when (clearScreenStatus) {
+ ClearScreenStatus.NORMAL -> {
+ if (startTranslateX>= width / 2) {
+ translateX = width - startTranslateX
+ clearScreenStatus = ClearScreenStatus.NORMAL
+ } else {
+ translateX = -startTranslateX
+ clearScreenStatus = ClearScreenStatus.CLEARED
+ }
+ mAnimator?.start()
+ }
+ ClearScreenStatus.CLEARED -> {
+ if (startTranslateX>= width / 2) {
+ translateX = width - startTranslateX
+ clearScreenStatus = ClearScreenStatus.NORMAL
+ } else {
+ translateX = (-startTranslateX)
+ clearScreenStatus = ClearScreenStatus.CLEARED
+ }
+ mAnimator?.start()
+ }
+ }
+ }
+ MotionEvent.ACTION_MOVE -> {
+ translateX = 0
+ val move = x - mDownX
+ when (clearScreenStatus) {
+ ClearScreenStatus.NORMAL -> {
+ val translate = if (x <= mDownX) 0 else move + startTranslateX = translate + if (translate != 0) { + translateChild(translate.toFloat()) + } + } + ClearScreenStatus.CLEARED -> {
+ val translate = if (x> mDownX) 0 else mDownX - x
+ startTranslateX = width - translate
+ if (startTranslateX != 0) {
+ translateChild(startTranslateX.toFloat())
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun setLeft2RightMoveTranslateX() {
+ when (clearScreenType) {
+ ClearScreenType.LEFT_TO_RIGHT -> {
+ startTranslateX = 0
+ translateX = if (clearScreenStatus == ClearScreenStatus.NORMAL) width else 0
+ }
+ ClearScreenType.RIGHT_TO_LEFT -> {
+ startTranslateX = -width
+ translateX = if (clearScreenStatus == ClearScreenStatus.NORMAL) 0 else width
+ }
+ }
+ }
+
+ private fun setRight2LeftMoveTranslateX() {
+ when (clearScreenType) {
+ ClearScreenType.LEFT_TO_RIGHT -> {
+ startTranslateX = width
+ translateX = if (clearScreenStatus == ClearScreenStatus.NORMAL) 0 else -width
+ }
+ ClearScreenType.RIGHT_TO_LEFT -> {
+ startTranslateX = 0
+ translateX = if (clearScreenStatus == ClearScreenStatus.NORMAL) -width else 0
+ }
+ }
+ }
+
+ /**
+ * 是否大于清屏方向的最小值
+ */
+ private fun isGreaterThanMinSize(downX: Int, moveX: Int): Boolean {
+ return when (clearScreenType) {
+ ClearScreenType.LEFT_TO_RIGHT -> {
+ when (clearScreenStatus) {
+ ClearScreenStatus.NORMAL -> moveX - downX> MIN_SCROLL_SIZE
+ ClearScreenStatus.CLEARED -> downX - moveX> MIN_SCROLL_SIZE
+ }
+ }
+ ClearScreenType.RIGHT_TO_LEFT -> {
+ when (clearScreenStatus) {
+ ClearScreenStatus.NORMAL -> downX - moveX> MIN_SCROLL_SIZE
+ ClearScreenStatus.CLEARED -> moveX - downX> MIN_SCROLL_SIZE
+ }
+ }
+ }
+ }
+
+ private fun translateChild(translate: Float) {
+ isScrolling = true
+ for (view in listClearViews) {
+ view.translationX = translate
+ }
+ }
+
+ fun addClearViews(views: List) {
+ for (view in views) {
+ if (!listClearViews.contains(view)) {
+ listClearViews.add(view)
+ }
+ }
+ }
+
+ fun addClearView(view: View) {
+ if (!listClearViews.contains(view)) {
+ listClearViews.add(view)
+ }
+ }
+
+ fun removeClearViews(views: List) {
+ for (view in views) {
+ listClearViews.remove(view)
+ }
+ }
+
+ fun removeAllClearViews() {
+ listClearViews.clear()
+ }
+
+ fun setOnClearScreenListener(listener: OnClearScreenListener?) {
+ this.clearScreenListener = listener
+ }
+
+ interface OnClearScreenListener {
+ fun onCleared()
+ fun onRestored()
+ fun isForbidClearScreen(): Boolean = false
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/StudyPlanProgressView.kt b/app/src/main/java/com/allen/androidcustomview/widget/StudyPlanProgressView.kt
new file mode 100644
index 0000000..468cf0f
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/StudyPlanProgressView.kt
@@ -0,0 +1,163 @@
+package com.allen.androidcustomview.widget
+
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.BitmapDrawable
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.view.View
+import com.allen.androidcustomview.R
+import kotlin.math.min
+
+/**
+ *
+ * @author : Allen
+ * date : 2019年08月12日
+ * desc : 学习计划view
+ *
+ */
+class StudyPlanProgressView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
+ private var mWidth = 0
+ private var mHeight = 0
+
+ private var iconPaint: Paint? = null
+ private var linePaint: Paint = Paint()
+ private var textPaint: Paint = Paint()
+ private var iconRect = Rect()
+ private var textRectF = Rect()
+
+ private val ALL_POINT_SIZE = 7
+ private var cellWidth = 0
+ private var iconSize = 0
+ private var textSize = 0
+ private var textColor = 0
+ private var progressWidth = 0
+ private var uncheckedProgressColor = Color.GRAY
+ private var checkedProgressColor = Color.YELLOW
+
+ private var iconUncheckedBitmapRes: Bitmap? = null
+ private var iconCheckedBitmapRes: Bitmap? = null
+
+ private var dates: MutableList = mutableListOf()
+
+ private var marginLeftAndRight = 0
+
+ init {
+ marginLeftAndRight = dp2px(27f).toInt()
+ iconSize = dp2px(14f).toInt()
+ initAttr(context, attrs, defStyleAttr)
+ initPaint()
+ }
+
+ private fun initAttr(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ val typedArray = context.obtainStyledAttributes(attrs, R.styleable.StudyPlanProgressView)
+ iconUncheckedBitmapRes = (typedArray.getDrawable(R.styleable.StudyPlanProgressView_sppv_iconUnchecked) as? BitmapDrawable)?.bitmap
+ iconCheckedBitmapRes = (typedArray.getDrawable(R.styleable.StudyPlanProgressView_sppv_iconChecked) as? BitmapDrawable)?.bitmap
+ iconSize = typedArray.getDimensionPixelOffset(R.styleable.StudyPlanProgressView_sppv_iconSize, dp2px(14f).toInt())
+ progressWidth = typedArray.getDimensionPixelOffset(R.styleable.StudyPlanProgressView_sppv_progressWidth, dp2px(2f).toInt())
+ uncheckedProgressColor = typedArray.getColor(R.styleable.StudyPlanProgressView_sppv_uncheckedProgressColor, uncheckedProgressColor)
+ checkedProgressColor = typedArray.getColor(R.styleable.StudyPlanProgressView_sppv_checkedProgressColor, checkedProgressColor)
+ textSize = typedArray.getDimensionPixelSize(R.styleable.StudyPlanProgressView_sppv_textSize, sp2px(11))
+ textColor = typedArray.getColor(R.styleable.StudyPlanProgressView_sppv_textColor, checkedProgressColor)
+
+ typedArray.recycle()
+ }
+
+ private fun initPaint() {
+ iconPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ iconPaint?.isFilterBitmap = true
+ iconPaint?.isDither = true
+
+ linePaint.strokeWidth = progressWidth.toFloat()
+ linePaint.color = uncheckedProgressColor
+ linePaint.isAntiAlias = true
+ linePaint.style = Paint.Style.FILL_AND_STROKE
+
+ textPaint.textSize = textSize.toFloat()
+ textPaint.color = textColor
+ textPaint.textAlign = Paint.Align.CENTER
+ textPaint.isAntiAlias = true
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ mWidth = w
+ mHeight = h
+
+ cellWidth = (mWidth - marginLeftAndRight * 2 - iconSize * ALL_POINT_SIZE) / (ALL_POINT_SIZE - 1)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ drawProgressLine(canvas)
+ drawProgress(canvas)
+ }
+
+ private fun drawProgressLine(canvas: Canvas) {
+ val lineStart = (marginLeftAndRight + iconSize / 2).toFloat()
+ linePaint.color = uncheckedProgressColor
+ canvas.drawLine(lineStart, (mHeight / 2).toFloat(), (mWidth - marginLeftAndRight - iconSize / 2).toFloat(), (mHeight / 2).toFloat(), linePaint)
+ if (getDataSize() == 0) return
+ val lineEnd = lineStart + (getDataSize() - 1) * (cellWidth + iconSize)
+ linePaint.color = checkedProgressColor
+ canvas.drawLine(lineStart, (mHeight / 2).toFloat(), lineEnd, (mHeight / 2).toFloat(), linePaint)
+ }
+
+ private fun drawProgress(canvas: Canvas) {
+ if (iconUncheckedBitmapRes == null || iconCheckedBitmapRes == null) return
+ for (i in 0 until ALL_POINT_SIZE) {
+ val left = marginLeftAndRight + (cellWidth + iconSize) * i
+ iconRect.set(left, (mHeight - iconSize) / 2, left + iconSize, (mHeight + iconSize) / 2)
+ val bitmap = if (isFinished(i)) iconCheckedBitmapRes else iconUncheckedBitmapRes
+ canvas.drawBitmap(bitmap, null, iconRect, iconPaint)
+ drawText(canvas, i)
+ }
+ }
+
+ private fun isFinished(position: Int): Boolean {
+ return if (position <= getDataSize() - 1) { + dates[position].isFinished + } else false + } + + private fun drawText(canvas: Canvas, position: Int) { + if (getDataSize() == 0) return + if (position < getDataSize()) { + val textWidth = textPaint.measureText(dates[position].content).toInt() + val isTop = position % 2 == 0 + val left = marginLeftAndRight + iconSize / 2 + (cellWidth + iconSize) * position - textWidth / 2 + textRectF.left = left + textRectF.right = left + textWidth + textRectF.top = if (isTop) 0 else (mHeight + iconSize) / 2 + textRectF.bottom = if (isTop) (mHeight - iconSize) / 2 else mHeight + + val fontMetrics = textPaint.fontMetricsInt + val baseline = (textRectF.bottom + textRectF.top - fontMetrics.bottom - fontMetrics.top) / 2 + //文字绘制到整个布局的中心位置 + canvas.drawText(dates[position].content, textRectF.centerX().toFloat(), baseline.toFloat(), textPaint) + } + } + + fun setData(date: List?) {
+ dates.clear()
+ val timeList = date?.toMutableList() ?: mutableListOf()
+ timeList.forEach {
+ dates.add(ProgressData(it, true))
+ }
+ invalidate()
+ }
+
+ private fun getDataSize() = min(dates.size, 7)
+
+ fun dp2px(dpVal: Float): Float {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ dpVal, resources.displayMetrics)
+ }
+
+ fun sp2px(spVal: Int): Int {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+ spVal.toFloat(), resources.displayMetrics).toInt()
+ }
+
+ class ProgressData(var content: String, var isFinished: Boolean)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteContainerView.kt b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteContainerView.kt
new file mode 100644
index 0000000..9cf7f2c
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteContainerView.kt
@@ -0,0 +1,283 @@
+package com.allen.androidcustomview.widget.vote
+
+import android.content.Context
+import android.graphics.Outline
+import android.os.Build
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewOutlineProvider
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.Toast
+import com.allen.androidcustomview.R
+import com.allen.androidcustomview.bean.VoteBean
+import com.allen.androidcustomview.bean.VoteOption
+import kotlinx.android.synthetic.main.widget_vote_layout.view.*
+import java.lang.ref.WeakReference
+
+/**
+ *
+ * @author : Allen
+ * date : 2019年08月01日
+ * desc :
+ *
+ */
+class VoteContainerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) {
+
+ private var voteViewHolders: ArrayList = arrayListOf()
+
+ var onVoteClickListener: OnVoteClickListener? = null
+
+ companion object {
+ val VOTE_TYPE_MULTIPLE = "multiple"
+ val VOTE_TYPE_SINGLE = "single"
+ }
+
+ private var optionIds = arrayListOf()
+
+ private var mData: VoteBean? = null
+
+ init {
+ initView()
+ }
+
+ private fun initView() {
+ LayoutInflater.from(context).inflate(R.layout.widget_vote_layout, this)
+
+ vote_container_vote_btn.setOnClickListener {
+ if (mData == null) return@setOnClickListener
+ onVoteClickListener?.onVoteCommitBtnClick(mData, optionIds)
+ }
+ }
+
+ fun setVoteData(data: VoteBean?) {
+ if (data == null || data.options.isNullOrEmpty()) {
+ visibility = View.GONE
+ } else {
+ mData = data
+ setVoteTitle(data.title)
+ voteViewHolders.clear()
+ optionIds.clear()
+ setVoteStatus(data)
+ setVoteBtnStatus()
+ vote_item_ll.removeAllViews()
+ data.options?.forEachIndexed { index, voteOption ->
+ val viewHolder = onCreateViewHolder()
+ viewHolder.bind(index, voteOption, data)
+ voteViewHolders.add(viewHolder)
+ vote_item_ll.addView(viewHolder.voteView)
+ }
+ }
+ }
+
+ private fun setVoteTitle(title: String?) {
+ vote_container_title.text = title ?: ""
+ }
+
+ private fun setVoteStatus(vote: VoteBean) {
+ val isVoteMulti = vote.choiceType == VOTE_TYPE_MULTIPLE
+ val voteResult = vote.sumVoteCount ?: 0
+ val voted = vote.voted
+ vote_container_vote_btn.visibility = if (voted != true && isVoteMulti) View.VISIBLE else View.GONE
+ vote_container_vote_result.visibility = if (voted == true) View.VISIBLE else View.GONE
+ setVoteResult("共${voteResult}人参与了投票")
+ }
+
+ private fun addOptionIds(id: Int) {
+ optionIds.add(id)
+ }
+
+
+ private fun removeOptionIds(id: Int) {
+ optionIds.remove(id)
+ }
+
+ private fun getOptionIdsSize(): Int {
+ return optionIds.size
+ }
+
+ private fun setVoteBtnStatus() {
+ val clickable = getOptionIdsSize()> 0
+ if (clickable) {
+ vote_container_vote_btn.setBackgroundResource(R.drawable.shape_bg_clickable)
+ } else {
+ vote_container_vote_btn.setBackgroundResource(R.drawable.shape_bg_un_clickable)
+ }
+ vote_container_vote_btn.isClickable = clickable
+ }
+
+ fun refreshDataAfterVoteSuccess() {
+ vote_container_vote_result.visibility = View.VISIBLE
+ vote_container_vote_btn.visibility = View.GONE
+ refreshVoteResult()
+ startProgressAnim()
+ }
+
+ fun refreshDataAfterVoteFailed() {
+ voteViewHolders.forEach {
+ it.resetDataAfterSingleVoteFailed()
+ }
+ }
+
+ fun onDestroy() {
+ voteViewHolders.forEach {
+ it.onVoteDestroy()
+ }
+ }
+
+ private fun refreshVoteResult() {
+ val voteResult = (mData?.sumVoteCount ?: 0)
+ setVoteResult("共${(voteResult + 1)}人参与了投票")
+ }
+
+ private fun setVoteResult(result: String?) {
+ vote_container_vote_result.text = result ?: ""
+ }
+
+ private fun startProgressAnim() {
+ voteViewHolders.forEach {
+ it.setProgress()
+ }
+ }
+
+ private fun onCreateViewHolder(): VoteItemViewHolder {
+ return VoteItemViewHolder(getVoteView(context), this)
+ }
+
+ class VoteItemViewHolder(var voteView: VoteView, voteContainerView: VoteContainerView) {
+
+ private val voteContainerViewRef = WeakReference(voteContainerView)
+
+ private fun ref(): VoteContainerView? {
+ return voteContainerViewRef.get()
+ }
+
+ private var data: VoteOption? = null
+ private var mainVote: VoteBean? = null
+
+ var isVoteMulti = true
+
+ fun bind(position: Int, voteOption: VoteOption, mainVote: VoteBean) {
+ this.data = voteOption
+ this.mainVote = mainVote
+
+ isVoteMulti = mainVote.choiceType == VOTE_TYPE_MULTIPLE
+
+ val voteResultCount = voteOption.showCount ?: 0
+
+ voteView.setVoteIsSelected(voteOption.voted ?: false)
+ .setVoteContent(voteOption.content)
+ .setVoteResultText("${voteResultCount}人").refreshView()
+
+ if (isHaveVoted()) {
+ val sum = mainVote?.sumVoteCount ?: 0
+ val showCount = data?.showCount ?: 0
+ val progress = if (sum == 0) 0f else showCount.toFloat() / sum.toFloat()
+
+ voteView.setProgress(progress)
+ }
+
+ voteView.setOnClickListener {
+ if (isHaveVoted()) return@setOnClickListener
+ if (isVoteMulti) {
+ setMultiChoice(voteView, voteOption)
+ ref()?.onVoteClickListener?.onVoteItemClick(mainVote, data)
+ } else {
+ if (ref()?.getOptionIdsSize() ?: 0> 0) return@setOnClickListener
+ setSingleChoice(voteView, voteOption)
+ ref()?.onVoteClickListener?.onVoteItemClick(mainVote, data)
+ }
+ }
+
+ }
+
+ private fun isHaveVoted(): Boolean {
+ return mainVote?.voted ?: false
+ }
+
+ fun setProgress() {
+ val sum = mainVote?.sumVoteCount ?: 0
+ var showCount = data?.showCount ?: 0
+ val realShowCount = if (data?.voted == true) showCount + 1 else showCount
+ voteView.setVoteResultText("${realShowCount}人")
+ mainVote?.voted = true
+ val progress = if (sum == 0) 0f else realShowCount.toFloat() / sum.toFloat()
+ voteView.setProgressWithAnim(progress)
+ }
+
+ fun resetDataAfterSingleVoteFailed() {
+ if (isVoteMulti) return
+ data?.voted = false
+ ref()?.removeOptionIds(data?.id ?: 0)
+ voteView.setVoteIsSelected(data?.voted ?: false).refreshView()
+ }
+
+ fun onVoteDestroy() {
+ voteView.onDestroy()
+ }
+
+ private fun setMultiChoice(voteView: VoteView, voteOption: VoteOption) {
+ if (voteOption.voted == true) {
+ voteOption.voted = false
+ ref()?.removeOptionIds(voteOption.id ?: 0)
+ } else {
+ val optionsIdSize = ref()?.optionIds?.size ?: 0
+ val maxSelect = mainVote?.maxSelect ?: 0
+ if (optionsIdSize < maxSelect) { + voteOption.voted = true + ref()?.addOptionIds(voteOption.id ?: 0) + } else { + Toast.makeText(ref()?.context, "最多可选${maxSelect}个", Toast.LENGTH_SHORT).show() + } + } + ref()?.setVoteBtnStatus() + voteView.setVoteIsSelected(voteOption.voted ?: false).refreshView() + } + + private fun setSingleChoice(voteView: VoteView, voteOption: VoteOption) { + voteOption.voted = true + ref()?.addOptionIds(voteOption.id ?: 0) + voteView.setVoteIsSelected(voteOption.voted ?: false).refreshView() + ref()?.onVoteClickListener?.onVoteCommitBtnClick(mainVote, ref()?.optionIds + ?: arrayListOf()) + } + + } + + private fun getVoteView(context: Context): VoteView { + val voteView = VoteView(context) + voteView.setVoteTextSize(voteView.sp2px(15)) + .setVoteUncheckedContentTextColor(resources.getColor(R.color.unchecked_content_text_color)) + .setVoteCheckedContentTextColor(resources.getColor(R.color.checked_content_text_color)) + .setVoteUncheckedResultTextColor(resources.getColor(R.color.unchecked_result_text_color)) + .setVoteCheckedResultTextColor(resources.getColor(R.color.checked_result_text_color)) + .setVoteUncheckedProgressColor(resources.getColor(R.color.unchecked_progress_color)) + .setVoteCheckedProgressColor(resources.getColor(R.color.checked_progress_color)) + .setVoteCheckedIcon(resources.getDrawable(R.mipmap.icon_vote_check)) + .setVoteBorderRadius(voteView.dp2px(3f)) + .setVoteBorderColor(resources.getColor(R.color.border_color)) + .setVoteRightIconSize(voteView.dp2px(18f).toInt()) + .setVoteAnimDuration(2000L) + if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.LOLLIPOP) {
+ voteView.clipToOutline = true
+ voteView.outlineProvider = object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(0, 0, view.width, view.height, voteView.dp2px(3f))
+ }
+ }
+ }
+
+ val layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, voteView.dp2px(40f).toInt())
+ layoutParams.bottomMargin = voteView.dp2px(12f).toInt()
+ voteView.layoutParams = layoutParams
+ return voteView
+ }
+
+
+ interface OnVoteClickListener {
+ fun onVoteCommitBtnClick(mainVote: VoteBean?, optionIds: ArrayList)
+ fun onVoteItemClick(mainVote: VoteBean?, voteOption: VoteOption?)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteLayoutAdapter.kt b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteLayoutAdapter.kt
new file mode 100644
index 0000000..95a7eca
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteLayoutAdapter.kt
@@ -0,0 +1,89 @@
+package com.allen.androidcustomview.widget.vote
+
+import android.view.View
+import android.view.ViewGroup
+import com.allen.androidcustomview.bean.VoteBean
+import com.allen.androidcustomview.bean.VoteOption
+import java.lang.ref.WeakReference
+
+/**
+ *
+ * @author : Allen
+ * date : 2019年08月03日
+ * desc :
+ *
+ */
+class VoteLayoutAdapter(private val viewGroup: ViewGroup) {
+
+ private var viewHolders = mutableListOf()
+
+ var onVoteClickListener: OnVoteClickListener? = null
+
+ fun setData(vote: ArrayList?) {
+ viewGroup.removeAllViews()
+ viewHolders.clear()
+ if (vote == null || vote.size <= 0) { + viewGroup.visibility = View.GONE + } else { + viewGroup.visibility = View.VISIBLE + val size = vote.size + for (i in 0 until size) { + val viewHolder = onCreateViewHolder(viewGroup, i) + viewHolder.bind(vote[i]) + viewHolders.add(viewHolder) + viewGroup.addView(viewHolder.voteContainerView) + } + } + } + + fun refreshDataAfterVotedSuccess(position: Int) { + viewHolders[position].voteContainerView.refreshDataAfterVoteSuccess() + } + + fun refreshDataAfterVotedFailed(position: Int) { + viewHolders[position].voteContainerView.refreshDataAfterVoteFailed() + } + + fun onDestroy() { + viewHolders.forEach { + it.voteContainerView.onDestroy() + } + } + + private fun onCreateViewHolder(viewGroup: ViewGroup, position: Int): VoteViewHolder { + val view = VoteContainerView(viewGroup.context) + return VoteViewHolder(view, this, position) + } + + class VoteViewHolder(view: VoteContainerView, adapter: VoteLayoutAdapter, var position: Int) { + + private val adapterRef = WeakReference(adapter) + private fun ref(): VoteLayoutAdapter? { + return adapterRef.get() + } + + var voteContainerView = view + private var mMainVote: VoteBean? = null + + fun bind(mainVote: VoteBean) { + mMainVote = mainVote + voteContainerView.setVoteData(mainVote) + voteContainerView.onVoteClickListener = object : VoteContainerView.OnVoteClickListener { + override fun onVoteCommitBtnClick(mainVote: VoteBean?, optionIds: ArrayList) {
+ ref()?.onVoteClickListener?.onVoteCommitBtnClick(mainVote, optionIds, position)
+ }
+
+ override fun onVoteItemClick(mainVote: VoteBean?, voteOption: VoteOption?) {
+ ref()?.onVoteClickListener?.onVoteItemClick(mainVote, voteOption, position)
+ }
+
+ }
+ }
+
+ }
+
+ interface OnVoteClickListener {
+ fun onVoteCommitBtnClick(mainVote: VoteBean?, optionIds: ArrayList, position: Int)
+ fun onVoteItemClick(mainVote: VoteBean?, voteOption: VoteOption?, position: Int)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteView.kt b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteView.kt
new file mode 100644
index 0000000..7a0845c
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteView.kt
@@ -0,0 +1,419 @@
+package com.allen.androidcustomview.widget.vote
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.view.View
+import android.view.animation.DecelerateInterpolator
+import com.allen.androidcustomview.R
+import kotlin.math.max
+
+/**
+ *
+ * @author : Allen
+ * date : 2019年07月30日
+ * desc :
+ *
+ */
+class VoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
+
+ private var mWidth = 0
+ private var mHeight = 0
+
+ private var bgRectF = RectF()
+ private var progressRectF = RectF()
+ private var voteContentRectF = Rect()
+ private var voteResultRectF = Rect()
+ private var voteRightIconRectF = Rect()
+
+ private var voteResultBaseline = 0
+ private var voteContentBaseline = 0
+
+ private var progressPaint: Paint? = null
+ private var iconPaint: Paint? = null
+ private var bgPaint: Paint? = null
+ private var borderPaint: Paint? = null
+
+ private var voteContentTextPaint: Paint? = null
+ private var voteResultTextPaint: Paint? = null
+
+ private var animDuration = 1000L
+
+ private var mScale = 1f
+
+ private var mProgress = -1f
+ private var mVoteContent: String? = null
+ private var mVoteResult: String? = null
+
+ private var valueAnimator: ValueAnimator? = null
+
+ private var textMarginLeft = 0
+ private var voteResultMarginRight = 0
+
+ private var textPaintSize: Int = 0
+
+ private var rightCheckedBitmapRes: Bitmap? = null
+
+ private var rightIconWidth = 0
+ private var rightIconHeight = 0
+
+ private var checkedProgressColor = 0
+ private var unCheckedProgressColor = 0
+
+ private var checkedContentTextColor = 0
+ private var uncheckedContentTextColor = 0
+
+ private var checkedResultTextColor = 0
+ private var uncheckedResultTextColor = 0
+
+ private var borderColor = 0
+ private var borderRadius = 0f
+
+ private var isVoteChecked = false
+ private var textWidth = 0
+
+ private val defaultCheckedProgressColor = Color.argb(1, 255, 124, 5)
+ private val defaultUncheckedProgressColor = Color.parseColor("#F3F3F3")
+ private val defaultCheckedTextColor = Color.parseColor("#FF7C05")
+ private val defaultUncheckedTextColor = Color.parseColor("#1a1a1a")
+ private val defaultBorderColor = Color.parseColor("#e6e6e6")
+
+ init {
+
+ textMarginLeft = dp2px(15f).toInt()
+ voteResultMarginRight = dp2px(15f).toInt()
+
+ initAttr(context, attrs, defStyleAttr)
+
+ initPaint()
+
+ initVoteRightIcon()
+
+ initColor()
+ }
+
+ private fun initColor() {
+ voteContentTextPaint?.color = if (isVoteChecked) checkedContentTextColor else uncheckedContentTextColor
+ voteResultTextPaint?.color = if (isVoteChecked) checkedResultTextColor else uncheckedResultTextColor
+ progressPaint?.color = if (isVoteChecked) checkedProgressColor else unCheckedProgressColor
+ bgPaint?.color = if (isVoteChecked) checkedProgressColor else unCheckedProgressColor
+ }
+
+ private fun initVoteRightIcon() {
+ if (rightCheckedBitmapRes != null) {
+ if (rightIconWidth == 0 || rightIconHeight == 0) {
+ rightIconWidth = dp2px(36f).toInt()
+ rightIconHeight = dp2px(36f).toInt()
+ }
+ }
+ }
+
+ private fun initAttr(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ val typedArray = context.obtainStyledAttributes(attrs, R.styleable.VoteView)
+
+ checkedProgressColor = typedArray.getColor(R.styleable.VoteView_voteCheckedProgressColor, defaultCheckedProgressColor)
+ unCheckedProgressColor = typedArray.getColor(R.styleable.VoteView_voteUncheckedProgressColor, defaultUncheckedProgressColor)
+
+ checkedContentTextColor = typedArray.getColor(R.styleable.VoteView_voteCheckedContentTextColor, defaultCheckedTextColor)
+ uncheckedContentTextColor = typedArray.getColor(R.styleable.VoteView_voteUncheckedContentTextColor, defaultUncheckedTextColor)
+
+ checkedResultTextColor = typedArray.getColor(R.styleable.VoteView_voteCheckedResultTextColor, defaultCheckedTextColor)
+ uncheckedResultTextColor = typedArray.getColor(R.styleable.VoteView_voteUncheckedResultTextColor, defaultUncheckedTextColor)
+
+ textPaintSize = typedArray.getDimensionPixelSize(R.styleable.VoteView_voteTextSize, sp2px(15))
+
+ borderColor = typedArray.getColor(R.styleable.VoteView_voteBorderColor, defaultBorderColor)
+ borderRadius = typedArray.getDimensionPixelOffset(R.styleable.VoteView_voteBorderRadius, dp2px(1f).toInt()).toFloat()
+
+ animDuration = typedArray.getInt(R.styleable.VoteView_voteAnimDuration, 500).toLong()
+
+ rightCheckedBitmapRes = (typedArray.getDrawable(R.styleable.VoteView_voteCheckedIcon) as? BitmapDrawable)?.bitmap
+
+ rightIconWidth = typedArray.getDimensionPixelOffset(R.styleable.VoteView_voteRightIconWidth, 0)
+ rightIconHeight = typedArray.getDimensionPixelOffset(R.styleable.VoteView_voteRightIconHeight, 0)
+
+ typedArray.recycle()
+ }
+
+ private fun initPaint() {
+ iconPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ iconPaint?.isFilterBitmap = true
+ iconPaint?.isDither = true
+
+ bgPaint = getPaint(dp2px(0.5f), Color.WHITE, Paint.Style.FILL)
+ progressPaint = getPaint(dp2px(0.5f), unCheckedProgressColor, Paint.Style.FILL)
+ borderPaint = getPaint(dp2px(0.5f), borderColor, Paint.Style.STROKE)
+
+ voteContentTextPaint = getTextPaint(uncheckedContentTextColor, textPaintSize.toFloat())
+ voteResultTextPaint = getTextPaint(uncheckedResultTextColor, textPaintSize.toFloat())
+ }
+
+ private fun getPaint(strokeWidth: Float, color: Int, style: Paint.Style): Paint {
+ val paint = Paint(Paint.ANTI_ALIAS_FLAG)
+ paint.strokeWidth = strokeWidth
+ paint.color = color
+ paint.isAntiAlias = true
+ paint.style = style
+ return paint
+ }
+
+ private fun getTextPaint(color: Int, textSize: Float): Paint {
+ val textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ textPaint.textSize = textSize
+ textPaint.color = color
+ textPaint.textAlign = Paint.Align.CENTER
+ textPaint.isAntiAlias = true
+// textPaint.typeface = Typeface.DEFAULT_BOLD
+ return textPaint
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ mWidth = w
+ mHeight = h
+
+ setBgRect()
+ setProgressRect()
+
+ setVoteResultRect()
+ setVoteContentRect()
+ setVoteRightIconRect()
+
+ }
+
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+
+ drawBg(canvas)
+ drawProgress(canvas)
+ drawBorder(canvas)
+
+ drawVoteContentText(canvas)
+ drawVoteResultText(canvas)
+ drawVoteRightIcon(canvas)
+ }
+
+
+ private fun setBgRect() {
+ bgRectF.set(0f, 0f, mWidth.toFloat(), mHeight.toFloat())
+ }
+
+ private fun setProgressRect() {
+ progressRectF.set(0f, 0f, 0f, mHeight.toFloat())
+ }
+
+ private fun setVoteResultRect() {
+ if (mVoteResult.isNullOrBlank()) return
+ voteResultTextPaint!!.getTextBounds(mVoteResult, 0, mVoteResult!!.length, voteResultRectF)
+
+ voteResultRectF.top = 0
+ voteResultRectF.bottom = mHeight
+
+ val fontMetrics = voteResultTextPaint!!.fontMetricsInt
+ voteResultBaseline = (voteResultRectF.bottom + voteResultRectF.top - fontMetrics.bottom - fontMetrics.top) / 2
+ }
+
+ private fun setVoteContentRect() {
+ if (mVoteContent.isNullOrBlank()) return
+ voteContentTextPaint!!.getTextBounds(mVoteContent, 0, mVoteContent!!.length, voteContentRectF)
+
+ textWidth = (voteContentRectF.right - voteContentRectF.left)
+ voteContentRectF.top = 0
+ voteContentRectF.bottom = mHeight
+ voteContentRectF.left = (mWidth - textWidth) / 2
+ voteContentRectF.right = voteContentRectF.left + textWidth
+
+ val fontMetrics = voteContentTextPaint!!.fontMetricsInt
+ voteContentBaseline = (voteContentRectF.bottom + voteContentRectF.top - fontMetrics.bottom - fontMetrics.top) / 2
+ }
+
+ private fun setVoteRightIconRect() {
+ voteRightIconRectF.set(voteContentRectF.right + voteResultMarginRight, (mHeight - rightIconHeight) / 2, voteContentRectF.right + voteResultMarginRight + rightIconWidth, (mHeight + rightIconHeight) / 2)
+ }
+
+
+ private fun drawBg(canvas: Canvas) {
+ if (mProgress != -1f) {
+ bgPaint?.color = Color.WHITE
+ }
+ canvas.drawRoundRect(bgRectF, 0f, 0f, bgPaint!!)
+ }
+
+ private fun drawProgress(canvas: Canvas) {
+ if (mProgress == -1f) return
+ canvas.drawRoundRect(getProgressRectF(), 0f, 0f, progressPaint!!)
+ }
+
+ private fun drawBorder(canvas: Canvas) {
+ borderPaint?.color = borderColor
+ canvas.drawRoundRect(bgRectF, borderRadius, borderRadius, borderPaint)
+ }
+
+ private fun drawVoteContentText(canvas: Canvas) {
+ if (mVoteContent.isNullOrBlank()) return
+ //文字绘制到整个布局的中心位置
+ if (mProgress == -1f) {
+ voteContentRectF.left = (mWidth - textWidth) / 2
+ voteContentRectF.right = voteContentRectF.left + textWidth
+ } else {
+ voteContentRectF.left = max(((1 - mScale) * (mWidth - textWidth) / 2).toInt(), textMarginLeft)
+ voteContentRectF.right = voteContentRectF.left + textWidth
+ }
+
+ canvas.drawText(mVoteContent, voteContentRectF.centerX().toFloat(), voteContentBaseline.toFloat(), voteContentTextPaint)
+ }
+
+ private fun drawVoteResultText(canvas: Canvas) {
+ if (mProgress == -1f || mVoteResult.isNullOrBlank()) return
+ //文字绘制到整个布局的中心位置
+ voteResultTextPaint?.alpha = (255 * mScale).toInt()
+ canvas.drawText(mVoteResult, mWidth - voteResultMarginRight - voteResultRectF.centerX().toFloat(), voteResultBaseline.toFloat(), voteResultTextPaint)
+ }
+
+ private fun drawVoteRightIcon(canvas: Canvas) {
+ if (rightCheckedBitmapRes != null && isVoteChecked) {
+ voteRightIconRectF.left = voteContentRectF.right + voteResultMarginRight
+ voteRightIconRectF.right = voteRightIconRectF.left + rightIconWidth
+ canvas.drawBitmap(rightCheckedBitmapRes!!, null, voteRightIconRectF, iconPaint)
+ }
+ }
+
+ private fun getProgressRectF(): RectF {
+// val currentProgress = mProgress * mWidth * mScale / 100
+ val currentProgress = mProgress * mWidth * mScale
+ progressRectF.set(0f, 0f, currentProgress, mHeight.toFloat())
+ return progressRectF
+ }
+
+
+ fun setVoteCheckedProgressColor(color: Int): VoteView {
+ this.checkedProgressColor = color
+ return this
+ }
+
+ fun setVoteUncheckedProgressColor(color: Int): VoteView {
+ this.unCheckedProgressColor = color
+ return this
+ }
+
+ fun setVoteBorderRadius(radius: Float): VoteView {
+ this.borderRadius = radius
+ return this
+ }
+
+ fun setVoteBorderColor(color: Int): VoteView {
+ this.borderColor = color
+ return this
+ }
+
+ fun setVoteCheckedContentTextColor(color: Int): VoteView {
+ this.checkedContentTextColor = color
+ return this
+ }
+
+ fun setVoteUncheckedContentTextColor(color: Int): VoteView {
+ this.uncheckedContentTextColor = color
+ return this
+ }
+
+
+ fun setVoteCheckedResultTextColor(color: Int): VoteView {
+ this.checkedResultTextColor = color
+ return this
+ }
+
+ fun setVoteUncheckedResultTextColor(color: Int): VoteView {
+ this.uncheckedResultTextColor = color
+ return this
+ }
+
+ fun setVoteCheckedIcon(iconBitmap: Drawable): VoteView {
+ this.rightCheckedBitmapRes = (iconBitmap as? BitmapDrawable)?.bitmap
+ return this
+ }
+
+ fun setVoteRightIconSize(width_height: Int): VoteView {
+ this.rightIconWidth = width_height
+ this.rightIconHeight = width_height
+ return this
+ }
+
+ fun setVoteTextSize(textSize: Int): VoteView {
+ this.textPaintSize = textSize
+ return this
+ }
+
+ fun setVoteAnimDuration(duration: Long): VoteView {
+ this.animDuration = duration
+ return this
+ }
+
+ fun setVoteContent(content: String?): VoteView {
+ mVoteContent = content ?: ""
+ setVoteContentRect()
+ return this
+ }
+
+ fun setVoteResultText(voteResult: String?): VoteView {
+ mVoteResult = voteResult ?: ""
+ setVoteResultRect()
+ return this
+ }
+
+ fun refreshView() {
+ initColor()
+ invalidate()
+ }
+
+
+ fun setProgress(progress: Float) {
+ mProgress = progress
+ if (mProgress != -1f) {
+ invalidate()
+ }
+ }
+
+ fun setProgressWithAnim(progress: Float) {
+ mProgress = progress
+ startAnim()
+ }
+
+ private fun startAnim() {
+ valueAnimator?.cancel()
+ if (valueAnimator == null) {
+ valueAnimator = ValueAnimator.ofFloat(0f, 1f)
+ }
+ valueAnimator?.duration = animDuration
+ valueAnimator?.interpolator = DecelerateInterpolator()
+ valueAnimator?.addUpdateListener {
+ mScale = it.animatedValue as Float
+ invalidate()
+ }
+ valueAnimator?.start()
+ }
+
+ fun onDestroy() {
+ valueAnimator?.cancel()
+ valueAnimator = null
+ }
+
+ fun setVoteIsSelected(isVoteSelected: Boolean): VoteView {
+ this.isVoteChecked = isVoteSelected
+ return this
+ }
+
+ fun dp2px(dpVal: Float): Float {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ dpVal, resources.displayMetrics)
+ }
+
+ fun sp2px(spVal: Int): Int {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+ spVal.toFloat(), resources.displayMetrics).toInt()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_bg_clickable.xml b/app/src/main/res/drawable/shape_bg_clickable.xml
new file mode 100644
index 0000000..5994224
--- /dev/null
+++ b/app/src/main/res/drawable/shape_bg_clickable.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_bg_un_clickable.xml b/app/src/main/res/drawable/shape_bg_un_clickable.xml
new file mode 100644
index 0000000..902bdea
--- /dev/null
+++ b/app/src/main/res/drawable/shape_bg_un_clickable.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_vote_item_bg.xml b/app/src/main/res/drawable/shape_vote_item_bg.xml
new file mode 100644
index 0000000..594eaf2
--- /dev/null
+++ b/app/src/main/res/drawable/shape_vote_item_bg.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_clear_screen.xml b/app/src/main/res/layout/activity_clear_screen.xml
new file mode 100644
index 0000000..675b590
--- /dev/null
+++ b/app/src/main/res/layout/activity_clear_screen.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_path.xml b/app/src/main/res/layout/activity_path.xml
new file mode 100644
index 0000000..338c4f9
--- /dev/null
+++ b/app/src/main/res/layout/activity_path.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_pregress_bar.xml b/app/src/main/res/layout/activity_pregress_bar.xml
index e343327..aaa7149 100644
--- a/app/src/main/res/layout/activity_pregress_bar.xml
+++ b/app/src/main/res/layout/activity_pregress_bar.xml
@@ -44,6 +44,18 @@
android:layout_height="wrap_content"
android:layout_margin="20dp" />
+
+