diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 96cc43e..61a9130 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,22 +1,6 @@ - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index afaee01..57af74c 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,8 +1,10 @@ + diff --git a/.idea/misc.xml b/.idea/misc.xml index ba7052b..ba4f0e0 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,26 +5,37 @@ - + diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 788937b..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index f700cde..eef6e0a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ +# 扫码下载最新demo第一时间体验新功能 ## 如有问题可以加群讨论 STV&RxHttp交流群 或者手动加QQ群:688433795 ## Demo示例下载 -![demo下载](https://github.com/lygttpod/AndroidCustomView/blob/master/app/src/main/res/mipmap-xxhdpi/app_download.png?raw=true) +[点击下载demo体验](http://d.firim.top/whez) # [**炫酷的提交按钮**](https://github.com/lygttpod/AndroidCustomView/blob/master/animation_button.md) ![99.gif](http://upload-images.jianshu.io/upload_images/2057501-0d1119721429bf71.gif?imageMogr2/auto-orient/strip) diff --git a/app/build.gradle b/app/build.gradle index ff97b77..d9383c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,7 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 27 @@ -21,13 +24,14 @@ android { } dependencies { - compile fileTree(include: ['*.jar'], dir: 'libs') - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + implementation fileTree(include: ['*.jar'], dir: 'libs') + androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:27.1.1' + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support:design:27.1.1' - compile 'com.android.support:recyclerview-v7:27.1.1' - testCompile 'junit:junit:4.12' - compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.40' + implementation 'com.android.support:recyclerview-v7:27.1.1' + testImplementation 'junit:junit:4.12' + implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.40' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1b5ec68..ce59942 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,6 +36,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/activity/ClearScreenActivity.kt b/app/src/main/java/com/allen/androidcustomview/activity/ClearScreenActivity.kt new file mode 100644 index 0000000..f6091f0 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/activity/ClearScreenActivity.kt @@ -0,0 +1,39 @@ +package com.allen.androidcustomview.activity + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.widget.Toast +import com.allen.androidcustomview.R +import com.allen.androidcustomview.widget.ClearScreenMode +import com.allen.androidcustomview.widget.ClearScreenView +import kotlinx.android.synthetic.main.activity_clear_screen.* + +/** + *
+ * 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 typeBeans = new ArrayList(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - recyclerView = findViewById(R.id.recycler_view); - - adapter = new MainAdapter(getData()); - adapter.setOnItemClickListener(this); - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - recyclerView.addItemDecoration(new SuperDividerItemDecoration.Builder(this) - .build()); - recyclerView.setAdapter(adapter); - } - - private List getData() { - typeBeans.add(new TypeBean("气泡漂浮动画", 0)); - typeBeans.add(new TypeBean("波浪动画--贝塞尔曲线实现", 1)); - typeBeans.add(new TypeBean("波浪动画--正余弦函数实现", 2)); - typeBeans.add(new TypeBean("水波(雷达)扩散效果", 3)); - typeBeans.add(new TypeBean("RecyclerView实现另类的Tag标签", 4)); - typeBeans.add(new TypeBean("按钮自定义动画", 5)); - typeBeans.add(new TypeBean("自定义支付密码输入框", 6)); - typeBeans.add(new TypeBean("自定义进度条", 7)); - typeBeans.add(new TypeBean("使用的带动画的view", 8)); - typeBeans.add(new TypeBean("粘性小球", 9)); - typeBeans.add(new TypeBean("banner", 10)); - typeBeans.add(new TypeBean("吸顶效果--一行代码实现", 11)); - typeBeans.add(new TypeBean("揭露动画", 12)); - typeBeans.add(new TypeBean("支付宝首页效果", 13)); - return typeBeans; - } - - @Override - public void onItemClick(BaseQuickAdapter adapter, View view, int position) { - switch (typeBeans.get(position).getType()) { - case 0: - startActivity(new Intent(MainActivity.this, BubbleViewActivity.class)); - break; - case 1: - startActivity(new Intent(MainActivity.this, WaveByBezierActivity.class)); - break; - case 2: - startActivity(new Intent(MainActivity.this, WaveBySinCosActivity.class)); - break; - case 3: - startActivity(new Intent(MainActivity.this, RadarActivity.class)); - break; - case 4: - startActivity(new Intent(MainActivity.this, TagActivity.class)); - break; - case 5: - startActivity(new Intent(MainActivity.this, AnimationBtnActivity.class)); - break; - case 6: - startActivity(new Intent(MainActivity.this, PayPsdViewActivity.class)); - break; - case 7: - startActivity(new Intent(MainActivity.this, ProgressBarActivity.class)); - break; - case 8: - startActivity(new Intent(MainActivity.this, AnimationViewActivity.class)); - break; - case 9: - startActivity(new Intent(MainActivity.this, DragBallActivity.class)); - break; - case 10: - startActivity(new Intent(MainActivity.this, BannerActivity.class)); - break; - case 11: - startActivity(new Intent(MainActivity.this, HoverItemActivity.class)); - break; - case 12: - startActivity(new Intent(MainActivity.this, RevealAnimationActivity.class)); - break; - case 13: - startActivity(new Intent(MainActivity.this, AliPayHomeActivity.class)); - break; - default: - - } - } -} diff --git a/app/src/main/java/com/allen/androidcustomview/activity/MainActivity.kt b/app/src/main/java/com/allen/androidcustomview/activity/MainActivity.kt new file mode 100644 index 0000000..5df548d --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/activity/MainActivity.kt @@ -0,0 +1,91 @@ +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.view.View +import android.widget.Toast +import com.allen.androidcustomview.R +import com.allen.androidcustomview.adapter.MainAdapter +import com.allen.androidcustomview.bean.TypeBean +import com.allen.androidcustomview.helper.DragViewHelper +import com.allen.androidcustomview.tagview.TagActivity +import com.allen.androidcustomview.widget.SuperDividerItemDecoration +import com.chad.library.adapter.base.BaseQuickAdapter +import kotlinx.android.synthetic.main.activity_main.* +import java.util.* + + +class MainActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickListener { + + private var adapter: MainAdapter? = null + + private val typeBeans = ArrayList() + + private val data: List + get() { + typeBeans.add(TypeBean("气泡漂浮动画", 0)) + typeBeans.add(TypeBean("波浪动画--贝塞尔曲线实现", 1)) + typeBeans.add(TypeBean("波浪动画--正余弦函数实现", 2)) + typeBeans.add(TypeBean("水波(雷达)扩散效果", 3)) + typeBeans.add(TypeBean("RecyclerView实现另类的Tag标签", 4)) + typeBeans.add(TypeBean("按钮自定义动画", 5)) + typeBeans.add(TypeBean("自定义支付密码输入框", 6)) + typeBeans.add(TypeBean("自定义进度条", 7)) + typeBeans.add(TypeBean("使用的带动画的view", 8)) + typeBeans.add(TypeBean("粘性小球", 9)) + typeBeans.add(TypeBean("banner", 10)) + typeBeans.add(TypeBean("吸顶效果--一行代码实现", 11)) + typeBeans.add(TypeBean("揭露动画", 12)) + typeBeans.add(TypeBean("支付宝首页效果", 13)) + typeBeans.add(TypeBean("RecyclerView的item动画", 14)) + typeBeans.add(TypeBean("路径path动画", 15)) + typeBeans.add(TypeBean("仿新浪投票控件", 16)) + typeBeans.add(TypeBean("直播侧滑清屏效果", 17)) + return typeBeans + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + adapter = MainAdapter(data) + adapter!!.onItemClickListener = this + recycler_view.layoutManager = LinearLayoutManager(this) + recycler_view.addItemDecoration(SuperDividerItemDecoration.Builder(this) + .build()) + recycler_view.adapter = adapter + + DragViewHelper.addDragView(this, + root_view, + "网络图片地址", + defaultImgResId = R.mipmap.ic_camera_3x, + onClick = { + Toast.makeText(this, "点击事件", Toast.LENGTH_SHORT).show() + }) + } + + override fun onItemClick(adapter: BaseQuickAdapter<*, *>, view: View, position: Int) { + when (typeBeans[position].type) { + 0 -> startActivity(Intent(this@MainActivity, BubbleViewActivity::class.java)) + 1 -> startActivity(Intent(this@MainActivity, WaveByBezierActivity::class.java)) + 2 -> startActivity(Intent(this@MainActivity, WaveBySinCosActivity::class.java)) + 3 -> startActivity(Intent(this@MainActivity, RadarActivity::class.java)) + 4 -> startActivity(Intent(this@MainActivity, TagActivity::class.java)) + 5 -> startActivity(Intent(this@MainActivity, AnimationBtnActivity::class.java)) + 6 -> startActivity(Intent(this@MainActivity, PayPsdViewActivity::class.java)) + 7 -> startActivity(Intent(this@MainActivity, ProgressBarActivity::class.java)) + 8 -> startActivity(Intent(this@MainActivity, AnimationViewActivity::class.java)) + 9 -> startActivity(Intent(this@MainActivity, DragBallActivity::class.java)) + 10 -> startActivity(Intent(this@MainActivity, BannerActivity::class.java)) + 11 -> startActivity(Intent(this@MainActivity, HoverItemActivity::class.java)) + 12 -> startActivity(Intent(this@MainActivity, RevealAnimationActivity::class.java)) + 13 -> startActivity(Intent(this@MainActivity, AliPayHomeActivity::class.java)) + 14 -> startActivity(Intent(this@MainActivity, RecyclerViewItemAnimActivity::class.java)) + 15 -> startActivity(Intent(this@MainActivity, PathActivity::class.java)) + 16 -> startActivity(Intent(this@MainActivity, SinaVoteActivity::class.java)) + 17 -> startActivity(Intent(this@MainActivity, ClearScreenActivity::class.java)) + } + } +} diff --git a/app/src/main/java/com/allen/androidcustomview/activity/PathActivity.kt b/app/src/main/java/com/allen/androidcustomview/activity/PathActivity.kt new file mode 100644 index 0000000..736beb2 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/activity/PathActivity.kt @@ -0,0 +1,25 @@ +package com.allen.androidcustomview.activity + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import com.allen.androidcustomview.R +import kotlinx.android.synthetic.main.activity_path.* + +/** + *
+ * @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 getPlanData(Boolean isAll) { + List list = new ArrayList(); + list.add("08月10日"); + list.add("08月11日"); + list.add("08月12日"); + list.add("08月13日"); + if (isAll) { + list.add("08月14日"); + list.add("08月15日"); + list.add("08月16日"); + } + return list; } @Override diff --git a/app/src/main/java/com/allen/androidcustomview/activity/RecyclerViewItemAnimActivity.kt b/app/src/main/java/com/allen/androidcustomview/activity/RecyclerViewItemAnimActivity.kt new file mode 100644 index 0000000..bda6db8 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/activity/RecyclerViewItemAnimActivity.kt @@ -0,0 +1,98 @@ +package com.allen.androidcustomview.activity + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.DefaultItemAnimator +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import com.allen.androidcustomview.R +import com.allen.androidcustomview.adapter.ItemAnimAdapter +import com.allen.androidcustomview.anim.RotateXItemAnimation +import com.allen.androidcustomview.anim.RotateYItemAnimation +import com.allen.androidcustomview.anim.ScaleItemAnimation +import com.allen.androidcustomview.anim.SlideItemAnimation +import kotlinx.android.synthetic.main.activity_recycler_view_item_anim.* +import kotlin.random.Random + +/** + *
+ * @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 { + val list: ArrayList = arrayListOf() + list.add("人生如戏,全靠演技") + list.add("年轻就是资本") + list.add("我的一颗眼泪掉进了海洋,当我找到它的那一天就是我停止爱你的那一天") + list.add("你若一直在,我便一直爱") + list.add("路,跪着也要走完") + list.add("美丽的彩虹就像一座七彩的桥一样高挂在雨后的天空") + list.add("留情不留命,留命伤感情") + list.add("宽容就是在别人和自己意见不一致时也不要勉强") + list.add("那些曾经以为念念不忘的事情,就在我们念念不忘的过程里,被我们遗忘了") + list.add("朝花夕拾捡的是枯萎") + list.add("黄绢幼妇,其土老人") + list.add("要有最朴素的生活,与最遥远的梦想,即使明日天寒地冻,路远马亡") + list.add("不是路不平,而是你不行") + return list + } + + private fun getItemData(): String { + return initData()[Random.nextInt(100) / 10 + 1] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/activity/SinaVoteActivity.kt b/app/src/main/java/com/allen/androidcustomview/activity/SinaVoteActivity.kt new file mode 100644 index 0000000..2fee8ab --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/activity/SinaVoteActivity.kt @@ -0,0 +1,43 @@ +package com.allen.androidcustomview.activity + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import com.allen.androidcustomview.R +import com.allen.androidcustomview.bean.VoteBean +import com.allen.androidcustomview.bean.VoteOption +import com.allen.androidcustomview.data.getMockData +import com.allen.androidcustomview.widget.vote.VoteLayoutAdapter +import kotlinx.android.synthetic.main.activity_sina_vote.* + +/** + *
+ * @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, position: Int) { + voteLayoutAdapter?.refreshDataAfterVotedSuccess(position) + } + + override fun onVoteItemClick(mainVote: VoteBean?, voteOption: VoteOption?, position: Int) { + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/adapter/ItemAnimAdapter.kt b/app/src/main/java/com/allen/androidcustomview/adapter/ItemAnimAdapter.kt new file mode 100644 index 0000000..b1a28ab --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/adapter/ItemAnimAdapter.kt @@ -0,0 +1,19 @@ +package com.allen.androidcustomview.adapter + +import com.allen.androidcustomview.R +import com.chad.library.adapter.base.BaseQuickAdapter +import com.chad.library.adapter.base.BaseViewHolder + +/** + *
+ * @author : Allen
+ * e-mail :lygttpod@163.com
+ * date : 2019年05月11日
+ * desc :
+ * 
+ */ +class ItemAnimAdapter(list: ArrayList) : BaseQuickAdapter(R.layout.item_danmu_layout, list) { + override fun convert(helper: BaseViewHolder, item: String) { + helper.setText(R.id.content_tv, item) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/anim/BaseItemAnimation.java b/app/src/main/java/com/allen/androidcustomview/anim/BaseItemAnimation.java new file mode 100644 index 0000000..2564076 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/anim/BaseItemAnimation.java @@ -0,0 +1,730 @@ +package com.allen.androidcustomview.anim; + + +/** + *
+ * @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 mPendingRemovals = new ArrayList(); + private ArrayList mPendingAdditions = new ArrayList(); + private ArrayList mPendingMoves = new ArrayList(); + private ArrayList mPendingChanges = new ArrayList(); + + ArrayList> mAdditionsList = new ArrayList(); + ArrayList> mMovesList = new ArrayList(); + ArrayList> mChangesList = new ArrayList(); + + ArrayList mAddAnimations = new ArrayList(); + ArrayList mMoveAnimations = new ArrayList(); + ArrayList mRemoveAnimations = new ArrayList(); + ArrayList mChangeAnimations = new ArrayList(); + + private static class MoveInfo { + public ViewHolder holder; + public int fromX, fromY, toX, toY; + + MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) { + this.holder = holder; + this.fromX = fromX; + this.fromY = fromY; + this.toX = toX; + this.toY = toY; + } + } + + private static class ChangeInfo { + public ViewHolder oldHolder, newHolder; + public int fromX, fromY, toX, toY; + + private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) { + this.oldHolder = oldHolder; + this.newHolder = newHolder; + } + + ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, + int fromX, int fromY, int toX, int toY) { + this(oldHolder, newHolder); + this.fromX = fromX; + this.fromY = fromY; + this.toX = toX; + this.toY = toY; + } + + @Override + public String toString() { + return "ChangeInfo{" + + "oldHolder=" + oldHolder + + ", newHolder=" + newHolder + + ", fromX=" + fromX + + ", fromY=" + fromY + + ", toX=" + toX + + ", toY=" + toY + + '}'; + } + } + + @Override + public void runPendingAnimations() { + boolean removalsPending = !mPendingRemovals.isEmpty(); + boolean movesPending = !mPendingMoves.isEmpty(); + boolean changesPending = !mPendingChanges.isEmpty(); + boolean additionsPending = !mPendingAdditions.isEmpty(); + if (!removalsPending && !movesPending && !additionsPending && !changesPending) { + // nothing to animate + return; + } + // First, remove stuff + for (ViewHolder holder : mPendingRemovals) { + animateRemoveImpl(holder); + } + mPendingRemovals.clear(); + // Next, move stuff + if (movesPending) { + final ArrayList moves = new ArrayList(); + moves.addAll(mPendingMoves); + mMovesList.add(moves); + mPendingMoves.clear(); + Runnable mover = new Runnable() { + @Override + public void run() { + for (MoveInfo moveInfo : moves) { + animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, + moveInfo.toX, moveInfo.toY); + } + moves.clear(); + mMovesList.remove(moves); + } + }; + if (removalsPending) { + View view = moves.get(0).holder.itemView; + ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); + } else { + mover.run(); + } + } + // Next, change stuff, to run in parallel with move animations + if (changesPending) { + final ArrayList changes = new ArrayList(); + changes.addAll(mPendingChanges); + mChangesList.add(changes); + mPendingChanges.clear(); + Runnable changer = new Runnable() { + @Override + public void run() { + for (ChangeInfo change : changes) { + animateChangeImpl(change); + } + changes.clear(); + mChangesList.remove(changes); + } + }; + if (removalsPending) { + ViewHolder holder = changes.get(0).oldHolder; + ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration()); + } else { + changer.run(); + } + } + // Next, add stuff + if (additionsPending) { + final ArrayList additions = new ArrayList(); + additions.addAll(mPendingAdditions); + mAdditionsList.add(additions); + mPendingAdditions.clear(); + Runnable adder = new Runnable() { + @Override + public void run() { + for (ViewHolder holder : additions) { + animateAddImpl(holder); + } + additions.clear(); + mAdditionsList.remove(additions); + } + }; + if (removalsPending || movesPending || changesPending) { + long removeDuration = removalsPending ? getRemoveDuration() : 0; + long moveDuration = movesPending ? getMoveDuration() : 0; + long changeDuration = changesPending ? getChangeDuration() : 0; + long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); + View view = additions.get(0).itemView; + ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); + } else { + adder.run(); + } + } + } + + @Override + public boolean animateRemove(final ViewHolder holder) { + resetAnimation(holder); + mPendingRemovals.add(holder); + return true; + } + + private void animateRemoveImpl(final ViewHolder holder) { + final View view = holder.itemView; + final ViewPropertyAnimator animation = view.animate(); + mRemoveAnimations.add(holder); + setRemoveAnimation(holder, animation); + // TODO: 2019年05月15日 animation.setDuration(getRemoveDuration()).alpha(0).setListener( + animation.setDuration(getRemoveDuration()).setListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + dispatchRemoveStarting(holder); + } + + @Override + public void onAnimationEnd(Animator animator) { + animation.setListener(null); + // TODO: 2019年05月15日 view.setAlpha(1); + setRemoveAnimationEnd(holder); + dispatchRemoveFinished(holder); + mRemoveAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + }).start(); + } + + @Override + public boolean animateAdd(final ViewHolder holder) { + resetAnimation(holder); + // TODO: 2019年05月15日 holder.itemView.setAlpha(0); + setAddItemAnimationInit(holder); + mPendingAdditions.add(holder); + return true; + } + + void animateAddImpl(final ViewHolder holder) { + final View view = holder.itemView; + final ViewPropertyAnimator animation = view.animate(); + mAddAnimations.add(holder); + setAddItemAnimation(holder, animation); + // TODO: 2019年05月15日 animation.alpha(1).setDuration(getAddDuration()).setListener(new AnimatorListenerAdapter() { + animation.setDuration(getAddDuration()).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + dispatchAddStarting(holder); + } + + @Override + public void onAnimationEnd(Animator animator) { + animation.setListener(null); + dispatchAddFinished(holder); + mAddAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + + @Override + public void onAnimationCancel(Animator animation) { + // TODO: 2019年05月15日 view.setAlpha(1); + setAddItemAnimationCancel(holder); + } + }).start(); + } + + @Override + public boolean animateMove(final ViewHolder holder, int fromX, int fromY, + int toX, int toY) { + final View view = holder.itemView; + fromX += (int) holder.itemView.getTranslationX(); + fromY += (int) holder.itemView.getTranslationY(); + resetAnimation(holder); + int deltaX = toX - fromX; + int deltaY = toY - fromY; + if (deltaX == 0 && deltaY == 0) { + dispatchMoveFinished(holder); + return false; + } + if (deltaX != 0) { + view.setTranslationX(-deltaX); + } + if (deltaY != 0) { + view.setTranslationY(-deltaY); + } + mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); + return true; + } + + void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { + final View view = holder.itemView; + final int deltaX = toX - fromX; + final int deltaY = toY - fromY; + if (deltaX != 0) { + view.animate().translationX(0); + } + if (deltaY != 0) { + view.animate().translationY(0); + } + // TODO: make EndActions end listeners instead, since end actions aren't called when + // vpas are canceled (and can't end them. why?) + // need listener functionality in VPACompat for this. Ick. + final ViewPropertyAnimator animation = view.animate(); + mMoveAnimations.add(holder); + animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + dispatchMoveStarting(holder); + } + + @Override + public void onAnimationCancel(Animator animator) { + if (deltaX != 0) { + view.setTranslationX(0); + } + if (deltaY != 0) { + view.setTranslationY(0); + } + } + + @Override + public void onAnimationEnd(Animator animator) { + animation.setListener(null); + dispatchMoveFinished(holder); + mMoveAnimations.remove(holder); + dispatchFinishedWhenDone(); + } + }).start(); + } + + @Override + public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, + int fromX, int fromY, int toX, int toY) { + if (oldHolder == newHolder) { + // Don't know how to run change animations when the same view holder is re-used. + // run a move animation to handle position changes. + return animateMove(oldHolder, fromX, fromY, toX, toY); + } + final float prevTranslationX = oldHolder.itemView.getTranslationX(); + final float prevTranslationY = oldHolder.itemView.getTranslationY(); + final float prevAlpha = oldHolder.itemView.getAlpha(); + resetAnimation(oldHolder); + int deltaX = (int) (toX - fromX - prevTranslationX); + int deltaY = (int) (toY - fromY - prevTranslationY); + // recover prev translation state after ending animation + oldHolder.itemView.setTranslationX(prevTranslationX); + oldHolder.itemView.setTranslationY(prevTranslationY); + oldHolder.itemView.setAlpha(prevAlpha); + if (newHolder != null) { + // carry over translation values + resetAnimation(newHolder); + newHolder.itemView.setTranslationX(-deltaX); + newHolder.itemView.setTranslationY(-deltaY); + // TODO: 2019年05月15日 newHolder.itemView.setAlpha(0); + setNewChangeAnimationInit(newHolder); + } + mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); + return true; + } + + void animateChangeImpl(final ChangeInfo changeInfo) { + final ViewHolder holder = changeInfo.oldHolder; + final View view = holder == null ? null : holder.itemView; + final ViewHolder newHolder = changeInfo.newHolder; + final View newView = newHolder != null ? newHolder.itemView : null; + if (view != null) { + final ViewPropertyAnimator oldViewAnim = view.animate().setDuration( + getChangeDuration()); + mChangeAnimations.add(changeInfo.oldHolder); + oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); + oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); + setOldChangeAnimation(holder, oldViewAnim); + // TODO: 2019年05月15日 oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() { + oldViewAnim.setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + dispatchChangeStarting(changeInfo.oldHolder, true); + } + + @Override + public void onAnimationEnd(Animator animator) { + oldViewAnim.setListener(null); + // TODO: 2019年05月15日 view.setAlpha(1); + setOldChangeAnimationEnd(holder); + view.setTranslationX(0); + view.setTranslationY(0); + dispatchChangeFinished(changeInfo.oldHolder, true); + mChangeAnimations.remove(changeInfo.oldHolder); + dispatchFinishedWhenDone(); + } + }).start(); + } + if (newView != null) { + final ViewPropertyAnimator newViewAnimation = newView.animate(); + mChangeAnimations.add(changeInfo.newHolder); + setNewChangeAnimation(newHolder, newViewAnimation); + // TODO: 2019年05月15日 newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).alpha(1) + newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + dispatchChangeStarting(changeInfo.newHolder, false); + } + + @Override + public void onAnimationEnd(Animator animator) { + newViewAnimation.setListener(null); + // TODO: 2019年05月15日 newView.setAlpha(1); + setNewChangeAnimationEnd(newHolder); + newView.setTranslationX(0); + newView.setTranslationY(0); + dispatchChangeFinished(changeInfo.newHolder, false); + mChangeAnimations.remove(changeInfo.newHolder); + dispatchFinishedWhenDone(); + } + }).start(); + } + } + + private void endChangeAnimation(List infoList, ViewHolder item) { + for (int i = infoList.size() - 1; i>= 0; i--) { + ChangeInfo changeInfo = infoList.get(i); + if (endChangeAnimationIfNecessary(changeInfo, item)) { + if (changeInfo.oldHolder == null && changeInfo.newHolder == null) { + infoList.remove(changeInfo); + } + } + } + } + + private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) { + if (changeInfo.oldHolder != null) { + endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder); + } + if (changeInfo.newHolder != null) { + endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder); + } + } + + private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) { + boolean oldItem = false; + if (changeInfo.newHolder == item) { + changeInfo.newHolder = null; + } else if (changeInfo.oldHolder == item) { + changeInfo.oldHolder = null; + oldItem = true; + } else { + return false; + } + // TODO: 2019年05月15日 item.itemView.setAlpha(1); + setNewChangeAnimationEnd(item); + item.itemView.setTranslationX(0); + item.itemView.setTranslationY(0); + dispatchChangeFinished(item, oldItem); + return true; + } + + @Override + public void endAnimation(ViewHolder item) { + final View view = item.itemView; + // this will trigger end callback which should set properties to their target values. + view.animate().cancel(); + // TODO if some other animations are chained to end, how do we cancel them as well? + for (int i = mPendingMoves.size() - 1; i>= 0; i--) { + MoveInfo moveInfo = mPendingMoves.get(i); + if (moveInfo.holder == item) { + view.setTranslationY(0); + view.setTranslationX(0); + dispatchMoveFinished(item); + mPendingMoves.remove(i); + } + } + endChangeAnimation(mPendingChanges, item); + if (mPendingRemovals.remove(item)) { + // TODO: 2019年05月15日 view.setAlpha(1); + setRemoveAnimationEnd(item); + dispatchRemoveFinished(item); + } + if (mPendingAdditions.remove(item)) { + // TODO: 2019年05月15日 view.setAlpha(1); + setAddItemAnimationCancel(item); + dispatchAddFinished(item); + } + + for (int i = mChangesList.size() - 1; i>= 0; i--) { + ArrayList changes = mChangesList.get(i); + endChangeAnimation(changes, item); + if (changes.isEmpty()) { + mChangesList.remove(i); + } + } + for (int i = mMovesList.size() - 1; i>= 0; i--) { + ArrayList moves = mMovesList.get(i); + for (int j = moves.size() - 1; j>= 0; j--) { + MoveInfo moveInfo = moves.get(j); + if (moveInfo.holder == item) { + view.setTranslationY(0); + view.setTranslationX(0); + dispatchMoveFinished(item); + moves.remove(j); + if (moves.isEmpty()) { + mMovesList.remove(i); + } + break; + } + } + } + for (int i = mAdditionsList.size() - 1; i>= 0; i--) { + ArrayList additions = mAdditionsList.get(i); + if (additions.remove(item)) { + // TODO: 2019年05月15日 view.setAlpha(1); + setAddItemAnimationCancel(item); + dispatchAddFinished(item); + if (additions.isEmpty()) { + mAdditionsList.remove(i); + } + } + } + + // animations should be ended by the cancel above. + //noinspection PointlessBooleanExpression,ConstantConditions + if (mRemoveAnimations.remove(item) && DEBUG) { + throw new IllegalStateException("after animation is cancelled, item should not be in " + + "mRemoveAnimations list"); + } + + //noinspection PointlessBooleanExpression,ConstantConditions + if (mAddAnimations.remove(item) && DEBUG) { + throw new IllegalStateException("after animation is cancelled, item should not be in " + + "mAddAnimations list"); + } + + //noinspection PointlessBooleanExpression,ConstantConditions + if (mChangeAnimations.remove(item) && DEBUG) { + throw new IllegalStateException("after animation is cancelled, item should not be in " + + "mChangeAnimations list"); + } + + //noinspection PointlessBooleanExpression,ConstantConditions + if (mMoveAnimations.remove(item) && DEBUG) { + throw new IllegalStateException("after animation is cancelled, item should not be in " + + "mMoveAnimations list"); + } + dispatchFinishedWhenDone(); + } + + private void resetAnimation(ViewHolder holder) { + if (sDefaultInterpolator == null) { + sDefaultInterpolator = new ValueAnimator().getInterpolator(); + } + holder.itemView.animate().setInterpolator(sDefaultInterpolator); + endAnimation(holder); + } + + @Override + public boolean isRunning() { + return (!mPendingAdditions.isEmpty() + || !mPendingChanges.isEmpty() + || !mPendingMoves.isEmpty() + || !mPendingRemovals.isEmpty() + || !mMoveAnimations.isEmpty() + || !mRemoveAnimations.isEmpty() + || !mAddAnimations.isEmpty() + || !mChangeAnimations.isEmpty() + || !mMovesList.isEmpty() + || !mAdditionsList.isEmpty() + || !mChangesList.isEmpty()); + } + + /** + * Check the state of currently pending and running animations. If there are none + * pending/running, call {@link #dispatchAnimationsFinished()} to notify any + * listeners. + */ + void dispatchFinishedWhenDone() { + if (!isRunning()) { + dispatchAnimationsFinished(); + } + } + + @Override + public void endAnimations() { + int count = mPendingMoves.size(); + for (int i = count - 1; i>= 0; i--) { + MoveInfo item = mPendingMoves.get(i); + View view = item.holder.itemView; + view.setTranslationY(0); + view.setTranslationX(0); + dispatchMoveFinished(item.holder); + mPendingMoves.remove(i); + } + count = mPendingRemovals.size(); + for (int i = count - 1; i>= 0; i--) { + ViewHolder item = mPendingRemovals.get(i); + dispatchRemoveFinished(item); + mPendingRemovals.remove(i); + } + count = mPendingAdditions.size(); + for (int i = count - 1; i>= 0; i--) { + ViewHolder item = mPendingAdditions.get(i); + // TODO: 2019年05月15日 view.setAlpha(1); + setAddItemAnimationCancel(item); + dispatchAddFinished(item); + mPendingAdditions.remove(i); + } + count = mPendingChanges.size(); + for (int i = count - 1; i>= 0; i--) { + endChangeAnimationIfNecessary(mPendingChanges.get(i)); + } + mPendingChanges.clear(); + if (!isRunning()) { + return; + } + + int listCount = mMovesList.size(); + for (int i = listCount - 1; i>= 0; i--) { + ArrayList moves = mMovesList.get(i); + count = moves.size(); + for (int j = count - 1; j>= 0; j--) { + MoveInfo moveInfo = moves.get(j); + ViewHolder item = moveInfo.holder; + View view = item.itemView; + view.setTranslationY(0); + view.setTranslationX(0); + dispatchMoveFinished(moveInfo.holder); + moves.remove(j); + if (moves.isEmpty()) { + mMovesList.remove(moves); + } + } + } + listCount = mAdditionsList.size(); + for (int i = listCount - 1; i>= 0; i--) { + ArrayList additions = mAdditionsList.get(i); + count = additions.size(); + for (int j = count - 1; j>= 0; j--) { + ViewHolder item = additions.get(j); + // TODO: 2019年05月15日 item.itemView.setAlpha(1); + setAddItemAnimationCancel(item); + dispatchAddFinished(item); + additions.remove(j); + if (additions.isEmpty()) { + mAdditionsList.remove(additions); + } + } + } + listCount = mChangesList.size(); + for (int i = listCount - 1; i>= 0; i--) { + ArrayList changes = mChangesList.get(i); + count = changes.size(); + for (int j = count - 1; j>= 0; j--) { + endChangeAnimationIfNecessary(changes.get(j)); + if (changes.isEmpty()) { + mChangesList.remove(changes); + } + } + } + + cancelAll(mRemoveAnimations); + cancelAll(mMoveAnimations); + cancelAll(mAddAnimations); + cancelAll(mChangeAnimations); + + dispatchAnimationsFinished(); + } + + void cancelAll(List viewHolders) { + for (int i = viewHolders.size() - 1; i>= 0; i--) { + viewHolders.get(i).itemView.animate().cancel(); + } + } + + /** + * {@inheritDoc} + *

+ * If the payload list is not empty, DefaultItemAnimator returns true. + * When this is the case: + *

    + *
  • If you override {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, both + * ViewHolder arguments will be the same instance. + *
  • + *
  • + * If you are not overriding {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, + * then DefaultItemAnimator will call {@link #animateMove(ViewHolder, int, int, int, int)} and + * run a move animation instead. + *
  • + *
+ */ + @Override + public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, + @NonNull List 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/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/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/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/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_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_vote_item_bg.xml b/app/src/main/res/drawable/shape_vote_item_bg.xml new file mode 100644 index 0000000..594eaf2 --- /dev/null +++ b/app/src/main/res/drawable/shape_vote_item_bg.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_clear_screen.xml b/app/src/main/res/layout/activity_clear_screen.xml new file mode 100644 index 0000000..675b590 --- /dev/null +++ b/app/src/main/res/layout/activity_clear_screen.xml @@ -0,0 +1,40 @@ + + + + + + + + + +