payloads) {
+ return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
+ }
+
+
+ /**
+ * 设置item添加动画初始化状态(比如默认透明度为0)
+ *
+ * @param holder 添加的ViewHolder
+ */
+ public abstract void setAddItemAnimationInit(ViewHolder holder);
+
+ /**
+ * 设置item添加动画
+ *
+ * @param holder 添加的ViewHolder
+ * @param animator 添加的ViewHolder对应动画对象
+ */
+ public abstract void setAddItemAnimation(ViewHolder holder, ViewPropertyAnimator animator);
+
+ /**
+ * 设置取消添加item动画,还原状态以复用
+ *
+ * @param holder 添加的ViewHolder
+ */
+ public abstract void setAddItemAnimationCancel(ViewHolder holder);
+
+
+ /**
+ * 设置item移除动画
+ *
+ * @param holder 添加的ViewHolder
+ * @param animator 添加的ViewHolder对应动画对象
+ */
+ public abstract void setRemoveAnimation(ViewHolder holder, ViewPropertyAnimator animator);
+
+
+ /**
+ * 设置结束移除item动画,还原状态以复用
+ *
+ * @param holder 添加的ViewHolder
+ */
+ public abstract void setRemoveAnimationEnd(ViewHolder holder);
+
+
+ public abstract void setOldChangeAnimation(ViewHolder holder, ViewPropertyAnimator animator);
+
+ public abstract void setOldChangeAnimationEnd(ViewHolder holder);
+
+ public abstract void setNewChangeAnimationInit(ViewHolder holder);
+
+ public abstract void setNewChangeAnimation(ViewHolder holder, ViewPropertyAnimator animator);
+
+ public abstract void setNewChangeAnimationEnd(ViewHolder holder);
+
+
+}
diff --git a/app/src/main/java/com/allen/androidcustomview/anim/RotateXItemAnimation.kt b/app/src/main/java/com/allen/androidcustomview/anim/RotateXItemAnimation.kt
new file mode 100644
index 0000000..91a1d75
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/anim/RotateXItemAnimation.kt
@@ -0,0 +1,41 @@
+package com.allen.androidcustomview.anim
+
+import android.support.v7.widget.RecyclerView
+import android.view.ViewPropertyAnimator
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019/05/15
+ * desc :
+ *
+ */
+class RotateXItemAnimation(animDuration: Long = 500) : SuperItemAnimation(animDuration) {
+
+ init {
+ addDuration = animDuration
+ removeDuration = animDuration
+ }
+
+ override fun setAddItemAnimInit(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.rotationX = -90f
+ }
+
+ override fun setAddItemAnim(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ animator?.rotationX(0f)
+ }
+
+ override fun setAddItemAnimCancel(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.rotationX = 0f
+ }
+
+ override fun setRemoveItemAnim(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ animator?.rotationX(-90f)
+ }
+
+ override fun setRemoveItemAnimEnd(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.rotationX = 0f
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/anim/RotateYItemAnimation.kt b/app/src/main/java/com/allen/androidcustomview/anim/RotateYItemAnimation.kt
new file mode 100644
index 0000000..429400a
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/anim/RotateYItemAnimation.kt
@@ -0,0 +1,36 @@
+package com.allen.androidcustomview.anim
+
+import android.support.v7.widget.RecyclerView
+import android.view.ViewPropertyAnimator
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019/05/15
+ * desc :
+ *
+ */
+class RotateYItemAnimation(animDuration: Long = 500) : SuperItemAnimation(animDuration) {
+
+ override fun setAddItemAnimInit(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.rotationY = -90f
+ }
+
+ override fun setAddItemAnim(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ animator?.rotationY(0f)
+ }
+
+ override fun setAddItemAnimCancel(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.rotationY = 0f
+ }
+
+ override fun setRemoveItemAnim(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ animator?.rotationY(-90f)
+ }
+
+ override fun setRemoveItemAnimEnd(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.rotationY = 0f
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/anim/ScaleItemAnimation.kt b/app/src/main/java/com/allen/androidcustomview/anim/ScaleItemAnimation.kt
new file mode 100644
index 0000000..dcf47ef
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/anim/ScaleItemAnimation.kt
@@ -0,0 +1,43 @@
+package com.allen.androidcustomview.anim
+
+import android.support.v7.widget.RecyclerView
+import android.view.ViewPropertyAnimator
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019/05/15
+ * desc :
+ *
+ */
+class ScaleItemAnimation(animDuration: Long = 500) : SuperItemAnimation(animDuration) {
+
+ override fun setAddItemAnimInit(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.scaleX = 0f
+ holder?.itemView?.scaleY = 0f
+ }
+
+ override fun setAddItemAnim(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ holder?.itemView?.pivotX = 0f
+ holder?.itemView?.pivotY = 0f
+ animator?.scaleX(1f)?.scaleY(1f)
+ }
+
+ override fun setAddItemAnimCancel(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.scaleX = 1f
+ holder?.itemView?.scaleY = 1f
+ }
+
+ override fun setRemoveItemAnim(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ holder?.itemView?.pivotX = 1f
+ holder?.itemView?.pivotY = 1f
+ animator?.scaleX(0f)?.scaleY(0f)
+ }
+
+ override fun setRemoveItemAnimEnd(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.scaleX = 1f
+ holder?.itemView?.scaleY = 1f
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/anim/SlideItemAnimation.kt b/app/src/main/java/com/allen/androidcustomview/anim/SlideItemAnimation.kt
new file mode 100644
index 0000000..ee63641
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/anim/SlideItemAnimation.kt
@@ -0,0 +1,38 @@
+package com.allen.androidcustomview.anim
+
+import android.support.v7.widget.RecyclerView
+import android.view.ViewPropertyAnimator
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019/05/15
+ * desc :
+ *
+ */
+class SlideItemAnimation(animDuration: Long = 500) : SuperItemAnimation(animDuration) {
+
+ override fun setAddItemAnimInit(holder: RecyclerView.ViewHolder?) {
+ val with = holder?.itemView?.width ?: 0
+ holder?.itemView?.translationX = -with.toFloat()
+ }
+
+ override fun setAddItemAnim(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ animator?.translationX(0f)
+ }
+
+ override fun setAddItemAnimCancel(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.translationX = 0f
+ }
+
+ override fun setRemoveItemAnim(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ val with = holder?.itemView?.width ?: 0
+ animator?.translationX(-with.toFloat())
+ }
+
+ override fun setRemoveItemAnimEnd(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.translationX = 0f
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/anim/SuperItemAnimation.kt b/app/src/main/java/com/allen/androidcustomview/anim/SuperItemAnimation.kt
new file mode 100644
index 0000000..a177c98
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/anim/SuperItemAnimation.kt
@@ -0,0 +1,69 @@
+package com.allen.androidcustomview.anim
+
+import android.support.v7.widget.RecyclerView
+import android.view.ViewPropertyAnimator
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019/05/15
+ * desc :
+ *
+ */
+abstract class SuperItemAnimation(animDuration: Long = 200) : BaseItemAnimation() {
+
+ init {
+ addDuration = animDuration
+ removeDuration = animDuration
+ }
+
+ abstract fun setAddItemAnimInit(holder: RecyclerView.ViewHolder?)
+ abstract fun setAddItemAnim(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?)
+ abstract fun setAddItemAnimCancel(holder: RecyclerView.ViewHolder?)
+
+ abstract fun setRemoveItemAnim(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?)
+ abstract fun setRemoveItemAnimEnd(holder: RecyclerView.ViewHolder?)
+
+
+ override fun setAddItemAnimationInit(holder: RecyclerView.ViewHolder?) {
+ setAddItemAnimInit(holder)
+ }
+
+ override fun setAddItemAnimation(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ setAddItemAnim(holder, animator)
+ }
+
+ override fun setAddItemAnimationCancel(holder: RecyclerView.ViewHolder?) {
+ setAddItemAnimCancel(holder)
+ }
+
+ override fun setRemoveAnimation(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ setRemoveItemAnim(holder, animator)
+ }
+
+ override fun setRemoveAnimationEnd(holder: RecyclerView.ViewHolder?) {
+ setRemoveItemAnimEnd(holder)
+ }
+
+
+ override fun setOldChangeAnimation(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ animator?.alpha(0f)
+ }
+
+ override fun setOldChangeAnimationEnd(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.alpha = 1f
+ }
+
+ override fun setNewChangeAnimationInit(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.alpha = 0f
+ }
+
+ override fun setNewChangeAnimation(holder: RecyclerView.ViewHolder?, animator: ViewPropertyAnimator?) {
+ animator?.alpha(1f)
+ }
+
+ override fun setNewChangeAnimationEnd(holder: RecyclerView.ViewHolder?) {
+ holder?.itemView?.alpha = 1f
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/bean/TypeBean.java b/app/src/main/java/com/allen/androidcustomview/bean/TypeBean.java
new file mode 100644
index 0000000..a67554b
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/bean/TypeBean.java
@@ -0,0 +1,37 @@
+package com.allen.androidcustomview.bean;
+
+/**
+ *
+ * @author : xiaoyao
+ * e-mail : xiaoyao@51vest.com
+ * date : 2018/05/14
+ * desc :
+ * version : 1.0
+ *
+ */
+
+public class TypeBean {
+ private String title;
+ private int type;
+
+ public TypeBean(String title, int type) {
+ this.title = title;
+ this.type = type;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+}
diff --git a/app/src/main/java/com/allen/androidcustomview/bean/VoteBean.kt b/app/src/main/java/com/allen/androidcustomview/bean/VoteBean.kt
new file mode 100644
index 0000000..961f14c
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/bean/VoteBean.kt
@@ -0,0 +1,25 @@
+package com.allen.androidcustomview.bean
+
+
+/**
+ *
+ * @author : Allen
+ * date : 2019/08/01
+ * desc :
+ *
+ */
+
+class VoteBean(val id: Int = 0,
+ val title: String?,
+ val choiceType: String?,
+ val maxSelect: Int?,
+ var voted: Boolean?,
+ val sumVoteCount: Int?,
+ val options: ArrayList?
+)
+
+data class VoteOption(var id: Int?,
+ var content: String?,
+ var voteId: Int?,
+ var showCount: Int?,
+ var voted: Boolean?)
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/data/VoteData.kt b/app/src/main/java/com/allen/androidcustomview/data/VoteData.kt
new file mode 100644
index 0000000..67d4b9c
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/data/VoteData.kt
@@ -0,0 +1,49 @@
+package com.allen.androidcustomview.data
+
+import com.allen.androidcustomview.bean.VoteBean
+import com.allen.androidcustomview.bean.VoteOption
+import kotlin.random.Random
+
+/**
+ *
+ * @author : Allen
+ * date : 2019/08/06
+ * desc :
+ *
+ */
+
+fun getMockData(): ArrayList {
+ val list: ArrayList = arrayListOf()
+ for (i in 0..1)
+ list.add(getVoteBeanData(i))
+ return list
+}
+
+private fun getVoteBeanData(index: Int): VoteBean {
+ val voteTitle: String = when (index) {
+ 0 -> "哪吒票房能否突破30亿(多选)"
+ 1 -> "你觉得谁最火呢?(单选)"
+ else -> ""
+ }
+ return VoteBean(11, voteTitle, if (index == 0) "multiple" else "single", 2, false, Random.nextInt(10000, 20000), getVoteOptionsDatas(index))
+}
+
+private fun getVoteOptionsDatas(index: Int): java.util.ArrayList? {
+ var list: ArrayList = 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/helper/DragViewHelper.kt b/app/src/main/java/com/allen/androidcustomview/helper/DragViewHelper.kt
new file mode 100644
index 0000000..6022b9d
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/helper/DragViewHelper.kt
@@ -0,0 +1,69 @@
+package com.allen.androidcustomview.helper
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import com.allen.androidcustomview.R
+import com.allen.androidcustomview.listener.OnDragTouchListener
+import com.allen.androidcustomview.utils.DisplayUtils
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019/03/14
+ * desc : 拖拽view辅助类
+ *
+ */
+object DragViewHelper {
+
+ @SuppressLint("ClickableViewAccessibility")
+ fun addDragView(context: Context,
+ parent: ViewGroup,
+ imgUrl: String,
+ defaultImgResId: Int = R.mipmap.ic_launcher,
+ dragViewSize: Float = 60f,
+ dragViewOriginalMarginRight: Float = 20f,
+ dragViewOriginalMarginBottom: Float = 20f,
+ autoPullToBorder: Boolean = true,
+ onClick: (() -> Unit)? = null): ImageView {
+ val dragView = ImageView(context)
+ parent.post {
+ val onDragTouchListener = OnDragTouchListener()
+ onDragTouchListener.clickListener = {
+ onClick?.invoke()
+ }
+ onDragTouchListener.mMaxWidth = parent.width
+ onDragTouchListener.mMaxHeight = parent.height
+ onDragTouchListener.mBorderMargin = DisplayUtils.dip2px(context, 15f).toFloat()
+ onDragTouchListener.mIsAutoToBorder = autoPullToBorder
+ dragView.scaleType = ImageView.ScaleType.CENTER_CROP
+ dragView.setOnTouchListener(onDragTouchListener)
+ val layoutParams = RelativeLayout.LayoutParams(DisplayUtils.dip2px(context, dragViewSize), DisplayUtils.dip2px(context, dragViewSize))
+ layoutParams.leftMargin = parent.width - DisplayUtils.dip2px(context, dragViewSize + dragViewOriginalMarginRight)
+ layoutParams.topMargin = parent.height - DisplayUtils.dip2px(context, dragViewSize + dragViewOriginalMarginBottom)
+// GlideApp.with(context).load(imgUrl).into(dragView)
+ dragView.setBackgroundResource(defaultImgResId)
+ parent.addView(dragView, layoutParams)
+ }
+
+ return dragView
+ }
+
+ fun removeDragView(parent: ViewGroup, view: View?) {
+ if (view != null) {
+ parent.removeView(view)
+ }
+ }
+
+ fun updateDragView(context: Context, dragView: ImageView?, imgUrl: String) {
+ if (dragView != null) {
+// GlideApp.with(context).load(imgUrl).into(dragView)
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/listener/OnDragTouchListener.kt b/app/src/main/java/com/allen/androidcustomview/listener/OnDragTouchListener.kt
new file mode 100644
index 0000000..587a745
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/listener/OnDragTouchListener.kt
@@ -0,0 +1,196 @@
+package com.allen.androidcustomview.listener
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.DecelerateInterpolator
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019/03/14
+ * desc :
+ *
+ */
+class OnDragTouchListener : View.OnTouchListener {
+
+ //手指按下时的初始位置
+ private var mOriginalX: Float = 0f
+ private var mOriginalY: Float = 0f
+ //记录手指与view的左上角的距离
+ private var mDistanceX: Float = 0f
+ private var mDistanceY: Float = 0f
+ //拖拽view的上下左右距离
+ private var left: Int = 0
+ private var top: Int = 0
+ private var right: Int = 0
+ private var bottom: Int = 0
+ //最小的拖拽距离,小于这个值认为不是拖拽,区分是点击还是拖拽
+ private var minDragDistance = 10f
+ private var mLayoutParams: ViewGroup.MarginLayoutParams? = null
+
+ //可拖拽屏幕区域宽高
+ var mMaxWidth: Int = 0
+ var mMaxHeight: Int = 0
+
+ //点击事件
+ var clickListener: (() -> Unit)? = null
+
+ //标记是否自动吸附到边缘
+ var mIsAutoToBorder = true
+ //吸附在边缘时候距离边界的距离
+ var mBorderMargin = 0f
+
+ var mBorderMarginLeft = -1f
+ var mBorderMarginRight = -1f
+ var mBorderMarginTop = -1f
+ var mBorderMarginBottom = -1f
+
+ override fun onTouch(view: View, event: MotionEvent): Boolean {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ view.parent.requestDisallowInterceptTouchEvent(true)
+
+ mLayoutParams = mLayoutParams ?: view.layoutParams as ViewGroup.MarginLayoutParams
+
+ mOriginalX = event.rawX
+ mOriginalY = event.rawY
+
+ mDistanceX = event.rawX - view.left
+ mDistanceY = event.rawY - view.top
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ left = (event.rawX - mDistanceX).toInt()
+ top = (event.rawY - mDistanceY).toInt()
+ right = left + view.width
+ bottom = top + view.height
+ if (left < 0) {
+ left = 0
+ right = left + view.width
+ }
+ if (top < 0) {
+ top = 0
+ bottom = top + view.height
+ }
+ if (right > mMaxWidth) {
+ right = mMaxWidth
+ left = right - view.width
+ }
+ if (bottom > mMaxHeight) {
+ bottom = mMaxHeight
+ top = bottom - view.height
+ }
+
+ //如果其他view刷新导致重绘会调用layout方法,导致位置一闪一闪的,所有要用layoutParams设置位置
+ //view.layout(left, top, right, bottom)
+ mLayoutParams?.setMargins(left, top, 0, 0)
+ view.layoutParams = mLayoutParams
+ }
+ MotionEvent.ACTION_UP -> {
+ //如果移动距离过小,则判定为点击
+ if (Math.abs(event.rawX - mOriginalX) < minDragDistance && Math.abs(event.rawY - mOriginalY) < minDragDistance) {
+ clickListener?.invoke()
+ } else {
+ setAutoToBorder(view)
+ }
+ view.parent.requestDisallowInterceptTouchEvent(false)
+ //调取performClick()方法消除警告OnDragTouchListener#onTouch should call View#performClick when a click is detected more...
+ view.performClick()
+ }
+ }
+ return true
+ }
+
+
+ /**
+ * 开启自动拖拽
+ *
+ * @param v 拉动控件
+ */
+ private fun setAutoToBorder(v: View) {
+ if (!mIsAutoToBorder) return
+ setAnimation(v)
+ }
+
+ private fun setAnimation(v: View) {
+ val animatorSet = AnimatorSet()
+ if (getTopOrBottomAnimation(v) == null) {
+ animatorSet.play(getLeftOrRightAnimation(v))
+ } else {
+ animatorSet.play(getLeftOrRightAnimation(v)).with(getTopOrBottomAnimation(v))
+ }
+ animatorSet.duration = 300
+ animatorSet.start()
+ }
+
+
+ /**
+ * 获取吸附在左右边界的动画
+ */
+ private fun getLeftOrRightAnimation(v: View): Animator {
+ //当用户拖拽完后,让控件回到最近的边缘
+ var leftOrRightEnd = getBorderMargin(mBorderMarginLeft)
+ //吸附在右边边界处
+ if (left + v.width / 2 >= mMaxWidth / 2) {
+ leftOrRightEnd = (mMaxWidth - v.width - getBorderMargin(mBorderMarginRight))
+ }
+ val animator = ValueAnimator.ofFloat(left.toFloat(), leftOrRightEnd)
+ animator.interpolator = DecelerateInterpolator()
+ animator.addUpdateListener { animation ->
+ val leftMargin = (animation.animatedValue as Float).toInt()
+ mLayoutParams?.leftMargin = leftMargin
+ v.layoutParams = mLayoutParams
+ }
+ return animator
+ }
+
+ /**
+ * 获取吸附在顶部或者底部的动画
+ */
+ private fun getTopOrBottomAnimation(v: View): Animator? {
+ //吸附在上下边界处
+ var topOrBottomEnd: Float
+ //吸附在下边界处
+ return when {
+ //吸附到距离底部mBorderMargin的距离的位置
+ top + v.height >= mMaxHeight - mBorderMargin -> {
+ topOrBottomEnd = mMaxHeight - v.height - getBorderMargin(mBorderMarginBottom)
+ createTopOrBottomAnimation(v, topOrBottomEnd)
+ }
+ //吸附到距离顶部mBorderMargin的距离的位置
+ top <= mBorderMargin -> {
+ topOrBottomEnd = getBorderMargin(mBorderMarginTop)
+ createTopOrBottomAnimation(v, topOrBottomEnd)
+ }
+ else -> null
+ }
+ }
+
+
+ /**
+ * 创建底吸附到底部或顶部的动画
+ */
+ private fun createTopOrBottomAnimation(view: View, end: Float): Animator? {
+ val animator = ValueAnimator.ofFloat(top.toFloat(), end)
+ animator.interpolator = DecelerateInterpolator()
+ animator.addUpdateListener { animation ->
+ val topMargin = (animation.animatedValue as Float).toInt()
+ mLayoutParams?.topMargin = topMargin
+ view.layoutParams = mLayoutParams
+ }
+ return animator
+ }
+
+ /**
+ * 获取吸附的边界值
+ */
+ private fun getBorderMargin(margin: Float): Float {
+ return if (margin != -1f) margin else mBorderMargin
+ }
+
+}
\ 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/DividerItemDecoration.java b/app/src/main/java/com/allen/androidcustomview/widget/DividerItemDecoration.java
new file mode 100644
index 0000000..040e988
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/DividerItemDecoration.java
@@ -0,0 +1,117 @@
+package com.allen.androidcustomview.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/**
+ *
+ * @author : xiaoyao
+ * e-mail : xiaoyao@51vest.com
+ * date : 2018/02/05
+ * desc : recyclerview的分割线
+ * version : 1.0
+ *
+ */
+
+public class DividerItemDecoration extends RecyclerView.ItemDecoration {
+ private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
+
+ public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
+
+ public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
+
+
+ private Drawable mDivider;
+
+ private int mOrientation;
+
+ private Context context;
+
+ public DividerItemDecoration(Context context, int orientation) {
+ final TypedArray a = context.obtainStyledAttributes(ATTRS);
+ mDivider = a.getDrawable(0);
+ this.context = context;
+ a.recycle();
+ setOrientation(orientation);
+ }
+
+ public void setOrientation(int orientation) {
+ if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
+ throw new IllegalArgumentException("invalid orientation");
+ }
+ mOrientation = orientation;
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ super.onDraw(c, parent, state);
+ if (mOrientation == VERTICAL_LIST) {
+ drawVertical(c, parent);
+ } else {
+ drawHorizontal(c, parent);
+ }
+ }
+
+ public void drawVertical(Canvas c, RecyclerView parent) {
+ final int left = parent.getPaddingLeft() + dip2px(context, 0);
+ final int right = parent.getWidth() - parent.getPaddingRight() - dip2px(context, 0);
+
+ final int childCount = parent.getChildCount();
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(
+ parent.getContext());
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ final int top = child.getBottom() + params.bottomMargin;
+ final int bottom = top + mDivider.getIntrinsicHeight();
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+
+ public void drawHorizontal(Canvas c, RecyclerView parent) {
+ final int top = parent.getPaddingTop();
+ final int bottom = parent.getHeight() - parent.getPaddingBottom();
+
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ final int left = child.getRight() + params.rightMargin;
+ final int right = left + mDivider.getIntrinsicHeight();
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+ super.getItemOffsets(outRect, view, parent, state);
+ if (mOrientation == VERTICAL_LIST) {
+ outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
+ } else {
+ outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
+ }
+ }
+
+ /**
+ * 单位转换工具类
+ *
+ * @param context 上下文对象
+ * @param dipValue 值
+ * @return 返回值
+ */
+ private int dip2px(Context context, float dipValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dipValue * scale + 0.5f);
+ }
+}
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/HorizontalProgressBar.java b/app/src/main/java/com/allen/androidcustomview/widget/HorizontalProgressBar.java
index e418c2a..e18d347 100644
--- a/app/src/main/java/com/allen/androidcustomview/widget/HorizontalProgressBar.java
+++ b/app/src/main/java/com/allen/androidcustomview/widget/HorizontalProgressBar.java
@@ -345,6 +345,7 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) {
moveDis = currentProgress - tipWidth / 2;
}
invalidate();
+ setCurrentProgress(value);
}
});
progressAnimator.start();
@@ -373,6 +374,15 @@ public HorizontalProgressBar setCurrentProgress(float progress) {
mProgress = progress;
currentProgress = progress * mWidth / 100;
textString = formatNum(format2Int(progress));
+
+ //移动百分比提示框,只有当前进度到提示框中间位置之后开始移动,
+ //当进度框移动到最右边的时候停止移动,但是进度条还可以继续移动
+ //moveDis是tip框移动的距离
+ if (currentProgress >= (tipWidth / 2) &&
+ currentProgress <= (mWidth - tipWidth / 2)) {
+ moveDis = currentProgress - tipWidth / 2;
+ }
+
invalidate();
return this;
}
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/HoverItemDecoration.java b/app/src/main/java/com/allen/androidcustomview/widget/HoverItemDecoration.java
index 1497702..46570b2 100644
--- a/app/src/main/java/com/allen/androidcustomview/widget/HoverItemDecoration.java
+++ b/app/src/main/java/com/allen/androidcustomview/widget/HoverItemDecoration.java
@@ -152,6 +152,8 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle
//如果是分组第一个就留出绘制item的高度
if (isFirstInGroup(position)) {
outRect.top = itemHeight;
+ }else {
+ outRect.top = itemDivideHeight;
}
}
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/SuperDividerItemDecoration.java b/app/src/main/java/com/allen/androidcustomview/widget/SuperDividerItemDecoration.java
new file mode 100644
index 0000000..a7ae5ca
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/SuperDividerItemDecoration.java
@@ -0,0 +1,255 @@
+package com.allen.androidcustomview.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.v7.widget.RecyclerView;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.LinearLayout;
+
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2018/04/15
+ * desc : 万能分割线
+ * version : 1.0
+ *
+ */
+public class SuperDividerItemDecoration extends RecyclerView.ItemDecoration {
+
+
+ public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
+ public static final int VERTICAL = LinearLayout.VERTICAL;
+
+ private static Context context;
+
+
+ /**
+ * 默认分割线的颜色
+ */
+ private int dividerDefaultColor = 0xFFE1E5E8;
+
+ /**
+ * 分割线的颜色
+ */
+ private int dividerColor;
+ /**
+ * 分割线的宽度
+ */
+ private int dividerWidth;
+ /**
+ * 分割线距离左右两边的距离
+ */
+ private int dividerPadding;
+ /**
+ * 分割线距离左边的距离
+ */
+ private int dividerPaddingLeft;
+ /**
+ * 分割线距离右边的距离
+ */
+ private int dividerPaddingRight;
+
+ /**
+ * 分割线距离上边的距离
+ */
+ private int dividerPaddingTop;
+ /**
+ * 分割线距离下边的距离
+ */
+ private int dividerPaddingBottom;
+ /**
+ * 是否显示列表最后一条分割线
+ */
+ private boolean dividerIsShowLastDivide;
+
+
+ /**
+ * 分割线item的画笔
+ */
+ private Paint dividerPaint;
+
+ /**
+ * 分割线开始的位置(解决recyclerView添加头布局的时候,要从header下边的position位置算起)
+ */
+ private int dividerFromPosition = 0;
+
+
+ /**
+ * recyclerView布局方式(水平或者垂直)
+ */
+ private int orientation;
+
+
+ public SuperDividerItemDecoration(Builder builder) {
+
+ context = builder.context;
+
+ dividerColor = builder.dividerColor == 0 ? dividerDefaultColor : builder.dividerColor;
+ dividerPadding = dp2px(builder.dividerPadding);
+ dividerPaddingLeft = dp2px(builder.dividerPaddingLeft);
+ dividerPaddingRight = dp2px(builder.dividerPaddingRight);
+ dividerPaddingTop = dp2px(builder.dividerPaddingTop);
+ dividerPaddingBottom = dp2px(builder.dividerPaddingBottom);
+ dividerWidth = builder.dividerWidth == 0 ? dp2px(0.5f) : dp2px(builder.dividerWidth);
+ dividerFromPosition = builder.dividerFromPosition;
+ dividerIsShowLastDivide = builder.dividerIsShowLastDivide;
+ orientation = builder.orientation;
+ dividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ dividerPaint.setColor(dividerColor);
+
+ if (dividerPadding != 0) {
+ dividerPaddingLeft = dividerPaddingRight = dividerPadding;
+ dividerPaddingTop = dividerPaddingBottom = dividerPadding;
+ }
+ }
+
+ public static class Builder {
+
+ private Context context;
+ private int dividerColor;
+ private int dividerWidth;
+ private int dividerPadding;
+ private int dividerPaddingLeft;
+ private int dividerPaddingRight;
+ private int dividerPaddingTop;
+ private int dividerPaddingBottom;
+ private int dividerFromPosition;
+ private boolean dividerIsShowLastDivide;
+ private int orientation = VERTICAL;
+
+ public Builder(Context context) {
+ this.context = context;
+ }
+
+ public Builder setDividerColor(int dividerColor) {
+ this.dividerColor = dividerColor;
+ return this;
+ }
+
+ public Builder setDividerWidth(int dividerWidth) {
+ this.dividerWidth = dividerWidth;
+ return this;
+ }
+
+ public Builder setDividerPadding(int dividerPadding) {
+ this.dividerPadding = dividerPadding;
+ return this;
+ }
+
+ public Builder setDividerPaddingLeft(int dividerPaddingLeft) {
+ this.dividerPaddingLeft = dividerPaddingLeft;
+ return this;
+ }
+
+ public Builder setDividerPaddingRight(int dividerPaddingRight) {
+ this.dividerPaddingRight = dividerPaddingRight;
+ return this;
+ }
+
+ public Builder setDividerPaddingTop(int dividerPaddingTop) {
+ this.dividerPaddingTop = dividerPaddingTop;
+ return this;
+ }
+
+ public Builder setDividerPaddingBottom(int dividerPaddingBottom) {
+ this.dividerPaddingBottom = dividerPaddingBottom;
+ return this;
+ }
+
+ public Builder setDividerFromPosition(int dividerFromPosition) {
+ this.dividerFromPosition = dividerFromPosition;
+ return this;
+ }
+
+ public Builder setIsShowLastDivide(boolean dividerIsShowLastDivide) {
+ this.dividerIsShowLastDivide = dividerIsShowLastDivide;
+ return this;
+ }
+
+ public Builder setOrientation(int orientation) {
+ this.orientation = orientation;
+ return this;
+ }
+
+ public SuperDividerItemDecoration build() {
+ return new SuperDividerItemDecoration(this);
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ super.onDraw(c, parent, state);
+
+ if (orientation == VERTICAL) {
+ drawVertical(c, parent);
+ } else {
+ drawHorizontal(c, parent);
+ }
+
+ }
+
+
+ private void drawVertical(Canvas c, RecyclerView parent) {
+ int count = parent.getChildCount();
+
+ if (count > 0) {
+ int showCount = dividerIsShowLastDivide ? count : count - 1;
+ for (int i = dividerFromPosition; i < showCount; i++) {
+ View view = parent.getChildAt(i);
+ //可见item的底部
+ int itemBottom = view.getBottom();
+
+ c.drawRect(parent.getPaddingLeft() + dividerPaddingLeft,
+ itemBottom,
+ parent.getWidth() - parent.getPaddingRight() - dividerPaddingRight,
+ itemBottom + dividerWidth,
+ dividerPaint);
+ }
+ }
+ }
+
+ private void drawHorizontal(Canvas c, RecyclerView parent) {
+ int count = parent.getChildCount();
+ if (count > 0) {
+ int showCount = dividerIsShowLastDivide ? count : count - 1;
+ for (int i = dividerFromPosition; i < showCount; i++) {
+ View view = parent.getChildAt(i);
+ //可见item的底部
+ int itemRight = view.getRight();
+
+ c.drawRect(itemRight,
+ parent.getPaddingTop() + dividerPaddingTop,
+ itemRight + dividerWidth,
+ parent.getHeight() - parent.getPaddingBottom() - dividerPaddingBottom,
+ dividerPaint);
+ }
+ }
+ }
+
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+ super.getItemOffsets(outRect, view, parent, state);
+ if (orientation == VERTICAL) {
+ outRect.bottom = dividerWidth;
+ } else {
+ outRect.right = dividerWidth;
+ }
+ }
+
+ /**
+ * dp 2 px
+ *
+ * @param dpVal
+ */
+ public static int dp2px(float dpVal) {
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ dpVal, context.getResources().getDisplayMetrics());
+ }
+
+}
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/status/StatusBuilder.kt b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusBuilder.kt
new file mode 100644
index 0000000..a036c92
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusBuilder.kt
@@ -0,0 +1,50 @@
+package com.allen.androidcustomview.widget.status
+
+import android.view.View
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019/03/19
+ * desc :
+ *
+ */
+class StatusBuilder {
+
+ class Builder{
+
+ var contentView: View? = null
+ var errorView: View? = null
+ var emptyView: View? = null
+ var loadingView: View? = null
+
+ fun setContentView(contentView: View):Builder {
+ this.contentView = contentView
+ return this
+ }
+ fun setContentView(layoutResId: Int):Builder {
+ this.contentView = contentView
+ return this
+ }
+
+ fun setErrorView(errorView: View):Builder {
+ this.errorView = errorView
+ return this
+ }
+
+ fun setEmptyView(emptyView: View):Builder {
+ this.emptyView = emptyView
+ return this
+ }
+
+ fun setLoadingView(loadingView: View):Builder {
+ this.loadingView = loadingView
+ return this
+ }
+
+ fun build(){
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/status/StatusLayout.kt b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusLayout.kt
new file mode 100644
index 0000000..470b9bc
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusLayout.kt
@@ -0,0 +1,27 @@
+package com.allen.androidcustomview.widget.status
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019/03/19
+ * desc : 状态布局
+ *
+ */
+
+class StatusLayout : FrameLayout {
+
+ 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) {
+ initAttrs(attrs)
+ }
+
+ private fun initAttrs(attrs: AttributeSet?) {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/allen/androidcustomview/widget/status/StatusManager.kt b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusManager.kt
new file mode 100644
index 0000000..383baf7
--- /dev/null
+++ b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusManager.kt
@@ -0,0 +1,29 @@
+package com.allen.androidcustomview.widget.status
+
+/**
+ *
+ * @author : Allen
+ * e-mail : lygttpod@163.com
+ * date : 2019/03/19
+ * desc :
+ *
+ */
+object StatusManager {
+
+
+ fun setEmptyView(){
+
+ }
+
+ fun setErrorView(){
+
+ }
+
+ fun setContentView() {
+
+ }
+
+ fun setLoadingView() {
+
+ }
+}
\ 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_circle_bg.xml b/app/src/main/res/drawable/shape_circle_bg.xml
new file mode 100644
index 0000000..5c92444
--- /dev/null
+++ b/app/src/main/res/drawable/shape_circle_bg.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_item_bg.xml b/app/src/main/res/drawable/shape_item_bg.xml
new file mode 100644
index 0000000..a15600f
--- /dev/null
+++ b/app/src/main/res/drawable/shape_item_bg.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_search.xml b/app/src/main/res/drawable/shape_search.xml
new file mode 100644
index 0000000..b45f287
--- /dev/null
+++ b/app/src/main/res/drawable/shape_search.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ 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_alipay_home.xml b/app/src/main/res/layout/activity_alipay_home.xml
new file mode 100644
index 0000000..ff55fd7
--- /dev/null
+++ b/app/src/main/res/layout/activity_alipay_home.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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_main.xml b/app/src/main/res/layout/activity_main.xml
index 78286ee..0df5fab 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,82 +1,13 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="match_parent" />
+
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_pay_psd_view.xml b/app/src/main/res/layout/activity_pay_psd_view.xml
index 01a8df9..d3481b0 100644
--- a/app/src/main/res/layout/activity_pay_psd_view.xml
+++ b/app/src/main/res/layout/activity_pay_psd_view.xml
@@ -5,14 +5,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
+ android:paddingTop="50dp"
tools:context="com.allen.androidcustomview.activity.PayPsdViewActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_reveal_animation.xml b/app/src/main/res/layout/activity_reveal_animation.xml
new file mode 100644
index 0000000..63ba912
--- /dev/null
+++ b/app/src/main/res/layout/activity_reveal_animation.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_sina_vote.xml b/app/src/main/res/layout/activity_sina_vote.xml
new file mode 100644
index 0000000..cb60371
--- /dev/null
+++ b/app/src/main/res/layout/activity_sina_vote.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_item_hover_user.xml b/app/src/main/res/layout/adapter_item_hover_user.xml
index 93e9bfa..1054cef 100644
--- a/app/src/main/res/layout/adapter_item_hover_user.xml
+++ b/app/src/main/res/layout/adapter_item_hover_user.xml
@@ -11,4 +11,5 @@
android:layout_height="50dp"
android:background="@android:color/white"
android:paddingLeft="20dp" />
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_item_main.xml b/app/src/main/res/layout/adapter_item_main.xml
new file mode 100644
index 0000000..eac4730
--- /dev/null
+++ b/app/src/main/res/layout/adapter_item_main.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/include_default_layout.xml b/app/src/main/res/layout/include_default_layout.xml
new file mode 100644
index 0000000..f5eb9ea
--- /dev/null
+++ b/app/src/main/res/layout/include_default_layout.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/include_toolbar_close.xml b/app/src/main/res/layout/include_toolbar_close.xml
new file mode 100644
index 0000000..1348361
--- /dev/null
+++ b/app/src/main/res/layout/include_toolbar_close.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/include_toolbar_open.xml b/app/src/main/res/layout/include_toolbar_open.xml
new file mode 100644
index 0000000..2703831
--- /dev/null
+++ b/app/src/main/res/layout/include_toolbar_open.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_danmu_layout.xml b/app/src/main/res/layout/item_danmu_layout.xml
new file mode 100644
index 0000000..0906697
--- /dev/null
+++ b/app/src/main/res/layout/item_danmu_layout.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget_vote_layout.xml b/app/src/main/res/layout/widget_vote_layout.xml
new file mode 100644
index 0000000..38cef29
--- /dev/null
+++ b/app/src/main/res/layout/widget_vote_layout.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
index cde69bc..3c9de7c 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
index c133a0c..9df99a2 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/icon_study_progress_completed.png b/app/src/main/res/mipmap-mdpi/icon_study_progress_completed.png
new file mode 100755
index 0000000..cb766a8
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icon_study_progress_completed.png differ
diff --git a/app/src/main/res/mipmap-mdpi/icon_study_progress_not_check.png b/app/src/main/res/mipmap-mdpi/icon_study_progress_not_check.png
new file mode 100755
index 0000000..0d68f56
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/icon_study_progress_not_check.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index bfa42f0..250a0ac 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/alipay_home_bg.jpg b/app/src/main/res/mipmap-xxhdpi/alipay_home_bg.jpg
new file mode 100644
index 0000000..50e6bcf
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/alipay_home_bg.jpg differ
diff --git a/app/src/main/res/mipmap-xxhdpi/car.png b/app/src/main/res/mipmap-xxhdpi/car.png
new file mode 100644
index 0000000..2b1fc47
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/car.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_add.png b/app/src/main/res/mipmap-xxhdpi/ic_add.png
new file mode 100644
index 0000000..4d563a8
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_add.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_camera_3x.png b/app/src/main/res/mipmap-xxhdpi/ic_camera_3x.png
new file mode 100644
index 0000000..e5d04a1
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_camera_3x.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_card.png b/app/src/main/res/mipmap-xxhdpi/ic_card.png
new file mode 100644
index 0000000..626fa18
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_card.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_contact.png b/app/src/main/res/mipmap-xxhdpi/ic_contact.png
new file mode 100644
index 0000000..802747b
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_contact.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 324e72c..8bcfa9d 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_payment.png b/app/src/main/res/mipmap-xxhdpi/ic_payment.png
new file mode 100644
index 0000000..7b6b4cc
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_payment.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_scan.png b/app/src/main/res/mipmap-xxhdpi/ic_scan.png
new file mode 100644
index 0000000..7be7b64
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_scan.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_search.png b/app/src/main/res/mipmap-xxhdpi/ic_search.png
new file mode 100644
index 0000000..02c7f68
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_search.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_transfer.png b/app/src/main/res/mipmap-xxhdpi/ic_transfer.png
new file mode 100644
index 0000000..db41da5
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_transfer.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/icon_img1.png b/app/src/main/res/mipmap-xxhdpi/icon_img1.png
new file mode 100644
index 0000000..64f74aa
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_img1.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/icon_img2.jpg b/app/src/main/res/mipmap-xxhdpi/icon_img2.jpg
new file mode 100644
index 0000000..663ce32
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_img2.jpg differ
diff --git a/app/src/main/res/mipmap-xxhdpi/icon_vote_check.png b/app/src/main/res/mipmap-xxhdpi/icon_vote_check.png
new file mode 100755
index 0000000..64d7c8a
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_vote_check.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/icon_vote_unchecked.png b/app/src/main/res/mipmap-xxhdpi/icon_vote_unchecked.png
new file mode 100755
index 0000000..ee844a2
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_vote_unchecked.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/live_bg.jpeg b/app/src/main/res/mipmap-xxhdpi/live_bg.jpeg
new file mode 100644
index 0000000..b29c7f6
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/live_bg.jpeg differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index aee44e1..a1fe6ba 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 9791d5b..a43a62b 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -10,7 +10,7 @@
-
+
@@ -29,8 +29,8 @@
-
-
+
+
@@ -59,4 +59,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index c677974..5a8852d 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -19,4 +19,21 @@
#80FF7E37
#80EB4438
+ #ffffff
+ #1983d1
+ #11222222
+
+
+ #e6e6e6
+ #1AFF7C05
+ #F3F3F3
+ #1a1a1a
+ #FF7C05
+ #333333
+ #FF7C05
+
+ #333333
+ #E2E2E2
+ #FFD500
+
diff --git a/build.gradle b/build.gradle
index d9b961b..270f05a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,11 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
+ ext.kotlin_version = '1.4.21'
repositories {
jcenter()
+ google()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.2.3'
+ classpath 'com.android.tools.build:gradle:4.1.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -16,6 +20,8 @@ allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
+ google()
+ mavenCentral()
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 04e285f..6533050 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Dec 28 10:00:20 PST 2015
+#Mon May 14 16:20:08 CST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
\ No newline at end of file