+ * author : Allen + * date : 2021年1月28日 + * desc : + *+ */ +class ClearScreenActivity : AppCompatActivity(), ClearScreenView.OnClearScreenListener { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_clear_screen) + + clear_screen_container.addClearView(iv_clear_content) + + clear_screen_container.setOnClearScreenListener(this) + + btn_quick_clear.setOnClickListener { clear_screen_container.clearScreenMode = ClearScreenMode.QUICK_SCROLL } + btn_slow_clear.setOnClickListener { clear_screen_container.clearScreenMode = ClearScreenMode.SLOW_SCROLL } + } + + override fun onCleared() { + Toast.makeText(this, "清屏了", Toast.LENGTH_SHORT).show() + } + + override fun onRestored() { + Toast.makeText(this, "恢复了", Toast.LENGTH_SHORT).show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/activity/MainActivity.java b/app/src/main/java/com/allen/androidcustomview/activity/MainActivity.java deleted file mode 100644 index 9da4d34..0000000 --- a/app/src/main/java/com/allen/androidcustomview/activity/MainActivity.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.allen.androidcustomview.activity; - -import android.content.Intent; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.View; - -import com.allen.androidcustomview.R; -import com.allen.androidcustomview.adapter.MainAdapter; -import com.allen.androidcustomview.bean.TypeBean; -import com.allen.androidcustomview.tagview.TagActivity; -import com.allen.androidcustomview.widget.SuperDividerItemDecoration; -import com.chad.library.adapter.base.BaseQuickAdapter; - -import java.util.ArrayList; -import java.util.List; - - -public class MainActivity extends AppCompatActivity implements BaseQuickAdapter.OnItemClickListener { - - - private RecyclerView recyclerView; - - private MainAdapter adapter; - - private List
+ * @author : Allen + * date : 2019年07月23日 + * desc : + *+ */ +class PathActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_path) + start_btn.setOnClickListener { + car_anim_view.startAnim() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/activity/ProgressBarActivity.java b/app/src/main/java/com/allen/androidcustomview/activity/ProgressBarActivity.java index 99b3321..09c91c2 100644 --- a/app/src/main/java/com/allen/androidcustomview/activity/ProgressBarActivity.java +++ b/app/src/main/java/com/allen/androidcustomview/activity/ProgressBarActivity.java @@ -13,6 +13,10 @@ import com.allen.androidcustomview.widget.LoadingLineView; import com.allen.androidcustomview.widget.LoadingView; import com.allen.androidcustomview.widget.ProductProgressBar; +import com.allen.androidcustomview.widget.StudyPlanProgressView; + +import java.util.ArrayList; +import java.util.List; public class ProgressBarActivity extends AppCompatActivity { @@ -24,6 +28,7 @@ public class ProgressBarActivity extends AppCompatActivity { LoadingLineView loadingLineView; Button button; + StudyPlanProgressView studyPlanProgressView; @Override protected void onCreate(Bundle savedInstanceState) { @@ -39,6 +44,7 @@ protected void onCreate(Bundle savedInstanceState) { textView = (TextView) findViewById(R.id.progress_tv); button = (Button) findViewById(R.id.startAnimationBtn); + studyPlanProgressView = findViewById(R.id.study_plan_progress_view); circleProgressBarView.setProgressWithAnimation(60); circleProgressBarView.setProgressListener(new CircleProgressBarView.ProgressListener() { @@ -79,8 +85,25 @@ public void currentProgressListener(float currentProgress) { textView.setText("当前进度:" + currentProgress); } }); + studyPlanProgressView.setData(getPlanData(true)); } }); + studyPlanProgressView.setData(getPlanData(false)); + + } + + private List
+ * @author : Allen + * e-mail : lygttpod@163.com + * date : 2019年05月11日 + * desc : + *+ */ +class RecyclerViewItemAnimActivity : AppCompatActivity() { + + var adapter: ItemAnimAdapter? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_recycler_view_item_anim) + initListener() + initView() + initData() + } + + private fun initView() { + adapter = ItemAnimAdapter(arrayListOf()) + recycler_view.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) + recycler_view.adapter = adapter + recycler_view.itemAnimator = DefaultItemAnimator() + } + + private fun initListener() { + normal_btn.setOnClickListener { setReverseLayout(false) } + reverse_btn.setOnClickListener { setReverseLayout(true) } + scale_btn.setOnClickListener { setItemAnimation(ScaleItemAnimation()) } + slide_btn.setOnClickListener { setItemAnimation(SlideItemAnimation()) } + rotate_x_btn.setOnClickListener { setItemAnimation(RotateXItemAnimation()) } + rotate_y_btn.setOnClickListener { setItemAnimation(RotateYItemAnimation()) } + + add_btn.setOnClickListener { + adapter?.addData(0, getItemData()) + (recycler_view.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(0, 0) + } + + remove_btn.setOnClickListener { + if (adapter?.data?.size ?: 0> 0) { + adapter?.remove(0) + (recycler_view.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(0, 0) + } + } + } + + private fun setReverseLayout(reverseLayout: Boolean) { + recycler_view.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, reverseLayout) + adapter?.data?.clear() + adapter?.notifyDataSetChanged() + } + + private fun setItemAnimation(itemAnimation: RecyclerView.ItemAnimator) { + adapter?.data?.clear() + adapter?.notifyDataSetChanged() + recycler_view.itemAnimator = itemAnimation + } + + private fun initData(): ArrayList
+ * @author : Allen + * date : 2019年08月06日 + * desc : + *+ */ +class SinaVoteActivity : AppCompatActivity(), VoteLayoutAdapter.OnVoteClickListener { + + var voteLayoutAdapter: VoteLayoutAdapter? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_sina_vote) + voteLayoutAdapter = VoteLayoutAdapter(vote_ll) + voteLayoutAdapter?.setData(getMockData()) + voteLayoutAdapter?.onVoteClickListener = this + } + + override fun onDestroy() { + super.onDestroy() + voteLayoutAdapter?.onDestroy() + } + + override fun onVoteCommitBtnClick(mainVote: VoteBean?, optionIds: ArrayList
+ * @author : Allen + * e-mail :lygttpod@163.com + * date : 2019年05月11日 + * desc : + *+ */ +class ItemAnimAdapter(list: ArrayList
+ * @author : Allen + * e-mail : lygttpod@163.com + * date : 2019年05月11日 + * desc : 基于DefaultItemAnimator扩展的BaseItemAnimation,暴露常用方法 + *+ */ + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.support.annotation.NonNull; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.RecyclerView.ViewHolder; +import android.support.v7.widget.SimpleItemAnimator; +import android.view.View; +import android.view.ViewPropertyAnimator; + +import java.util.ArrayList; +import java.util.List; + +public abstract class BaseItemAnimation extends SimpleItemAnimator { + private static final boolean DEBUG = false; + + private static TimeInterpolator sDefaultInterpolator; + + private ArrayList
+ * If the payload list is not empty, DefaultItemAnimator returns true.
+ * When this is the case:
+ *
+ * @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/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
+ * @author : Allen + * date : 2019年08月06日 + * desc : + *+ */ + +fun getMockData(): ArrayList
+ * @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
+ * @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
+ * @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
+ * @author : Allen + * date : 2019年08月03日 + * desc : + *+ */ +class VoteLayoutAdapter(private val viewGroup: ViewGroup) { + + private var viewHolders = mutableListOf
+ * @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 @@ + +