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 0e23f8e..57af74c 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,9 +1,11 @@ + diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 1059328..e3ba279 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,11 +1,96 @@ \ No newline at end of file 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 2189058..eef6e0a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -![demo下载](https://github.com/lygttpod/AndroidCustomView/blob/master/app/src/main/res/mipmap-xxhdpi/app_download.png?raw=true) +# 扫码下载最新demo第一时间体验新功能 +## 如有问题可以加群讨论 +STV&RxHttp交流群 + +或者手动加QQ群:688433795 + +## Demo示例下载 +[点击下载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) @@ -25,3 +32,10 @@ # [**进度条**] ![进度条.gif](http://osnoex6vf.bkt.clouddn.com/loading.gif) +# [**一行代码实现吸顶悬停效果**] +![吸顶悬停.gif](http://osnoex6vf.bkt.clouddn.com/hover_view.gif) + + +# [**支付宝首页效果**] +![支付宝首页效果.gif](http://osnoex6vf.bkt.clouddn.com/alipay_home.gif) + diff --git a/app/build.gradle b/app/build.gradle index 3ac6095..d9383c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,12 +1,14 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 24 - buildToolsVersion "24.0.2" + compileSdkVersion 27 defaultConfig { applicationId "com.allen.androidcustomview" - minSdkVersion 19 - targetSdkVersion 24 + targetSdkVersion 27 + minSdkVersion 17 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -22,12 +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:24.2.1' - compile 'com.android.support.constraint:constraint-layout:1.0.2' - compile 'com.android.support:recyclerview-v7:24.0.0-alpha1' - testCompile 'junit:junit:4.12' + 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' + 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 de88352..ce59942 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,6 +32,14 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/activity/AliPayHomeActivity.java b/app/src/main/java/com/allen/androidcustomview/activity/AliPayHomeActivity.java new file mode 100644 index 0000000..6d5e5d0 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/activity/AliPayHomeActivity.java @@ -0,0 +1,82 @@ +package com.allen.androidcustomview.activity; + +import android.graphics.Color; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.v7.app.AppCompatActivity; +import android.view.View; + +import com.allen.androidcustomview.R; + +/** + *
+ *      @author : Allen
+ *      date    : 2018/09/30
+ *      desc    : 支付宝首页 来源 https://mp.weixin.qq.com/s/GegMt7GDBCFVoUgFQWG3Sw
+ *      version : 1.0
+ * 
+ */ +public class AliPayHomeActivity extends AppCompatActivity implements AppBarLayout.OnOffsetChangedListener { + + + private AppBarLayout mAppBarLayout; + private View mToolbarOpenBgView; + private View mToolbarCloseBgView; + private View mToolbarOpenLayout; + private View mToolbarCloseLayout; + private View contentBgView; + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_alipay_home); + initView(); + mAppBarLayout.addOnOffsetChangedListener(this); + } + + private void initView() { + + mToolbarOpenBgView = findViewById(R.id.toolbar_open_bg_view); + mToolbarOpenLayout = findViewById(R.id.include_toolbar_open); + + mToolbarCloseBgView = findViewById(R.id.bg_toolbar_close); + mToolbarCloseLayout = findViewById(R.id.include_toolbar_close); + contentBgView = findViewById(R.id.content_bg_view); + + mAppBarLayout = findViewById(R.id.app_bar_layout); + } + + @Override + public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { + + //垂直方向偏移量 + int offset = Math.abs(verticalOffset); + //最大偏移距离 + int scrollRange = appBarLayout.getTotalScrollRange(); + if (offset <= scrollRange / 2) {//当滑动没超过一半,展开状态下toolbar显示内容,根据收缩位置,改变透明值 + mToolbarOpenLayout.setVisibility(View.VISIBLE); + mToolbarCloseLayout.setVisibility(View.GONE); + //根据偏移百分比 计算透明值 + float scale2 = (float) offset / (scrollRange / 2); + int alpha2 = (int) (255 * scale2); + mToolbarOpenBgView.setBackgroundColor(Color.argb(alpha2, 25, 131, 209)); + } else {//当滑动超过一半,收缩状态下toolbar显示内容,根据收缩位置,改变透明值 + mToolbarOpenLayout.setVisibility(View.GONE); + mToolbarCloseLayout.setVisibility(View.VISIBLE); + float scale3 = (float) (scrollRange - offset) / (scrollRange / 2); + int alpha3 = (int) (255 * scale3); + mToolbarCloseBgView.setBackgroundColor(Color.argb(alpha3, 25, 131, 209)); + } + //根据偏移百分比计算扫一扫布局的透明度值 + float scale = (float) offset / scrollRange; + int alpha = (int) (255 * scale); + contentBgView.setBackgroundColor(Color.argb(alpha, 25, 131, 209)); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mAppBarLayout.removeOnOffsetChangedListener(this); + } +} diff --git a/app/src/main/java/com/allen/androidcustomview/activity/BannerActivity.java b/app/src/main/java/com/allen/androidcustomview/activity/BannerActivity.java new file mode 100644 index 0000000..d142a4e --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/activity/BannerActivity.java @@ -0,0 +1,89 @@ +package com.allen.androidcustomview.activity; + +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.Toast; + +import com.allen.androidcustomview.R; +import com.allen.androidcustomview.widget.banner.BannerView; +import com.allen.androidcustomview.widget.banner.listener.OnPageChangeListener; +import com.allen.androidcustomview.widget.banner.listener.OnPageClickListener; +import com.allen.androidcustomview.widget.banner.PagerOptions; +import com.allen.androidcustomview.widget.banner.holder.BannerViewHolder; +import com.allen.androidcustomview.widget.banner.holder.BannerViewHolderCreator; + +import java.util.ArrayList; +import java.util.List; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/03/09
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public class BannerActivity extends AppCompatActivity { + + private BannerView bannerView; + private List datas = new ArrayList<>(); + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_banner); + + datas.add("http://7xi8d6.com1.z0.glb.clouddn.com/20180109085038_4A7atU_rakukoo_9_1_2018_8_50_25_276.jpeg"); + datas.add("http://7xi8d6.com1.z0.glb.clouddn.com/20180102083655_3t4ytm_Screenshot.jpeg"); + datas.add("http://7xi8d6.com1.z0.glb.clouddn.com/20171228085004_5yEHju_Screenshot.jpeg"); + + bannerView = (BannerView) findViewById(R.id.banner_view); + + PagerOptions options = new PagerOptions + .Builder(this) + .setPageMargin(20) + .setPrePagerWidth(50) + .setIndicatorDrawable(R.mipmap.banner_point_disabled,R.mipmap.banner_point_enabled) +// .setIndicatorColor(Color.YELLOW,Color.RED) + .setOnPageClickListener(new OnPageClickListener() { + @Override + public void onPageClick(View view, int position) { + Toast.makeText(BannerActivity.this,"Click"+position,Toast.LENGTH_SHORT).show(); + } + }) + .build(); + + + bannerView.setPagerOptions(options) + .setPages(datas, new BannerViewHolderCreator() { + @Override + public MyBanner createViewHolder() { + return new MyBanner(); + } + }); + + } + + public class MyBanner implements BannerViewHolder{ + + private ImageView imageView; + @Override + public View createView(Context context) { + View view = LayoutInflater.from(context).inflate(R.layout.banner_item,null); + return view; + } + + @Override + public void onBind(Context context, int position, String data) { + + } + } +} 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/HoverItemActivity.java b/app/src/main/java/com/allen/androidcustomview/activity/HoverItemActivity.java new file mode 100644 index 0000000..04b814e --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/activity/HoverItemActivity.java @@ -0,0 +1,153 @@ +package com.allen.androidcustomview.activity; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.widget.TextView; + +import com.allen.androidcustomview.R; +import com.allen.androidcustomview.adapter.HoverAdapter; +import com.allen.androidcustomview.bean.UserBean; +import com.allen.androidcustomview.utils.CharacterParser; +import com.allen.androidcustomview.utils.PinyinComparator; +import com.allen.androidcustomview.widget.HoverItemDecoration; +import com.allen.androidcustomview.widget.IndexView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/04/16
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public class HoverItemActivity extends AppCompatActivity { + private RecyclerView recyclerView; + private IndexView indexView; + private TextView showTextDialog; + + private HoverAdapter adapter; + + private List userBeans = new ArrayList<>(); + + private String[] names = new String[]{"阿妹", "打黑牛", "张三", "李四", "王五", "田鸡", "孙五"}; + + /** + * 汉字转换成拼音的类 + */ + private CharacterParser characterParser; + /** + * 根据拼音来排列ListView里面的数据类 + */ + private PinyinComparator pinyinComparator; + + private LinearLayoutManager layoutManager; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_hover_item); + + characterParser = CharacterParser.getInstance(); + pinyinComparator = new PinyinComparator(); + + + userBeans = filledData(getData()); + + recyclerView = (RecyclerView) findViewById(R.id.recycler_view); + indexView = (IndexView) findViewById(R.id.index_view); + showTextDialog = (TextView) findViewById(R.id.show_text_dialog); + + layoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(layoutManager); + //一行代码实现吸顶悬浮的效果 + recyclerView.addItemDecoration(new HoverItemDecoration(this, new HoverItemDecoration.BindItemTextCallback() { + @Override + public String getItemText(int position) { + //悬浮的信息 + return userBeans.get(position).getSortLetters(); + } + })); + + adapter = new HoverAdapter(userBeans); + + recyclerView.setAdapter(adapter); + + initIndexView(); + } + + /** + * 初始化右边字幕索引view + */ + private void initIndexView() { + indexView.setShowTextDialog(showTextDialog); + indexView.setOnTouchingLetterChangedListener(new IndexView.OnTouchingLetterChangedListener() { + @Override + public void onTouchingLetterChanged(String letter) { + // 该字母首次出现的位置 + int position = getPositionForSection(letter); + if (position != -1) { + layoutManager.scrollToPositionWithOffset(position, 0); + layoutManager.setStackFromEnd(false); + } + } + }); + } + + public int getPositionForSection(String section) { + for (int i = 0; i < userBeans.size(); i++) { + String sortStr = userBeans.get(i).getSortLetters(); + if (sortStr.equals(section)) { + return i; + } + } + return -1; + } + + private List getData() { + List userBeans = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + UserBean userBean = new UserBean(); + userBean.setUserName(names[i % 7]); + userBeans.add(userBean); + } + + return userBeans; + } + + private List filledData(List sortList) { + + for (int i = 0; i < sortList.size(); i++) { + + if ("".equals(sortList.get(i).getUserName())) { + sortList.get(i).setSortLetters("#"); + } else { + // 汉字转换成拼音 + String pinyin = characterParser.getSelling(sortList.get(i).getUserName()); + String sortString = pinyin.substring(0, 1).toUpperCase(); + + // 正则表达式,判断首字母是否是英文字母 + if (sortString.matches("[A-Z]")) { + sortList.get(i).setSortLetters(sortString.toUpperCase()); + } else { + sortList.get(i).setSortLetters("#"); + } + } + + } + + // 根据a-z进行排序源数据 + Collections.sort(sortList, pinyinComparator); + + return sortList; + } + +} 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 e781ed4..0000000 --- a/app/src/main/java/com/allen/androidcustomview/activity/MainActivity.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.allen.androidcustomview.activity; - -import android.content.Intent; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.widget.Button; - -import com.allen.androidcustomview.R; -import com.allen.androidcustomview.tagview.TagActivity; - - -public class MainActivity extends AppCompatActivity { - - private Button button_bubble; - private Button button_wave_bezier; - private Button button_wave_sin_cos; - private Button button_radar; - private Button button_tag; - private Button animation_btn; - private Button pay_psd_view_btn; - private Button progress_btn; - - private Button animationViewBtn; - private Button huaweiViewBtn; - private Button fingerBtn; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - button_bubble = (Button) findViewById(R.id.bubble_view_btn); - button_wave_bezier = (Button) findViewById(R.id.wave_view_by_bezier_btn); - button_wave_sin_cos = (Button) findViewById(R.id.wave_view_by_sin_cos_btn); - button_radar = (Button) findViewById(R.id.radar_view_btn); - button_tag = (Button) findViewById(R.id.tag_view_btn); - animation_btn = (Button) findViewById(R.id.animation_btn); - pay_psd_view_btn = (Button) findViewById(R.id.pay_psd_view_btn); - progress_btn = (Button) findViewById(R.id.progress_btn); - animationViewBtn = (Button) findViewById(R.id.animation_view_btn); - huaweiViewBtn = (Button) findViewById(R.id.huawei_view_btn); - fingerBtn = (Button) findViewById(R.id.finger_btn); - - button_bubble.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, BubbleViewActivity.class)); - } - }); - button_wave_bezier.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, WaveByBezierActivity.class)); - } - }); - button_wave_sin_cos.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, WaveBySinCosActivity.class)); - } - }); - button_radar.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, RadarActivity.class)); - } - }); - button_tag.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, TagActivity.class)); - } - }); - animation_btn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, AnimationBtnActivity.class)); - } - }); - - pay_psd_view_btn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, PayPsdViewActivity.class)); - } - }); - progress_btn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - startActivity(new Intent(MainActivity.this, ProgressBarActivity.class)); - } - }); - animationViewBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(MainActivity.this, AnimationViewActivity.class)); - } - }); - huaweiViewBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(MainActivity.this, DragBallActivity.class)); - } - }); - fingerBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(MainActivity.this, FingerprintActivity.class)); - } - }); - - - } - -} 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 761895a..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() { @@ -68,7 +74,7 @@ public void currentProgressListener(float currentProgress) { button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - loadingLineView.stopLoading(); + loadingLineView.startLoading(); loadingView.startAnimation(); horizontalProgressBar.setProgressWithAnimation(100); productProgressBar.setProgress(100); @@ -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/RevealAnimationActivity.java b/app/src/main/java/com/allen/androidcustomview/activity/RevealAnimationActivity.java new file mode 100644 index 0000000..6052124 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/activity/RevealAnimationActivity.java @@ -0,0 +1,85 @@ +package com.allen.androidcustomview.activity; + +import android.animation.Animator; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.widget.Button; +import android.widget.ImageView; + +import com.allen.androidcustomview.R; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/08/24
+ *      desc    : 揭露动画
+ *      version : 1.0
+ * 
+ */ +public class RevealAnimationActivity extends AppCompatActivity { + + private ImageView imageView; + private Button startBtn; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_reveal_animation); + + imageView = findViewById(R.id.img_iv); + startBtn = findViewById(R.id.start_btn); + + + final int centerX = 0; + final int centerY = 0; + startBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + final float radius = (float) Math.hypot(imageView.getWidth(), imageView.getHeight()); + + if (imageView.getVisibility() == View.VISIBLE) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Animator animator = ViewAnimationUtils.createCircularReveal(imageView, centerX, centerY, radius, 0); + animator.setDuration(3000); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + imageView.setVisibility(View.GONE); + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + animator.start(); + } + + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Animator animator = ViewAnimationUtils.createCircularReveal(imageView, centerX, centerY, 0, radius); + animator.setDuration(3000); + imageView.setVisibility(View.VISIBLE); + animator.start(); + } + } + } + }); + + } +} 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/HoverAdapter.java b/app/src/main/java/com/allen/androidcustomview/adapter/HoverAdapter.java new file mode 100644 index 0000000..14de51f --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/adapter/HoverAdapter.java @@ -0,0 +1,33 @@ +package com.allen.androidcustomview.adapter; + +import android.support.annotation.Nullable; + +import com.allen.androidcustomview.R; +import com.allen.androidcustomview.bean.UserBean; +import com.chad.library.adapter.base.BaseQuickAdapter; +import com.chad.library.adapter.base.BaseViewHolder; + +import java.util.List; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/04/16
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public class HoverAdapter extends BaseQuickAdapter { + + public HoverAdapter( @Nullable List data) { + super(R.layout.adapter_item_hover_user, data); + } + + @Override + protected void convert(BaseViewHolder helper, UserBean item) { + helper.setText(R.id.user_name_tv,item.getUserName()); + } + +} 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/adapter/MainAdapter.java b/app/src/main/java/com/allen/androidcustomview/adapter/MainAdapter.java new file mode 100644 index 0000000..e8a13e8 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/adapter/MainAdapter.java @@ -0,0 +1,32 @@ +package com.allen.androidcustomview.adapter; + +import android.support.annotation.Nullable; + +import com.allen.androidcustomview.R; +import com.allen.androidcustomview.bean.TypeBean; +import com.chad.library.adapter.base.BaseQuickAdapter; +import com.chad.library.adapter.base.BaseViewHolder; + +import java.util.List; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/05/14
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public class MainAdapter extends BaseQuickAdapter { + + public MainAdapter( @Nullable List data) { + super(R.layout.adapter_item_main, data); + } + + @Override + protected void convert(BaseViewHolder helper, TypeBean item) { + helper.setText(R.id.title_tv,item.getTitle()); + } +} 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/TypeBean.java b/app/src/main/java/com/allen/androidcustomview/bean/TypeBean.java new file mode 100644 index 0000000..a67554b --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/bean/TypeBean.java @@ -0,0 +1,37 @@ +package com.allen.androidcustomview.bean; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/05/14
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public class TypeBean { + private String title; + private int type; + + public TypeBean(String title, int type) { + this.title = title; + this.type = type; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } +} diff --git a/app/src/main/java/com/allen/androidcustomview/bean/UserBean.java b/app/src/main/java/com/allen/androidcustomview/bean/UserBean.java new file mode 100644 index 0000000..506ff43 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/bean/UserBean.java @@ -0,0 +1,33 @@ +package com.allen.androidcustomview.bean; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/04/16
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public class UserBean { + private String userName; + private String sortLetters=""; + + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getSortLetters() { + return sortLetters; + } + + public void setSortLetters(String sortLetters) { + this.sortLetters = sortLetters; + } +} 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/utils/CharacterParser.java b/app/src/main/java/com/allen/androidcustomview/utils/CharacterParser.java new file mode 100644 index 0000000..41dc911 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/utils/CharacterParser.java @@ -0,0 +1,131 @@ +package com.allen.androidcustomview.utils; + +/** + * Java汉字转换为拼音 + * + */ +public class CharacterParser { + private static int[] pyvalue = new int[] {-20319, -20317, -20304, -20295, -20292, -20283, -20265, -20257, -20242, -20230, -20051, -20036, -20032, + -20026, -20002, -19990, -19986, -19982, -19976, -19805, -19784, -19775, -19774, -19763, -19756, -19751, -19746, -19741, -19739, -19728, + -19725, -19715, -19540, -19531, -19525, -19515, -19500, -19484, -19479, -19467, -19289, -19288, -19281, -19275, -19270, -19263, -19261, + -19249, -19243, -19242, -19238, -19235, -19227, -19224, -19218, -19212, -19038, -19023, -19018, -19006, -19003, -18996, -18977, -18961, + -18952, -18783, -18774, -18773, -18763, -18756, -18741, -18735, -18731, -18722, -18710, -18697, -18696, -18526, -18518, -18501, -18490, + -18478, -18463, -18448, -18447, -18446, -18239, -18237, -18231, -18220, -18211, -18201, -18184, -18183, -18181, -18012, -17997, -17988, + -17970, -17964, -17961, -17950, -17947, -17931, -17928, -17922, -17759, -17752, -17733, -17730, -17721, -17703, -17701, -17697, -17692, + -17683, -17676, -17496, -17487, -17482, -17468, -17454, -17433, -17427, -17417, -17202, -17185, -16983, -16970, -16942, -16915, -16733, + -16708, -16706, -16689, -16664, -16657, -16647, -16474, -16470, -16465, -16459, -16452, -16448, -16433, -16429, -16427, -16423, -16419, + -16412, -16407, -16403, -16401, -16393, -16220, -16216, -16212, -16205, -16202, -16187, -16180, -16171, -16169, -16158, -16155, -15959, + -15958, -15944, -15933, -15920, -15915, -15903, -15889, -15878, -15707, -15701, -15681, -15667, -15661, -15659, -15652, -15640, -15631, + -15625, -15454, -15448, -15436, -15435, -15419, -15416, -15408, -15394, -15385, -15377, -15375, -15369, -15363, -15362, -15183, -15180, + -15165, -15158, -15153, -15150, -15149, -15144, -15143, -15141, -15140, -15139, -15128, -15121, -15119, -15117, -15110, -15109, -14941, + -14937, -14933, -14930, -14929, -14928, -14926, -14922, -14921, -14914, -14908, -14902, -14894, -14889, -14882, -14873, -14871, -14857, + -14678, -14674, -14670, -14668, -14663, -14654, -14645, -14630, -14594, -14429, -14407, -14399, -14384, -14379, -14368, -14355, -14353, + -14345, -14170, -14159, -14151, -14149, -14145, -14140, -14137, -14135, -14125, -14123, -14122, -14112, -14109, -14099, -14097, -14094, + -14092, -14090, -14087, -14083, -13917, -13914, -13910, -13907, -13906, -13905, -13896, -13894, -13878, -13870, -13859, -13847, -13831, + -13658, -13611, -13601, -13406, -13404, -13400, -13398, -13395, -13391, -13387, -13383, -13367, -13359, -13356, -13343, -13340, -13329, + -13326, -13318, -13147, -13138, -13120, -13107, -13096, -13095, -13091, -13076, -13068, -13063, -13060, -12888, -12875, -12871, -12860, + -12858, -12852, -12849, -12838, -12831, -12829, -12812, -12802, -12607, -12597, -12594, -12585, -12556, -12359, -12346, -12320, -12300, + -12120, -12099, -12089, -12074, -12067, -12058, -12039, -11867, -11861, -11847, -11831, -11798, -11781, -11604, -11589, -11536, -11358, + -11340, -11339, -11324, -11303, -11097, -11077, -11067, -11055, -11052, -11045, -11041, -11038, -11024, -11020, -11019, -11018, -11014, + -10838, -10832, -10815, -10800, -10790, -10780, -10764, -10587, -10544, -10533, -10519, -10331, -10329, -10328, -10322, -10315, -10309, + -10307, -10296, -10281, -10274, -10270, -10262, -10260, -10256, -10254}; + public static String[] pystr = new String[] {"a", "ai", "an", "ang", "ao", "ba", "bai", "ban", "bang", "bao", "bei", "ben", "beng", "bi", "bian", + "biao", "bie", "bin", "bing", "bo", "bu", "ca", "cai", "can", "cang", "cao", "ce", "ceng", "cha", "chai", "chan", "chang", "chao", "che", + "chen", "cheng", "chi", "chong", "chou", "chu", "chuai", "chuan", "chuang", "chui", "chun", "chuo", "ci", "cong", "cou", "cu", "cuan", + "cui", "cun", "cuo", "da", "dai", "dan", "dang", "dao", "de", "deng", "di", "dian", "diao", "die", "ding", "diu", "dong", "dou", "du", + "duan", "dui", "dun", "duo", "e", "en", "er", "fa", "fan", "fang", "fei", "fen", "feng", "fo", "fou", "fu", "ga", "gai", "gan", "gang", + "gao", "ge", "gei", "gen", "geng", "gong", "gou", "gu", "gua", "guai", "guan", "guang", "gui", "gun", "guo", "ha", "hai", "han", "hang", + "hao", "he", "hei", "hen", "heng", "hong", "hou", "hu", "hua", "huai", "huan", "huang", "hui", "hun", "huo", "ji", "jia", "jian", + "jiang", "jiao", "jie", "jin", "jing", "jiong", "jiu", "ju", "juan", "jue", "jun", "ka", "kai", "kan", "kang", "kao", "ke", "ken", + "keng", "kong", "kou", "ku", "kua", "kuai", "kuan", "kuang", "kui", "kun", "kuo", "la", "lai", "lan", "lang", "lao", "le", "lei", "leng", + "li", "lia", "lian", "liang", "liao", "lie", "lin", "ling", "liu", "long", "lou", "lu", "lv", "luan", "lue", "lun", "luo", "ma", "mai", + "man", "mang", "mao", "me", "mei", "men", "meng", "mi", "mian", "miao", "mie", "min", "ming", "miu", "mo", "mou", "mu", "na", "nai", + "nan", "nang", "nao", "ne", "nei", "nen", "neng", "ni", "nian", "niang", "niao", "nie", "nin", "ning", "niu", "nong", "nu", "nv", "nuan", + "nue", "nuo", "o", "ou", "pa", "pai", "pan", "pang", "pao", "pei", "pen", "peng", "pi", "pian", "piao", "pie", "pin", "ping", "po", "pu", + "qi", "qia", "qian", "qiang", "qiao", "qie", "qin", "qing", "qiong", "qiu", "qu", "quan", "que", "qun", "ran", "rang", "rao", "re", + "ren", "reng", "ri", "rong", "rou", "ru", "ruan", "rui", "run", "ruo", "sa", "sai", "san", "sang", "sao", "se", "sen", "seng", "sha", + "shai", "shan", "shang", "shao", "she", "shen", "sheng", "shi", "shou", "shu", "shua", "shuai", "shuan", "shuang", "shui", "shun", + "shuo", "si", "song", "sou", "su", "suan", "sui", "sun", "suo", "ta", "tai", "tan", "tang", "tao", "te", "teng", "ti", "tian", "tiao", + "tie", "ting", "tong", "tou", "tu", "tuan", "tui", "tun", "tuo", "wa", "wai", "wan", "wang", "wei", "wen", "weng", "wo", "wu", "xi", + "xia", "xian", "xiang", "xiao", "xie", "xin", "xing", "xiong", "xiu", "xu", "xuan", "xue", "xun", "ya", "yan", "yang", "yao", "ye", "yi", + "yin", "ying", "yo", "yong", "you", "yu", "yuan", "yue", "yun", "za", "zai", "zan", "zang", "zao", "ze", "zei", "zen", "zeng", "zha", + "zhai", "zhan", "zhang", "zhao", "zhe", "zhen", "zheng", "zhi", "zhong", "zhou", "zhu", "zhua", "zhuai", "zhuan", "zhuang", "zhui", + "zhun", "zhuo", "zi", "zong", "zou", "zu", "zuan", "zui", "zun", "zuo"}; + private StringBuilder buffer; + private String resource; + private static CharacterParser characterParser = new CharacterParser(); + + public static CharacterParser getInstance() { + return characterParser; + } + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + /** * 汉字转成ASCII码 * * @param chs * @return */ + private int getChsAscii(String chs) { + int asc = 0; + try { + byte[] bytes = chs.getBytes("gb2312"); + if (bytes == null || bytes.length > 2 || bytes.length <= 0) { + throw new RuntimeException("illegal resource string"); + } + if (bytes.length == 1) { + asc = bytes[0]; + } + if (bytes.length == 2) { + int hightByte = 256 + bytes[0]; + int lowByte = 256 + bytes[1]; + asc = (256 * hightByte + lowByte) - 256 * 256; + } + } catch (Exception e) { + System.out.println("ERROR:ChineseSpelling.class-getChsAscii(String chs)" + e); + } + return asc; + } + + /** * 单字解析 * * @param str * @return */ + public String convert(String str) { + String result = null; + int ascii = getChsAscii(str); + if (ascii > 0 && ascii < 160) { + result = String.valueOf((char) ascii); + } else { + for (int i = (pyvalue.length - 1); i >= 0; i--) { + if (pyvalue[i] <= ascii) { + result = pystr[i]; + break; + } + } + } + return result; + } + + /** * 词组解析 * * @param chs * @return */ + public String getSelling(String chs) { + String key, value; + buffer = new StringBuilder(); + for (int i = 0; i < chs.length(); i++) { + key = chs.substring(i, i + 1); + if (key.getBytes().length >= 2) { + value = (String) convert(key); + if (value == null) { + value = "unknown"; + } + } else { + value = key; + } + buffer.append(value); + } + return buffer.toString(); + } + + public String getSpelling() { + return this.getSelling(this.getResource()); + } +} diff --git a/app/src/main/java/com/allen/androidcustomview/utils/PinyinComparator.java b/app/src/main/java/com/allen/androidcustomview/utils/PinyinComparator.java new file mode 100644 index 0000000..3e3474a --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/utils/PinyinComparator.java @@ -0,0 +1,22 @@ +package com.allen.androidcustomview.utils; + + +import com.allen.androidcustomview.bean.UserBean; + +import java.util.Comparator; + +public class PinyinComparator implements Comparator { + + @Override + public int compare(UserBean o1, UserBean o2) { + if (o1.getSortLetters().equals("@") + || o2.getSortLetters().equals("#")) { + return -1; + } else if (o1.getSortLetters().equals("#") + || o2.getSortLetters().equals("@")) { + return 1; + } else { + return o1.getSortLetters().compareTo(o2.getSortLetters()); + } + } +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/ArcView.kt b/app/src/main/java/com/allen/androidcustomview/widget/ArcView.kt new file mode 100644 index 0000000..ef06f5f --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/ArcView.kt @@ -0,0 +1,87 @@ +package com.allen.androidcustomview.widget + +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import android.view.View +import com.allen.androidcustomview.R + +/** + *
+ *      @author : Allen
+ *      e-mail  : lygttpod@163.com
+ *      date    : 2019/07/07
+ *      desc    :
+ * 
+ */ +class ArcView : View { + private var mWidth = 0 + private var mHeight = 0 + /** + * 弧形高度 + */ + private var mArcHeight = 0 + /** + * 背景颜色 + */ + private var mBgColor = Color.WHITE + private var mPaint = Paint() + private var mContext: Context? = null + + private var mPath = Path() + + constructor(context: Context) : this(context, null) + constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + initView(context, attrs, defStyleAttr) + } + + private fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ArcView) + mArcHeight = typedArray.getDimensionPixelSize(R.styleable.ArcView_arcHeight, 0) + mBgColor = typedArray.getColor(R.styleable.ArcView_bgColor, Color.WHITE) + typedArray.recycle() + + mContext = context + mPaint.style = Paint.Style.FILL_AND_STROKE + mPaint.color = mBgColor + mPaint.isAntiAlias = true + mPaint.strokeWidth = 1f + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + mWidth = w + mHeight = h + resetPath() + } + + private fun resetPath() { + mPath.reset() + mPath.moveTo(0f, 0f) + mPath.lineTo(0f, (mHeight - mArcHeight).toFloat()) + mPath.quadTo((mWidth / 2).toFloat(), (mHeight + mArcHeight).toFloat(), mWidth.toFloat(), (mHeight - mArcHeight).toFloat()) + mPath.lineTo(mWidth.toFloat(), 0f) + mPath.close() + } + + override fun onDraw(canvas: Canvas?) { + super.onDraw(canvas) + canvas?.drawPath(mPath, mPaint) + } + + fun setArcViewBgColor(color: Int) { + mBgColor = color + mPaint.color = color + invalidate() + } + + fun getArcViewBgColor() = mBgColor + + fun setArcViewHeight(height: Int) { + if (height == mHeight) return + mHeight = height + resetPath() + invalidate() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/widget/CarMoveView.kt b/app/src/main/java/com/allen/androidcustomview/widget/CarMoveView.kt new file mode 100644 index 0000000..d88eeee --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/CarMoveView.kt @@ -0,0 +1,159 @@ +package com.allen.androidcustomview.widget + +import android.animation.Animator +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.* +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import com.allen.androidcustomview.R +import kotlin.math.atan2 + + +/** + *
+ *      @author : Allen
+ *      date    : 2019/07/23
+ *      desc    : 小汽车跟随轨迹运动
+ * 
+ */ +class CarMoveView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { + + private var mWidth = 0 + private var mHeight = 0 + + /** + * 背景颜色 + */ + private var mPathColor = Color.RED + private var mPaint = Paint() + private var mCarPaint = Paint() + + private var mRect = Rect() + private var mMovePath = Path() + + private var mStartX = 0f + private var mStartY = 0f + + private var pathMeasure: PathMeasure = PathMeasure() + + private val pos = FloatArray(2) + private val tan = FloatArray(2) + + private var isMoveCar = false + + private var mRectWidth = 30 + + private var mDuration = 5 + private var mCarDrawableRes: Drawable? = null + private var mCarBitmapRes: Bitmap? = null + + init { + initView(context, attrs, defStyleAttr) + initPaint() + mCarBitmapRes = (mCarDrawableRes as? BitmapDrawable)?.bitmap + } + + private fun initView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarMoveView) + mPathColor = typedArray.getColor(R.styleable.CarMoveView_carMovePathColor, mPathColor) + mDuration = typedArray.getInt(R.styleable.CarMoveView_carMoveDuration, mDuration) + mCarDrawableRes = typedArray.getDrawable(R.styleable.CarMoveView_carMoveDrawableRes) + typedArray.recycle() + } + + private fun initPaint() { + mPaint.color = mPathColor + mPaint.style = Paint.Style.STROKE + mPaint.isAntiAlias = true + + mCarPaint.color = mPathColor + mCarPaint.style = Paint.Style.FILL + mCarPaint.isAntiAlias = true + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + mWidth = w + mHeight = h + } + + override fun onDraw(canvas: Canvas?) { + super.onDraw(canvas) + drawPath(canvas) + drawMoveCar(canvas) + } + + private fun drawPath(canvas: Canvas?) { + canvas?.drawPath(mMovePath, mPaint) + } + + private fun drawMoveCar(canvas: Canvas?) { + pathMeasure.setPath(mMovePath, false) + if (isMoveCar) { + // 计算图片旋转角度 + val degrees = (atan2(tan[1].toDouble(), tan[0].toDouble()) * 180.0 / Math.PI).toFloat() + canvas?.rotate(degrees, pos[0], pos[1]) + //小车中心点在运行轨道上 + mRect.set((pos[0] - mRectWidth).toInt(), (pos[1] - mRectWidth).toInt(), (pos[0] + mRectWidth).toInt(), (pos[1] + mRectWidth).toInt()) + //小车轮子在运行轨道上 + //mRect.set((pos[0] - mRectWidth).toInt(), (pos[1] - mRectWidth * 2).toInt(), (pos[0] + mRectWidth).toInt(), (pos[1]).toInt()) + if (mCarBitmapRes == null) { + canvas?.drawRect(mRect, mCarPaint) + } else { + canvas?.drawBitmap(mCarBitmapRes, null, mRect, mPaint) + } + } + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + mStartX = event.x + mStartY = event.y + mMovePath.reset() + mMovePath.moveTo(mStartX, mStartY) + return true + } + MotionEvent.ACTION_MOVE -> { + val endX = (mStartX + event.x) / 2 + val endY = (mStartY + event.y) / 2 + mMovePath.quadTo(mStartX, mStartY, endX, endY) + mStartX = event.x + mStartY = event.y + invalidate() + return true + } + } + return super.onTouchEvent(event) + } + + fun startAnim() { + isMoveCar = true + val valueAnimator = ValueAnimator.ofFloat(0f, pathMeasure.length) + valueAnimator.duration = mDuration * 1000L + valueAnimator.addUpdateListener { + val distance: Float = it.animatedValue as Float + pathMeasure.getPosTan(distance, pos, tan) + invalidate() + } + valueAnimator.addListener(object : Animator.AnimatorListener { + override fun onAnimationRepeat(animation: Animator?) {} + + override fun onAnimationEnd(animation: Animator?) { + isMoveCar = false + } + + override fun onAnimationCancel(animation: Animator?) { + isMoveCar = false + } + + override fun onAnimationStart(animation: Animator?) {} + + }) + valueAnimator.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/widget/ClearScreenView.kt b/app/src/main/java/com/allen/androidcustomview/widget/ClearScreenView.kt new file mode 100644 index 0000000..3b5e0bf --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/ClearScreenView.kt @@ -0,0 +1,371 @@ +package com.allen.androidcustomview.widget + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.VelocityTracker +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import kotlin.math.abs + + +/** + *
+ *      @author : Allen
+ *      e-mail  : lygttpod@163.com
+ *      date    : 2021/01/28
+ *      desc    : 滑动清屏view
+ * 
+ */ +enum class ClearScreenType { + LEFT_TO_RIGHT,//从左滑到右清屏 + RIGHT_TO_LEFT //从右滑到左清屏 +} + +enum class ClearScreenStatus { + NORMAL,//正常状态 + CLEARED//已经清屏状态 +} + +enum class ClearScreenMode { + QUICK_SCROLL,//快速滑动才触发清屏 + SLOW_SCROLL//滑动出发清屏 +} + +class ClearScreenView @JvmOverloads constructor(private val mContext: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(mContext, attrs, defStyleAttr) { + + companion object { + /** + * 最小移动距离 + */ + private const val MIN_SCROLL_SIZE = 50 + + /** + * 水平方向最小滑动速度 + */ + private const val MIN_X_VELOCITY = 10 + + /** + * 清屏动画时长 + */ + private const val DURATION = 300L + } + + /** + * 手指按下的x轴位置 + */ + private var mDownX = 0 + + /** + * 手指按下的y轴位置 + */ + private var mDownY = 0 + + /** + * 清屏view清屏时需要在x轴的偏移量 + */ + private var translateX = 0 + + /** + * 清屏view起始偏移量(例如:从无到有迁移量从-width到0) + */ + private var startTranslateX = 0 + + /** + * 滑动速度对象 + */ + private var mVelocityTracker: VelocityTracker? = null + + /** + * 清屏动画对象 + */ + private var mAnimator: ValueAnimator? = null + + /** + * 需要清除的Views + */ + private var listClearViews: ArrayList = ArrayList() + + /** + * 清屏事件 + */ + private var clearScreenListener: OnClearScreenListener? = null + + /** + * 清屏类型 左滑清屏 or 右滑清屏 (默认从左滑到右清屏) + */ + private var clearScreenType = ClearScreenType.LEFT_TO_RIGHT + + /** + * 当前清屏状态 + */ + private var clearScreenStatus = ClearScreenStatus.NORMAL + + /** + * 清屏模式 + */ + var clearScreenMode = ClearScreenMode.QUICK_SCROLL + + /** + * 是否正在处在滑动清屏状态 + */ + private var isScrolling = false + + init { + initView() + initAnim() + } + + private fun initView() { + val view = View(mContext) + view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + view.isClickable = true + addView(view, 0) + } + + private fun initAnim() { + mVelocityTracker = VelocityTracker.obtain() + mAnimator = ValueAnimator.ofFloat(0f, 1.0f).setDuration(DURATION) + mAnimator?.addUpdateListener { + val value = it.animatedValue as Float + val translate = startTranslateX + value * translateX + translateChild(translate) + } + mAnimator?.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + when (clearScreenStatus) { + ClearScreenStatus.CLEARED -> { + clearScreenStatus = ClearScreenStatus.NORMAL + clearScreenListener?.onRestored() + } + ClearScreenStatus.NORMAL -> { + clearScreenStatus = ClearScreenStatus.CLEARED + clearScreenListener?.onCleared() + } + } + isScrolling = false + } + }) + } + + override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { + if (clearScreenListener?.isForbidClearScreen() == true) { + return super.onInterceptTouchEvent(ev) + } + val x = ev.x.toInt() + val y = ev.y.toInt() + when (ev.action) { + MotionEvent.ACTION_DOWN -> { + mDownX = x + mDownY = y + } + } + return isInterceptClearScreenEvent(x, y) + } + + private fun isInterceptClearScreenEvent(x: Int, y: Int): Boolean { + return when (clearScreenMode) { + ClearScreenMode.QUICK_SCROLL -> { + val isIntercept = isMoveForHorizontal(x, y) && !isAnimRunning() && isGreaterThanMinSize(mDownX, x) + requestDisallowInterceptTouchEvent(isIntercept) + isIntercept + } + ClearScreenMode.SLOW_SCROLL -> { + val isIntercept = isMoveForHorizontal(x, y) && !isAnimRunning() || isScrolling + requestDisallowInterceptTouchEvent(isIntercept) + isIntercept + } + } + } + + /** + * 是否水平方向滑动 + */ + private fun isMoveForHorizontal(x: Int, y: Int): Boolean { + return abs(x - mDownX) > abs(y - mDownY) + } + + private fun isAnimRunning(): Boolean { + return mAnimator?.isRunning == true + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + if (isAnimRunning()) return true + when (clearScreenMode) { + ClearScreenMode.QUICK_SCROLL -> { + handleQuickScrollMode(event) + } + ClearScreenMode.SLOW_SCROLL -> { + handleSlowScrollMode(event) + } + } + return true + } + + private fun handleQuickScrollMode(event: MotionEvent) { + mVelocityTracker?.addMovement(event) + when (event.action) { + MotionEvent.ACTION_UP -> { + mVelocityTracker?.computeCurrentVelocity(10) + val xVelocity = mVelocityTracker?.xVelocity ?: 0f + when { + //从左往右滑动(是正数)速度大于阈值 + xVelocity > MIN_X_VELOCITY -> { + setLeft2RightMoveTranslateX() + } + //从右往左滑动(是负数)速度大于阈值 + xVelocity < -MIN_X_VELOCITY -> { + setRight2LeftMoveTranslateX() + } + else -> { + translateX = 0 + } + } + if (translateX != 0) { + mAnimator?.start() + } + } + } + } + + private fun handleSlowScrollMode(event: MotionEvent) { + val x = event.x.toInt() + when (event.action) { + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + when (clearScreenStatus) { + ClearScreenStatus.NORMAL -> { + if (startTranslateX >= width / 2) { + translateX = width - startTranslateX + clearScreenStatus = ClearScreenStatus.NORMAL + } else { + translateX = -startTranslateX + clearScreenStatus = ClearScreenStatus.CLEARED + } + mAnimator?.start() + } + ClearScreenStatus.CLEARED -> { + if (startTranslateX >= width / 2) { + translateX = width - startTranslateX + clearScreenStatus = ClearScreenStatus.NORMAL + } else { + translateX = (-startTranslateX) + clearScreenStatus = ClearScreenStatus.CLEARED + } + mAnimator?.start() + } + } + } + MotionEvent.ACTION_MOVE -> { + translateX = 0 + val move = x - mDownX + when (clearScreenStatus) { + ClearScreenStatus.NORMAL -> { + val translate = if (x <= mDownX) 0 else move + startTranslateX = translate + if (translate != 0) { + translateChild(translate.toFloat()) + } + } + ClearScreenStatus.CLEARED -> { + val translate = if (x > mDownX) 0 else mDownX - x + startTranslateX = width - translate + if (startTranslateX != 0) { + translateChild(startTranslateX.toFloat()) + } + } + } + } + } + } + + private fun setLeft2RightMoveTranslateX() { + when (clearScreenType) { + ClearScreenType.LEFT_TO_RIGHT -> { + startTranslateX = 0 + translateX = if (clearScreenStatus == ClearScreenStatus.NORMAL) width else 0 + } + ClearScreenType.RIGHT_TO_LEFT -> { + startTranslateX = -width + translateX = if (clearScreenStatus == ClearScreenStatus.NORMAL) 0 else width + } + } + } + + private fun setRight2LeftMoveTranslateX() { + when (clearScreenType) { + ClearScreenType.LEFT_TO_RIGHT -> { + startTranslateX = width + translateX = if (clearScreenStatus == ClearScreenStatus.NORMAL) 0 else -width + } + ClearScreenType.RIGHT_TO_LEFT -> { + startTranslateX = 0 + translateX = if (clearScreenStatus == ClearScreenStatus.NORMAL) -width else 0 + } + } + } + + /** + * 是否大于清屏方向的最小值 + */ + private fun isGreaterThanMinSize(downX: Int, moveX: Int): Boolean { + return when (clearScreenType) { + ClearScreenType.LEFT_TO_RIGHT -> { + when (clearScreenStatus) { + ClearScreenStatus.NORMAL -> moveX - downX > MIN_SCROLL_SIZE + ClearScreenStatus.CLEARED -> downX - moveX > MIN_SCROLL_SIZE + } + } + ClearScreenType.RIGHT_TO_LEFT -> { + when (clearScreenStatus) { + ClearScreenStatus.NORMAL -> downX - moveX > MIN_SCROLL_SIZE + ClearScreenStatus.CLEARED -> moveX - downX > MIN_SCROLL_SIZE + } + } + } + } + + private fun translateChild(translate: Float) { + isScrolling = true + for (view in listClearViews) { + view.translationX = translate + } + } + + fun addClearViews(views: List) { + for (view in views) { + if (!listClearViews.contains(view)) { + listClearViews.add(view) + } + } + } + + fun addClearView(view: View) { + if (!listClearViews.contains(view)) { + listClearViews.add(view) + } + } + + fun removeClearViews(views: List) { + for (view in views) { + listClearViews.remove(view) + } + } + + fun removeAllClearViews() { + listClearViews.clear() + } + + fun setOnClearScreenListener(listener: OnClearScreenListener?) { + this.clearScreenListener = listener + } + + interface OnClearScreenListener { + fun onCleared() + fun onRestored() + fun isForbidClearScreen(): Boolean = false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/widget/DividerItemDecoration.java b/app/src/main/java/com/allen/androidcustomview/widget/DividerItemDecoration.java new file mode 100644 index 0000000..040e988 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/DividerItemDecoration.java @@ -0,0 +1,117 @@ +package com.allen.androidcustomview.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/02/05
+ *      desc    : recyclerview的分割线
+ *      version : 1.0
+ * 
+ */ + +public class DividerItemDecoration extends RecyclerView.ItemDecoration { + private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; + + public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; + + public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; + + + private Drawable mDivider; + + private int mOrientation; + + private Context context; + + public DividerItemDecoration(Context context, int orientation) { + final TypedArray a = context.obtainStyledAttributes(ATTRS); + mDivider = a.getDrawable(0); + this.context = context; + a.recycle(); + setOrientation(orientation); + } + + public void setOrientation(int orientation) { + if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { + throw new IllegalArgumentException("invalid orientation"); + } + mOrientation = orientation; + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + super.onDraw(c, parent, state); + if (mOrientation == VERTICAL_LIST) { + drawVertical(c, parent); + } else { + drawHorizontal(c, parent); + } + } + + public void drawVertical(Canvas c, RecyclerView parent) { + final int left = parent.getPaddingLeft() + dip2px(context, 0); + final int right = parent.getWidth() - parent.getPaddingRight() - dip2px(context, 0); + + final int childCount = parent.getChildCount(); + + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView( + parent.getContext()); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child + .getLayoutParams(); + final int top = child.getBottom() + params.bottomMargin; + final int bottom = top + mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + public void drawHorizontal(Canvas c, RecyclerView parent) { + final int top = parent.getPaddingTop(); + final int bottom = parent.getHeight() - parent.getPaddingBottom(); + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child + .getLayoutParams(); + final int left = child.getRight() + params.rightMargin; + final int right = left + mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + if (mOrientation == VERTICAL_LIST) { + outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); + } else { + outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); + } + } + + /** + * 单位转换工具类 + * + * @param context 上下文对象 + * @param dipValue 值 + * @return 返回值 + */ + private int dip2px(Context context, float dipValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dipValue * scale + 0.5f); + } +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/HorizontalProgressBar.java b/app/src/main/java/com/allen/androidcustomview/widget/HorizontalProgressBar.java index e418c2a..e18d347 100644 --- a/app/src/main/java/com/allen/androidcustomview/widget/HorizontalProgressBar.java +++ b/app/src/main/java/com/allen/androidcustomview/widget/HorizontalProgressBar.java @@ -345,6 +345,7 @@ public void onAnimationUpdate(ValueAnimator valueAnimator) { moveDis = currentProgress - tipWidth / 2; } invalidate(); + setCurrentProgress(value); } }); progressAnimator.start(); @@ -373,6 +374,15 @@ public HorizontalProgressBar setCurrentProgress(float progress) { mProgress = progress; currentProgress = progress * mWidth / 100; textString = formatNum(format2Int(progress)); + + //移动百分比提示框,只有当前进度到提示框中间位置之后开始移动, + //当进度框移动到最右边的时候停止移动,但是进度条还可以继续移动 + //moveDis是tip框移动的距离 + if (currentProgress >= (tipWidth / 2) && + currentProgress <= (mWidth - tipWidth / 2)) { + moveDis = currentProgress - tipWidth / 2; + } + invalidate(); return this; } diff --git a/app/src/main/java/com/allen/androidcustomview/widget/HoverItemDecoration.java b/app/src/main/java/com/allen/androidcustomview/widget/HoverItemDecoration.java new file mode 100644 index 0000000..46570b2 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/HoverItemDecoration.java @@ -0,0 +1,223 @@ +package com.allen.androidcustomview.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.util.TypedValue; +import android.view.View; + + +/** + *
+ *      @author : Allen
+ *      e-mail  : lygttpod@163.com
+ *      date    : 2018/04/15
+ *      desc    : 悬停吸顶效果的ItemDecoration
+ *      version : 1.0
+ * 
+ */ +public class HoverItemDecoration extends RecyclerView.ItemDecoration { + + private Context context; + + private int width; + + /** + * 分组item的高度 + */ + private int itemHeight; + /** + * 分割线的高度 + */ + private int itemDivideHeight; + /** + * 分组text距离左边的距离 + */ + private int itemTextPaddingLeft; + + /** + * 分组item的画笔 + */ + private Paint itemPaint; + /** + * 分组item的颜色 + */ + private int itemHoverPaintColor=0xFFf4f4f4; + /** + * 分组文字的颜色 + */ + private int textPaintColor=0xFF999999; + + /** + * 悬停item的画笔 + */ + private Paint itemHoverPaint; + /** + * 文字的画笔 + */ + private Paint textPaint; + /** + * 绘制文字的矩形边框 + */ + private Rect textRect = new Rect(); + /** + * 分组字母的回调(一般是取的分组的大写字母) + */ + private BindItemTextCallback bindItemTextCallback; + + + public HoverItemDecoration(Context content, BindItemTextCallback bindItemTextCallback) { + this.context = content; + this.bindItemTextCallback = bindItemTextCallback; + + width = content.getResources().getDisplayMetrics().widthPixels; + + itemPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + itemPaint.setColor(itemHoverPaintColor); + + itemHoverPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + itemHoverPaint.setColor(itemHoverPaintColor); + + itemHeight = dp2px(30); + itemTextPaddingLeft = dp2px(20); + itemDivideHeight = dp2px(1); + + textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(textPaintColor); + textPaint.setTextSize(sp2px(15)); + + + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + super.onDraw(c, parent, state); + int count = parent.getChildCount(); + for (int i = 0; i < count; i++) { + View view = parent.getChildAt(i); + //分组item的顶部和底部 + int itemTop = view.getTop() - itemHeight; + int itemBottom = view.getTop(); + + //可见item在adapter中真实的位置 + int position = parent.getChildAdapterPosition(view); + + //获取回调的分组文字(一般是字母) + String text = bindItemTextCallback.getItemText(position); + + //如果是一组中第一个的话绘制出分组的item和文字,否则绘制分割线 + if (isFirstInGroup(position)) { + c.drawRect(0, itemTop, width, itemBottom, itemPaint); + drawText(c, itemTop, itemBottom, text); + } else { + c.drawRect(0, view.getTop() - itemDivideHeight, width, view.getTop(), itemHoverPaint); + } + + } + } + + @Override + public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { + super.onDrawOver(c, parent, state); + //绘制悬停的view + + int count = parent.getChildCount(); + + if (count > 0) { + //悬停只是第一个位置悬停,所以只取第一个view进行设置 + View firstView = parent.getChildAt(0); + + int position = parent.getChildAdapterPosition(firstView); + String text = bindItemTextCallback.getItemText(position); + + //如果悬停view的底部小于悬停布局的高度说明正在上滑,就让他随着滑动逐渐滑进去,否则就固定悬停位置不边 + //isFirstInGroup(position+1)是下一个item是另外分组第一个的时候当前item才滚动上去 + if (firstView.getBottom() <= itemHeight && isFirstInGroup(position + 1)) { + c.drawRect(0, 0, width, firstView.getBottom(), itemHoverPaint); + drawText(c, firstView.getBottom() - itemHeight, firstView.getBottom(), text); + } else { + c.drawRect(0, 0, width, itemHeight, itemHoverPaint); + drawText(c, 0, itemHeight, text); + } + } + + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + int position = parent.getChildAdapterPosition(view); + //如果是分组第一个就留出绘制item的高度 + if (isFirstInGroup(position)) { + outRect.top = itemHeight; + }else { + outRect.top = itemDivideHeight; + } + + } + + + /** + * 绘制文字 + * + * @param canvas 画布 + */ + private void drawText(Canvas canvas, int itemTop, int itemBottom, String textString) { + + textRect.left = itemTextPaddingLeft; + textRect.top = itemTop; + textRect.right = textString.length(); + textRect.bottom = itemBottom; + + Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt(); + int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2; + //文字绘制到整个布局的中心位置 + canvas.drawText(textString, textRect.left, baseline, textPaint); + } + + + private boolean isFirstInGroup(int position) { + if (position == 0) { + return true; + } else { + String prevItemText = bindItemTextCallback.getItemText(position - 1); + String currentItemText = bindItemTextCallback.getItemText(position); + //上一个和当前位置的值一样说明是同一个组的否则就是新的一组 + if (prevItemText.equals(currentItemText)) { + return false; + } else { + return true; + } + + } + } + + public interface BindItemTextCallback { + String getItemText(int position); + } + + /** + * dp 2 px + * + * @param dpVal + */ + protected int dp2px(int dpVal) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + dpVal, context.getResources().getDisplayMetrics()); + } + + /** + * sp 2 px + * + * @param spVal + * @return + */ + protected int sp2px(int spVal) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, + spVal, context.getResources().getDisplayMetrics()); + + } + +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/IndexView.java b/app/src/main/java/com/allen/androidcustomview/widget/IndexView.java new file mode 100644 index 0000000..17eaa3d --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/IndexView.java @@ -0,0 +1,154 @@ +package com.allen.androidcustomview.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.TextView; + +/** + * Created by allen on 2016/10/26. + */ + +public class IndexView extends View { + + private Context mContext; + private TextView mShowTextDialog; + + + private Paint mPaint; + + private int mWidth; + private int mHeight; + + private int mCellWidth; + private int mCellHeight; + + private int mWordSize; + private int mwordColor; + private int mChoose = -1;// 选中 + + + private int GRAY = 0xFFe8e8e8; + private int DEFAULT_TEXT_COLOR = 0xFF999999; + + private static final String[] WORDS = new String[]{ + "↑", "☆", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", + "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "#" + }; + + private OnTouchingLetterChangedListener mOnTouchingLetterChangedListener; + + public IndexView(Context context) { + this(context, null); + } + + public IndexView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public IndexView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mContext = context; + mWordSize = sp2px(mContext, 12); + mwordColor = 0; + initPaint(); + } + + + public void setShowTextDialog(TextView textDialog) { + this.mShowTextDialog = textDialog; + } + + private void initPaint() { + mPaint = new Paint(); + mPaint.setColor(DEFAULT_TEXT_COLOR); + mPaint.setAntiAlias(true); + mPaint.setTextSize(mWordSize); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + mWidth = getMeasuredWidth(); + mHeight = getMeasuredHeight(); + + mCellHeight = mHeight / WORDS.length; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + for (int i = 0; i < WORDS.length; i++) { + float xPos = mWidth / 2 - mPaint.measureText(WORDS[i]) / 2; + float yPos = mCellHeight * i + mCellHeight; + canvas.drawText(WORDS[i], xPos, yPos, mPaint); + } + + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final int action = event.getAction(); + final float y = event.getY();// 点击y坐标 + final int oldChoose = mChoose; + final int c = (int) (y / getHeight() * WORDS.length);// 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数. + + + switch (action) { + case MotionEvent.ACTION_UP: + setBackgroundColor(0x00000000); + mChoose = -1;// + invalidate(); + if (mShowTextDialog != null) { + mShowTextDialog.setVisibility(View.INVISIBLE); + } + break; + + default: + setBackgroundColor(GRAY); + if (oldChoose != c) { + if (c >= 0 && c < WORDS.length) { + if (mOnTouchingLetterChangedListener != null) { + mOnTouchingLetterChangedListener.onTouchingLetterChanged(WORDS[c]); + } + if (mShowTextDialog != null) { + mShowTextDialog.setText(WORDS[c]); + mShowTextDialog.setVisibility(View.VISIBLE); + } + + mChoose = c; + invalidate(); + } + } + + break; + } + return true; + } + + + public void setOnTouchingLetterChangedListener(OnTouchingLetterChangedListener letterChangedListener) { + mOnTouchingLetterChangedListener = letterChangedListener; + + } + + public interface OnTouchingLetterChangedListener { + void onTouchingLetterChanged(String letter); + } + + /** + * 文字字体大小sp转换px + * + * @param context 上下文对象 + * @param spValue sp的值 + * @return 返回值 + */ + public int sp2px(Context context, float spValue) { + final float scale = context.getResources().getDisplayMetrics().scaledDensity; + return (int) (spValue * scale + 0.5f); + } +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/IndicatorView.java b/app/src/main/java/com/allen/androidcustomview/widget/IndicatorView.java new file mode 100644 index 0000000..251dad0 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/IndicatorView.java @@ -0,0 +1,240 @@ +package com.allen.androidcustomview.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/02/27
+ *      desc    : 首页banner指示器
+ *      version : 1.0
+ * 
+ */ + +public class IndicatorView extends View { + + private int mWidth; + private int mHeight; + + private float startX; + + /** + * 动画起点x坐标 + */ + private int centerX; + + /** + * 动画起点y坐标 + */ + private int centerY; + + /** + * view真实高度 + */ + private int mViewHeight; + + private int mViewWidth; + + /** + * 画笔宽度(等于vie高度) + */ + private int paintWidth; + + private int radius; + private int R; + + /** + * 圆的颜色 + */ + private int circleBgColor = 0xFFCED3D6; + + /** + * 当前指示器的颜色 + */ + private int currentColor = 0xFFA0946C; + + + /** + * 圆画笔 + */ + private Paint circlePaint; + /** + * 当前指示器的画笔 + */ + private Paint indicatorPaint; + + + private int pointNum = 0; + private int currentIndex = 0; + /** + * 圆之间的间距 + */ + private int dis; + + private RectF rectF; + + public IndicatorView(Context context) { + this(context, null); + } + + public IndicatorView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public IndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + rectF = new RectF(); + paintWidth = dp2px(1); + mViewHeight = dp2px(8); + radius = dp2px(3); + R = 2 * radius; + dis = 2 * radius; + circlePaint = getPaint(paintWidth, circleBgColor, Paint.Style.FILL); + indicatorPaint = getPaint(paintWidth, currentColor, Paint.Style.FILL); + } + + + /** + * 统一处理paint + * + * @param strokeWidth 画笔宽度 + * @param color 颜色 + * @param style 风格 + * @return paint + */ + private Paint getPaint(int strokeWidth, int color, Paint.Style style) { + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setStrokeWidth(strokeWidth); + paint.setColor(color); + paint.setAntiAlias(true); + paint.setStyle(style); + return paint; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mWidth = w; + mHeight = h; + + centerX = w / 2; + centerY = h / 2; + + mViewWidth = (2 * pointNum + 1) * R; + + startX = (mWidth - mViewWidth) / 2; + + } + + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(measureWidth(widthMode, width), measureHeight(heightMode, height)); + } + + /** + * 测量宽度 + * + * @param mode + * @param width + * @return + */ + private int measureWidth(int mode, int width) { + switch (mode) { + case MeasureSpec.UNSPECIFIED: + case MeasureSpec.AT_MOST: + break; + case MeasureSpec.EXACTLY: + mWidth = width; + break; + } + return mWidth; + } + + /** + * 测量高度 + * + * @param mode + * @param height + * @return + */ + private int measureHeight(int mode, int height) { + switch (mode) { + case MeasureSpec.UNSPECIFIED: + case MeasureSpec.AT_MOST: + mHeight = mViewHeight; + break; + case MeasureSpec.EXACTLY: + mHeight = height; + break; + } + return mHeight; + } + + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + for (int i = 0; i < pointNum + 1; i++) { + drawCircle(canvas, i); + } + drawCurrentIndicator(canvas, currentIndex); + } + + + private void drawCircle(Canvas canvas, int i) { + float x = startX + radius + i * 2 * dis; + canvas.drawCircle(x, centerY, radius, circlePaint); + } + + + private void drawCurrentIndicator(Canvas canvas, int i) { + float x = startX + 2 * i * dis; + rectF.left = x; + rectF.top = centerY - radius; + rectF.right = x + 3 * dis; + rectF.bottom = centerY + radius; + canvas.drawRoundRect(rectF, radius, radius, indicatorPaint); + } + + + public IndicatorView setPointNum(int pointNum) { + this.pointNum = pointNum; + return this; + } + + public IndicatorView setCurrentPosition(int position) { + currentIndex = position; + invalidate(); + return this; + } + + /** + * dp 2 px + * + * @param dpVal dp + */ + protected int dp2px(int dpVal) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + dpVal, getResources().getDisplayMetrics()); + } +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/StudyPlanProgressView.kt b/app/src/main/java/com/allen/androidcustomview/widget/StudyPlanProgressView.kt new file mode 100644 index 0000000..468cf0f --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/StudyPlanProgressView.kt @@ -0,0 +1,163 @@ +package com.allen.androidcustomview.widget + +import android.content.Context +import android.graphics.* +import android.graphics.drawable.BitmapDrawable +import android.util.AttributeSet +import android.util.TypedValue +import android.view.View +import com.allen.androidcustomview.R +import kotlin.math.min + +/** + *
+ *      @author : Allen
+ *      date    : 2019/08/12
+ *      desc    : 学习计划view
+ * 
+ */ +class StudyPlanProgressView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { + private var mWidth = 0 + private var mHeight = 0 + + private var iconPaint: Paint? = null + private var linePaint: Paint = Paint() + private var textPaint: Paint = Paint() + private var iconRect = Rect() + private var textRectF = Rect() + + private val ALL_POINT_SIZE = 7 + private var cellWidth = 0 + private var iconSize = 0 + private var textSize = 0 + private var textColor = 0 + private var progressWidth = 0 + private var uncheckedProgressColor = Color.GRAY + private var checkedProgressColor = Color.YELLOW + + private var iconUncheckedBitmapRes: Bitmap? = null + private var iconCheckedBitmapRes: Bitmap? = null + + private var dates: MutableList = mutableListOf() + + private var marginLeftAndRight = 0 + + init { + marginLeftAndRight = dp2px(27f).toInt() + iconSize = dp2px(14f).toInt() + initAttr(context, attrs, defStyleAttr) + initPaint() + } + + private fun initAttr(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.StudyPlanProgressView) + iconUncheckedBitmapRes = (typedArray.getDrawable(R.styleable.StudyPlanProgressView_sppv_iconUnchecked) as? BitmapDrawable)?.bitmap + iconCheckedBitmapRes = (typedArray.getDrawable(R.styleable.StudyPlanProgressView_sppv_iconChecked) as? BitmapDrawable)?.bitmap + iconSize = typedArray.getDimensionPixelOffset(R.styleable.StudyPlanProgressView_sppv_iconSize, dp2px(14f).toInt()) + progressWidth = typedArray.getDimensionPixelOffset(R.styleable.StudyPlanProgressView_sppv_progressWidth, dp2px(2f).toInt()) + uncheckedProgressColor = typedArray.getColor(R.styleable.StudyPlanProgressView_sppv_uncheckedProgressColor, uncheckedProgressColor) + checkedProgressColor = typedArray.getColor(R.styleable.StudyPlanProgressView_sppv_checkedProgressColor, checkedProgressColor) + textSize = typedArray.getDimensionPixelSize(R.styleable.StudyPlanProgressView_sppv_textSize, sp2px(11)) + textColor = typedArray.getColor(R.styleable.StudyPlanProgressView_sppv_textColor, checkedProgressColor) + + typedArray.recycle() + } + + private fun initPaint() { + iconPaint = Paint(Paint.ANTI_ALIAS_FLAG) + iconPaint?.isFilterBitmap = true + iconPaint?.isDither = true + + linePaint.strokeWidth = progressWidth.toFloat() + linePaint.color = uncheckedProgressColor + linePaint.isAntiAlias = true + linePaint.style = Paint.Style.FILL_AND_STROKE + + textPaint.textSize = textSize.toFloat() + textPaint.color = textColor + textPaint.textAlign = Paint.Align.CENTER + textPaint.isAntiAlias = true + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + mWidth = w + mHeight = h + + cellWidth = (mWidth - marginLeftAndRight * 2 - iconSize * ALL_POINT_SIZE) / (ALL_POINT_SIZE - 1) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + drawProgressLine(canvas) + drawProgress(canvas) + } + + private fun drawProgressLine(canvas: Canvas) { + val lineStart = (marginLeftAndRight + iconSize / 2).toFloat() + linePaint.color = uncheckedProgressColor + canvas.drawLine(lineStart, (mHeight / 2).toFloat(), (mWidth - marginLeftAndRight - iconSize / 2).toFloat(), (mHeight / 2).toFloat(), linePaint) + if (getDataSize() == 0) return + val lineEnd = lineStart + (getDataSize() - 1) * (cellWidth + iconSize) + linePaint.color = checkedProgressColor + canvas.drawLine(lineStart, (mHeight / 2).toFloat(), lineEnd, (mHeight / 2).toFloat(), linePaint) + } + + private fun drawProgress(canvas: Canvas) { + if (iconUncheckedBitmapRes == null || iconCheckedBitmapRes == null) return + for (i in 0 until ALL_POINT_SIZE) { + val left = marginLeftAndRight + (cellWidth + iconSize) * i + iconRect.set(left, (mHeight - iconSize) / 2, left + iconSize, (mHeight + iconSize) / 2) + val bitmap = if (isFinished(i)) iconCheckedBitmapRes else iconUncheckedBitmapRes + canvas.drawBitmap(bitmap, null, iconRect, iconPaint) + drawText(canvas, i) + } + } + + private fun isFinished(position: Int): Boolean { + return if (position <= getDataSize() - 1) { + dates[position].isFinished + } else false + } + + private fun drawText(canvas: Canvas, position: Int) { + if (getDataSize() == 0) return + if (position < getDataSize()) { + val textWidth = textPaint.measureText(dates[position].content).toInt() + val isTop = position % 2 == 0 + val left = marginLeftAndRight + iconSize / 2 + (cellWidth + iconSize) * position - textWidth / 2 + textRectF.left = left + textRectF.right = left + textWidth + textRectF.top = if (isTop) 0 else (mHeight + iconSize) / 2 + textRectF.bottom = if (isTop) (mHeight - iconSize) / 2 else mHeight + + val fontMetrics = textPaint.fontMetricsInt + val baseline = (textRectF.bottom + textRectF.top - fontMetrics.bottom - fontMetrics.top) / 2 + //文字绘制到整个布局的中心位置 + canvas.drawText(dates[position].content, textRectF.centerX().toFloat(), baseline.toFloat(), textPaint) + } + } + + fun setData(date: List?) { + dates.clear() + val timeList = date?.toMutableList() ?: mutableListOf() + timeList.forEach { + dates.add(ProgressData(it, true)) + } + invalidate() + } + + private fun getDataSize() = min(dates.size, 7) + + fun dp2px(dpVal: Float): Float { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + dpVal, resources.displayMetrics) + } + + fun sp2px(spVal: Int): Int { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, + spVal.toFloat(), resources.displayMetrics).toInt() + } + + class ProgressData(var content: String, var isFinished: Boolean) +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/widget/SuperDividerItemDecoration.java b/app/src/main/java/com/allen/androidcustomview/widget/SuperDividerItemDecoration.java new file mode 100644 index 0000000..a7ae5ca --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/SuperDividerItemDecoration.java @@ -0,0 +1,255 @@ +package com.allen.androidcustomview.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.util.TypedValue; +import android.view.View; +import android.widget.LinearLayout; + + +/** + *
+ *      @author : Allen
+ *      e-mail  : lygttpod@163.com
+ *      date    : 2018/04/15
+ *      desc    : 万能分割线
+ *      version : 1.0
+ * 
+ */ +public class SuperDividerItemDecoration extends RecyclerView.ItemDecoration { + + + public static final int HORIZONTAL = LinearLayout.HORIZONTAL; + public static final int VERTICAL = LinearLayout.VERTICAL; + + private static Context context; + + + /** + * 默认分割线的颜色 + */ + private int dividerDefaultColor = 0xFFE1E5E8; + + /** + * 分割线的颜色 + */ + private int dividerColor; + /** + * 分割线的宽度 + */ + private int dividerWidth; + /** + * 分割线距离左右两边的距离 + */ + private int dividerPadding; + /** + * 分割线距离左边的距离 + */ + private int dividerPaddingLeft; + /** + * 分割线距离右边的距离 + */ + private int dividerPaddingRight; + + /** + * 分割线距离上边的距离 + */ + private int dividerPaddingTop; + /** + * 分割线距离下边的距离 + */ + private int dividerPaddingBottom; + /** + * 是否显示列表最后一条分割线 + */ + private boolean dividerIsShowLastDivide; + + + /** + * 分割线item的画笔 + */ + private Paint dividerPaint; + + /** + * 分割线开始的位置(解决recyclerView添加头布局的时候,要从header下边的position位置算起) + */ + private int dividerFromPosition = 0; + + + /** + * recyclerView布局方式(水平或者垂直) + */ + private int orientation; + + + public SuperDividerItemDecoration(Builder builder) { + + context = builder.context; + + dividerColor = builder.dividerColor == 0 ? dividerDefaultColor : builder.dividerColor; + dividerPadding = dp2px(builder.dividerPadding); + dividerPaddingLeft = dp2px(builder.dividerPaddingLeft); + dividerPaddingRight = dp2px(builder.dividerPaddingRight); + dividerPaddingTop = dp2px(builder.dividerPaddingTop); + dividerPaddingBottom = dp2px(builder.dividerPaddingBottom); + dividerWidth = builder.dividerWidth == 0 ? dp2px(0.5f) : dp2px(builder.dividerWidth); + dividerFromPosition = builder.dividerFromPosition; + dividerIsShowLastDivide = builder.dividerIsShowLastDivide; + orientation = builder.orientation; + dividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + dividerPaint.setColor(dividerColor); + + if (dividerPadding != 0) { + dividerPaddingLeft = dividerPaddingRight = dividerPadding; + dividerPaddingTop = dividerPaddingBottom = dividerPadding; + } + } + + public static class Builder { + + private Context context; + private int dividerColor; + private int dividerWidth; + private int dividerPadding; + private int dividerPaddingLeft; + private int dividerPaddingRight; + private int dividerPaddingTop; + private int dividerPaddingBottom; + private int dividerFromPosition; + private boolean dividerIsShowLastDivide; + private int orientation = VERTICAL; + + public Builder(Context context) { + this.context = context; + } + + public Builder setDividerColor(int dividerColor) { + this.dividerColor = dividerColor; + return this; + } + + public Builder setDividerWidth(int dividerWidth) { + this.dividerWidth = dividerWidth; + return this; + } + + public Builder setDividerPadding(int dividerPadding) { + this.dividerPadding = dividerPadding; + return this; + } + + public Builder setDividerPaddingLeft(int dividerPaddingLeft) { + this.dividerPaddingLeft = dividerPaddingLeft; + return this; + } + + public Builder setDividerPaddingRight(int dividerPaddingRight) { + this.dividerPaddingRight = dividerPaddingRight; + return this; + } + + public Builder setDividerPaddingTop(int dividerPaddingTop) { + this.dividerPaddingTop = dividerPaddingTop; + return this; + } + + public Builder setDividerPaddingBottom(int dividerPaddingBottom) { + this.dividerPaddingBottom = dividerPaddingBottom; + return this; + } + + public Builder setDividerFromPosition(int dividerFromPosition) { + this.dividerFromPosition = dividerFromPosition; + return this; + } + + public Builder setIsShowLastDivide(boolean dividerIsShowLastDivide) { + this.dividerIsShowLastDivide = dividerIsShowLastDivide; + return this; + } + + public Builder setOrientation(int orientation) { + this.orientation = orientation; + return this; + } + + public SuperDividerItemDecoration build() { + return new SuperDividerItemDecoration(this); + } + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + super.onDraw(c, parent, state); + + if (orientation == VERTICAL) { + drawVertical(c, parent); + } else { + drawHorizontal(c, parent); + } + + } + + + private void drawVertical(Canvas c, RecyclerView parent) { + int count = parent.getChildCount(); + + if (count > 0) { + int showCount = dividerIsShowLastDivide ? count : count - 1; + for (int i = dividerFromPosition; i < showCount; i++) { + View view = parent.getChildAt(i); + //可见item的底部 + int itemBottom = view.getBottom(); + + c.drawRect(parent.getPaddingLeft() + dividerPaddingLeft, + itemBottom, + parent.getWidth() - parent.getPaddingRight() - dividerPaddingRight, + itemBottom + dividerWidth, + dividerPaint); + } + } + } + + private void drawHorizontal(Canvas c, RecyclerView parent) { + int count = parent.getChildCount(); + if (count > 0) { + int showCount = dividerIsShowLastDivide ? count : count - 1; + for (int i = dividerFromPosition; i < showCount; i++) { + View view = parent.getChildAt(i); + //可见item的底部 + int itemRight = view.getRight(); + + c.drawRect(itemRight, + parent.getPaddingTop() + dividerPaddingTop, + itemRight + dividerWidth, + parent.getHeight() - parent.getPaddingBottom() - dividerPaddingBottom, + dividerPaint); + } + } + } + + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + if (orientation == VERTICAL) { + outRect.bottom = dividerWidth; + } else { + outRect.right = dividerWidth; + } + } + + /** + * dp 2 px + * + * @param dpVal + */ + public static int dp2px(float dpVal) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + dpVal, context.getResources().getDisplayMetrics()); + } + +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/banner/BannerView.java b/app/src/main/java/com/allen/androidcustomview/widget/banner/BannerView.java new file mode 100644 index 0000000..1a5048a --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/banner/BannerView.java @@ -0,0 +1,173 @@ +package com.allen.androidcustomview.widget.banner; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import com.allen.androidcustomview.R; +import com.allen.androidcustomview.widget.banner.adapter.BannerViewPagerAdapter; +import com.allen.androidcustomview.widget.banner.holder.BannerViewHolderCreator; + +import java.util.List; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/03/02
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public class BannerView extends RelativeLayout implements ViewPager.OnPageChangeListener { + private static final String TAG = BannerView.class.getSimpleName(); + + public ViewPager getViewPager() { + return mViewPager; + } + + private ViewPager mViewPager; + private LinearLayout mIndicatorContainer; + private BannerViewPagerAdapter mAdapter; + + private PagerOptions mPagerOptions; + private ImageView mCurrentIndicator; + + public BannerView(Context context) { + super(context); + init(context); + } + + public BannerView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public BannerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + LayoutInflater.from(context).inflate(R.layout.banner_view_layout, this); + mViewPager = (ViewPager) findViewById(R.id.banner_view_pager); + mIndicatorContainer = (LinearLayout) findViewById(R.id.banner_indicator_container); + + mPagerOptions = new PagerOptions.Builder(context).build(); + + mViewPager.setOffscreenPageLimit(4); + mViewPager.addOnPageChangeListener(this); + } + + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + // 切换indicator + setIndicatorSelected(position); + + setOnPageSelectedListener(position); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + + + private void setOnPageSelectedListener(int position) { + if (mPagerOptions.mOnPageChangeListener != null) { + mPagerOptions.mOnPageChangeListener.onPageSelected(position); + } + } + + /** + * 设置 PagerOptions + * + * @param options options + * @return BannerPager + */ + public BannerView setPagerOptions(PagerOptions options) { + mPagerOptions = options; + return this; + } + + /** + * 设置 page data + * + * @param data List + * @param creator BannerViewHolderCreator + */ + public void setPages(@NonNull List data, @NonNull BannerViewHolderCreator creator) { + if (data == null || creator == null) { + return; + } + mAdapter = new BannerViewPagerAdapter<>(data, creator); + + initIndicator(); + handlePagerOptions(); + + mViewPager.setAdapter(mAdapter); + + + } + + private void initIndicator() { + + int count = mAdapter.getRealCount(); + + mIndicatorContainer.removeAllViews(); + ImageView indicator; + LinearLayout.LayoutParams layoutParams; + + for (int i = 0; i < count; i++) { + layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + layoutParams.setMargins(10,0,10,0); + indicator = new ImageView(getContext()); + indicator.setImageDrawable(mPagerOptions.mIndicatorDrawable[0]); + indicator.setLayoutParams(layoutParams); + mIndicatorContainer.addView(indicator); + } + setIndicatorSelected(mViewPager.getCurrentItem()); + + } + + private void setIndicatorSelected(int currentItem) { + if (mCurrentIndicator != null) { + mCurrentIndicator.setImageDrawable(mPagerOptions.mIndicatorDrawable[0]); + mCurrentIndicator.setSelected(false); + } + if (mIndicatorContainer.getChildCount()>0){ + final ImageView indicator = (ImageView) mIndicatorContainer.getChildAt(currentItem); + indicator.setSelected(true); + indicator.setImageDrawable(mPagerOptions.mIndicatorDrawable[1]); + mCurrentIndicator = indicator; + } + } + + private void handlePagerOptions() { + //设置每页之间间距 + mViewPager.setPageMargin(mPagerOptions.mPageMargin); + //设置预显示宽 + final ViewGroup.MarginLayoutParams mp = (MarginLayoutParams) mViewPager.getLayoutParams(); + mp.leftMargin = mp.rightMargin = mPagerOptions.mPrePagerWidth; + mViewPager.setLayoutParams(mp); + + //设置切换效果 + mViewPager.setPageTransformer(true, mPagerOptions.mPageTransformer); + mAdapter.setPageClickListener(mPagerOptions.mOnPageClickListener); + + } + +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/banner/PagerOptions.java b/app/src/main/java/com/allen/androidcustomview/widget/banner/PagerOptions.java new file mode 100644 index 0000000..9a68aee --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/banner/PagerOptions.java @@ -0,0 +1,259 @@ +package com.allen.androidcustomview.widget.banner; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.support.annotation.ColorInt; +import android.support.annotation.DrawableRes; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.ViewPager; +import android.util.TypedValue; + +import com.allen.androidcustomview.widget.banner.listener.OnPageChangeListener; +import com.allen.androidcustomview.widget.banner.listener.OnPageClickListener; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/03/02
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public class PagerOptions { + + + int mPageMargin; + int mPrePagerWidth; + int mIndicatorVisibility; + Drawable[] mIndicatorDrawable; + int mIndicatorDistance; + boolean mLoopEnable; + int mDelayedTime; + int mIndicatorAlign; + ViewPager.PageTransformer mPageTransformer; + int mScrollDuration; + int mIndicatorMarginBottom; + int mIndicatorSize; + OnPageClickListener mOnPageClickListener; + OnPageChangeListener mOnPageChangeListener; + + private PagerOptions() { + } + + public static class Builder { + + private Context mContext; + private int mPageMargin; + private int mPrePagerWidth; + private int mIndicatorAlign; + private int mIndicatorVisibility; + private Drawable[] mIndicatorDrawable = new Drawable[2]; + private int mIndicatorDistance = 8; + private boolean mLoopEnable = true; + private ViewPager.PageTransformer mPageTransformer; + private int mDelayedTime = 3000; + private int mScrollDuration = 800; + private int mIndicatorMarginBottom = -1; + private int mIndicatorSize = -1; + private OnPageClickListener mOnPageClickListener; + private OnPageChangeListener mOnPageChangeListener; + + public Builder(Context context) { + mContext = context; + //设置默认指示器 +// setIndicatorDrawable(R.drawable.indicator_normal_default, R.drawable.indicator_selected_default); + } + + + /** + * 设置每个 page 之间间隔 + * + * @param px px value + * @return Builder + */ + public Builder setPageMargin(int px) { + mPageMargin = px; + return this; + } + + /** + * 左右两侧预显示宽度 + * + * @param px px value + * @return Builder + */ + public Builder setPrePagerWidth(int px) { + mPrePagerWidth = px; + return this; + } + + /** + * 设置指示器间距 + * + * @param distance px value + * @return Builder + */ + public Builder setIndicatorDistance(int distance) { + mIndicatorDistance = distance; + return this; + } + + /** + * 设置指示器距离底部间距 + * + * @param marginBottom marginBottom + * @return Builder + */ + public Builder setIndicatorMarginBottom(int marginBottom) { + mIndicatorMarginBottom = marginBottom; + return this; + } + + /** + * 设置指示器位置 + * + * @param align RelativeLayout.ALIGN_PARENT_LEFT || RelativeLayout.CENTER_IN_PARENT || RelativeLayout.ALIGN_PARENT_RIGHT + * @return Builder + */ + public Builder setIndicatorAlign(int align) { + mIndicatorAlign = align; + return this; + } + + /** + * 设置Indicator 是否可见 + * + * @param visibility One of VISIBLE, INVISIBLE, GONE. + * @return Builder + */ + public Builder setIndicatorVisibility(int visibility) { + mIndicatorVisibility = visibility; + return this; + } + + /** + * 设置轮播切换效果 + * + * @param transformer PageTransformer + * @return Builder + */ + public Builder setPageTransformer(ViewPager.PageTransformer transformer) { + mPageTransformer = transformer; + return this; + } + + /** + * 设置指示器 + * + * @param unSelected 未选中 + * @param selected 选中 + * @return Builder + */ + public Builder setIndicatorDrawable(@DrawableRes int unSelected, @DrawableRes int selected) { + mIndicatorDrawable[0] = ContextCompat.getDrawable(mContext, unSelected); + mIndicatorDrawable[1] = ContextCompat.getDrawable(mContext, selected); + return this; + } + + /** + * 设置指示器 + * + * @param unSelected 未选中 + * @param selected 选中 + * @return Builder + */ + public Builder setIndicatorColor(@ColorInt int unSelected, @ColorInt int selected) { + mIndicatorDrawable[0] = createDrawable(unSelected); + mIndicatorDrawable[1] = createDrawable(selected); + return this; + } + + private Drawable createDrawable(@ColorInt int color) { + final int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, mContext.getResources().getDisplayMetrics()); + final GradientDrawable gd = new GradientDrawable(); + gd.setColor(color); + gd.setShape(GradientDrawable.OVAL); + gd.setSize(size, size); + return gd; + } + + /** + * 设置指示器 + * + * @param size px + * @return Builder + */ + public Builder setIndicatorSize(int size) { + mIndicatorSize = size; + return this; + } + + /** + * 设置可否循环 + * + * @param loop loop + * @return Builder + */ + public Builder setLoopEnable(boolean loop) { + mLoopEnable = loop; + return this; + } + + /** + * 设置切换时间 + * + * @param duration ms + * @return Builder + */ + public Builder setTurnDuration(int duration) { + mDelayedTime = duration; + return this; + } + + /** + * 设置ViewPager的滚动速度 + * + * @param duration ms + */ + public Builder setScrollDuration(int duration) { + mScrollDuration = duration; + return this; + } + + + public Builder setOnPageClickListener(OnPageClickListener onPageClickListener) { + this.mOnPageClickListener = onPageClickListener; + return this; + } + + public Builder setOnPageChangeListener(OnPageChangeListener onPageChangeListener) { + this.mOnPageChangeListener = onPageChangeListener; + return this; + } + + public PagerOptions build() { + final PagerOptions options = new PagerOptions(); + + options.mPageMargin = mPageMargin; + options.mPrePagerWidth = mPrePagerWidth; + options.mIndicatorDistance = mIndicatorDistance; + options.mIndicatorDrawable = mIndicatorDrawable; + options.mIndicatorSize = mIndicatorSize; + options.mIndicatorAlign = mIndicatorAlign; + options.mIndicatorVisibility = mIndicatorVisibility; + options.mLoopEnable = mLoopEnable; + options.mPageTransformer = mPageTransformer; + options.mDelayedTime = mDelayedTime; + options.mScrollDuration = mScrollDuration; + options.mIndicatorMarginBottom = mIndicatorMarginBottom; + options.mOnPageClickListener = mOnPageClickListener; + options.mOnPageChangeListener = mOnPageChangeListener; + mContext = null; + return options; + } + } + +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/banner/adapter/BannerViewPagerAdapter.java b/app/src/main/java/com/allen/androidcustomview/widget/banner/adapter/BannerViewPagerAdapter.java new file mode 100644 index 0000000..2a14a6d --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/banner/adapter/BannerViewPagerAdapter.java @@ -0,0 +1,122 @@ +package com.allen.androidcustomview.widget.banner.adapter; + +import android.support.annotation.NonNull; +import android.support.v4.view.PagerAdapter; +import android.view.View; +import android.view.ViewGroup; + +import com.allen.androidcustomview.widget.banner.listener.OnPageClickListener; +import com.allen.androidcustomview.widget.banner.holder.BannerViewHolder; +import com.allen.androidcustomview.widget.banner.holder.BannerViewHolderCreator; + +import java.util.ArrayList; +import java.util.List; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/03/02
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public class BannerViewPagerAdapter extends PagerAdapter { + + private List mData; + private BannerViewHolderCreator mCreator; + private OnPageClickListener mPageClickListener; + + public BannerViewPagerAdapter(@NonNull List data, @NonNull BannerViewHolderCreator creator) { + + if (mData == null) { + mData = new ArrayList<>(); + } + mData = data; + mCreator = creator; + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public Object instantiateItem(ViewGroup container, final int position) { + View view = getView(position, container); + container.addView(view); + return view; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView((View) object); + } + + + + /** + * 获取真实的Count + * + * @return + */ + public int getRealCount() { + return mData == null ? 0 : mData.size(); + } + + @Override + public int getCount() { + return getRealCount(); + } + + /** + * @param position + * @param container + * @return + */ + private View getView(int position, ViewGroup container) { + + final int realPosition = position % getRealCount(); + BannerViewHolder holder = null; + // create holder + holder = mCreator.createViewHolder(); + + if (holder == null) { + throw new RuntimeException("can not return a null holder"); + } + // create View + View view = holder.createView(container.getContext()); + + if (mData != null && mData.size() > 0) { + holder.onBind(container.getContext(), realPosition, mData.get(realPosition)); + } + + // 添加page点击事件 + if (view != null) { + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mPageClickListener != null) { + mPageClickListener.onPageClick(v, realPosition); + } + } + }); + } + + return view; + } + + + public List getData() { + return mData; + } + + public void setData(List mData) { + this.mData = mData; + } + + public void setPageClickListener(OnPageClickListener pageClickListener) { + this.mPageClickListener = pageClickListener; + } +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/banner/holder/BannerViewHolder.java b/app/src/main/java/com/allen/androidcustomview/widget/banner/holder/BannerViewHolder.java new file mode 100644 index 0000000..35165dd --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/banner/holder/BannerViewHolder.java @@ -0,0 +1,33 @@ +package com.allen.androidcustomview.widget.banner.holder; + +import android.content.Context; +import android.view.View; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/03/02
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public interface BannerViewHolder { + + /** + * 创建View + * @param context + * @return + */ + View createView(Context context); + + /** + * 绑定数据 + * @param context + * @param position + * @param data + */ + void onBind(Context context, int position, T data); + +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/banner/holder/BannerViewHolderCreator.java b/app/src/main/java/com/allen/androidcustomview/widget/banner/holder/BannerViewHolderCreator.java new file mode 100644 index 0000000..5192566 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/banner/holder/BannerViewHolderCreator.java @@ -0,0 +1,20 @@ +package com.allen.androidcustomview.widget.banner.holder; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/03/02
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public interface BannerViewHolderCreator { + /** + * 创建 BannerViewHolder + * + * @return BannerViewHolder + */ + VH createViewHolder(); +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/banner/listener/OnPageChangeListener.java b/app/src/main/java/com/allen/androidcustomview/widget/banner/listener/OnPageChangeListener.java new file mode 100644 index 0000000..521f52d --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/banner/listener/OnPageChangeListener.java @@ -0,0 +1,21 @@ +package com.allen.androidcustomview.widget.banner.listener; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/03/09
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public interface OnPageChangeListener { + + /** + * item 选中事件 + * + * @param position position + */ + void onPageSelected(int position); +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/banner/listener/OnPageClickListener.java b/app/src/main/java/com/allen/androidcustomview/widget/banner/listener/OnPageClickListener.java new file mode 100644 index 0000000..e1ac8cd --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/banner/listener/OnPageClickListener.java @@ -0,0 +1,25 @@ +package com.allen.androidcustomview.widget.banner.listener; + +import android.view.View; + +/** + *
+ *      @author : xiaoyao
+ *      e-mail  : xiaoyao@51vest.com
+ *      date    : 2018/03/02
+ *      desc    :
+ *      version : 1.0
+ * 
+ */ + +public interface OnPageClickListener { + + /** + * item 点击事件 + * + * @param view view + * @param position position + */ + void onPageClick(View view, int position); + +} diff --git a/app/src/main/java/com/allen/androidcustomview/widget/status/StatusBuilder.kt b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusBuilder.kt new file mode 100644 index 0000000..a036c92 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusBuilder.kt @@ -0,0 +1,50 @@ +package com.allen.androidcustomview.widget.status + +import android.view.View + +/** + *
+ *      @author : Allen
+ *      e-mail  : lygttpod@163.com
+ *      date    : 2019/03/19
+ *      desc    :
+ * 
+ */ +class StatusBuilder { + + class Builder{ + + var contentView: View? = null + var errorView: View? = null + var emptyView: View? = null + var loadingView: View? = null + + fun setContentView(contentView: View):Builder { + this.contentView = contentView + return this + } + fun setContentView(layoutResId: Int):Builder { + this.contentView = contentView + return this + } + + fun setErrorView(errorView: View):Builder { + this.errorView = errorView + return this + } + + fun setEmptyView(emptyView: View):Builder { + this.emptyView = emptyView + return this + } + + fun setLoadingView(loadingView: View):Builder { + this.loadingView = loadingView + return this + } + + fun build(){ + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/widget/status/StatusLayout.kt b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusLayout.kt new file mode 100644 index 0000000..470b9bc --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusLayout.kt @@ -0,0 +1,27 @@ +package com.allen.androidcustomview.widget.status + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout + +/** + *
+ *      @author : Allen
+ *      e-mail  : lygttpod@163.com
+ *      date    : 2019/03/19
+ *      desc    : 状态布局
+ * 
+ */ + +class StatusLayout : FrameLayout { + + constructor(context: Context) : this(context, null) + constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + initAttrs(attrs) + } + + private fun initAttrs(attrs: AttributeSet?) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/widget/status/StatusManager.kt b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusManager.kt new file mode 100644 index 0000000..383baf7 --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/status/StatusManager.kt @@ -0,0 +1,29 @@ +package com.allen.androidcustomview.widget.status + +/** + *
+ *      @author : Allen
+ *      e-mail  : lygttpod@163.com
+ *      date    : 2019/03/19
+ *      desc    :
+ * 
+ */ +object StatusManager { + + + fun setEmptyView(){ + + } + + fun setErrorView(){ + + } + + fun setContentView() { + + } + + fun setLoadingView() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteContainerView.kt b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteContainerView.kt new file mode 100644 index 0000000..9cf7f2c --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteContainerView.kt @@ -0,0 +1,283 @@ +package com.allen.androidcustomview.widget.vote + +import android.content.Context +import android.graphics.Outline +import android.os.Build +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewOutlineProvider +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.Toast +import com.allen.androidcustomview.R +import com.allen.androidcustomview.bean.VoteBean +import com.allen.androidcustomview.bean.VoteOption +import kotlinx.android.synthetic.main.widget_vote_layout.view.* +import java.lang.ref.WeakReference + +/** + *
+ *      @author : Allen
+ *      date    : 2019/08/01
+ *      desc    :
+ * 
+ */ +class VoteContainerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) { + + private var voteViewHolders: ArrayList = arrayListOf() + + var onVoteClickListener: OnVoteClickListener? = null + + companion object { + val VOTE_TYPE_MULTIPLE = "multiple" + val VOTE_TYPE_SINGLE = "single" + } + + private var optionIds = arrayListOf() + + private var mData: VoteBean? = null + + init { + initView() + } + + private fun initView() { + LayoutInflater.from(context).inflate(R.layout.widget_vote_layout, this) + + vote_container_vote_btn.setOnClickListener { + if (mData == null) return@setOnClickListener + onVoteClickListener?.onVoteCommitBtnClick(mData, optionIds) + } + } + + fun setVoteData(data: VoteBean?) { + if (data == null || data.options.isNullOrEmpty()) { + visibility = View.GONE + } else { + mData = data + setVoteTitle(data.title) + voteViewHolders.clear() + optionIds.clear() + setVoteStatus(data) + setVoteBtnStatus() + vote_item_ll.removeAllViews() + data.options?.forEachIndexed { index, voteOption -> + val viewHolder = onCreateViewHolder() + viewHolder.bind(index, voteOption, data) + voteViewHolders.add(viewHolder) + vote_item_ll.addView(viewHolder.voteView) + } + } + } + + private fun setVoteTitle(title: String?) { + vote_container_title.text = title ?: "" + } + + private fun setVoteStatus(vote: VoteBean) { + val isVoteMulti = vote.choiceType == VOTE_TYPE_MULTIPLE + val voteResult = vote.sumVoteCount ?: 0 + val voted = vote.voted + vote_container_vote_btn.visibility = if (voted != true && isVoteMulti) View.VISIBLE else View.GONE + vote_container_vote_result.visibility = if (voted == true) View.VISIBLE else View.GONE + setVoteResult("共${voteResult}人参与了投票") + } + + private fun addOptionIds(id: Int) { + optionIds.add(id) + } + + + private fun removeOptionIds(id: Int) { + optionIds.remove(id) + } + + private fun getOptionIdsSize(): Int { + return optionIds.size + } + + private fun setVoteBtnStatus() { + val clickable = getOptionIdsSize() > 0 + if (clickable) { + vote_container_vote_btn.setBackgroundResource(R.drawable.shape_bg_clickable) + } else { + vote_container_vote_btn.setBackgroundResource(R.drawable.shape_bg_un_clickable) + } + vote_container_vote_btn.isClickable = clickable + } + + fun refreshDataAfterVoteSuccess() { + vote_container_vote_result.visibility = View.VISIBLE + vote_container_vote_btn.visibility = View.GONE + refreshVoteResult() + startProgressAnim() + } + + fun refreshDataAfterVoteFailed() { + voteViewHolders.forEach { + it.resetDataAfterSingleVoteFailed() + } + } + + fun onDestroy() { + voteViewHolders.forEach { + it.onVoteDestroy() + } + } + + private fun refreshVoteResult() { + val voteResult = (mData?.sumVoteCount ?: 0) + setVoteResult("共${(voteResult + 1)}人参与了投票") + } + + private fun setVoteResult(result: String?) { + vote_container_vote_result.text = result ?: "" + } + + private fun startProgressAnim() { + voteViewHolders.forEach { + it.setProgress() + } + } + + private fun onCreateViewHolder(): VoteItemViewHolder { + return VoteItemViewHolder(getVoteView(context), this) + } + + class VoteItemViewHolder(var voteView: VoteView, voteContainerView: VoteContainerView) { + + private val voteContainerViewRef = WeakReference(voteContainerView) + + private fun ref(): VoteContainerView? { + return voteContainerViewRef.get() + } + + private var data: VoteOption? = null + private var mainVote: VoteBean? = null + + var isVoteMulti = true + + fun bind(position: Int, voteOption: VoteOption, mainVote: VoteBean) { + this.data = voteOption + this.mainVote = mainVote + + isVoteMulti = mainVote.choiceType == VOTE_TYPE_MULTIPLE + + val voteResultCount = voteOption.showCount ?: 0 + + voteView.setVoteIsSelected(voteOption.voted ?: false) + .setVoteContent(voteOption.content) + .setVoteResultText("${voteResultCount}人").refreshView() + + if (isHaveVoted()) { + val sum = mainVote?.sumVoteCount ?: 0 + val showCount = data?.showCount ?: 0 + val progress = if (sum == 0) 0f else showCount.toFloat() / sum.toFloat() + + voteView.setProgress(progress) + } + + voteView.setOnClickListener { + if (isHaveVoted()) return@setOnClickListener + if (isVoteMulti) { + setMultiChoice(voteView, voteOption) + ref()?.onVoteClickListener?.onVoteItemClick(mainVote, data) + } else { + if (ref()?.getOptionIdsSize() ?: 0 > 0) return@setOnClickListener + setSingleChoice(voteView, voteOption) + ref()?.onVoteClickListener?.onVoteItemClick(mainVote, data) + } + } + + } + + private fun isHaveVoted(): Boolean { + return mainVote?.voted ?: false + } + + fun setProgress() { + val sum = mainVote?.sumVoteCount ?: 0 + var showCount = data?.showCount ?: 0 + val realShowCount = if (data?.voted == true) showCount + 1 else showCount + voteView.setVoteResultText("${realShowCount}人") + mainVote?.voted = true + val progress = if (sum == 0) 0f else realShowCount.toFloat() / sum.toFloat() + voteView.setProgressWithAnim(progress) + } + + fun resetDataAfterSingleVoteFailed() { + if (isVoteMulti) return + data?.voted = false + ref()?.removeOptionIds(data?.id ?: 0) + voteView.setVoteIsSelected(data?.voted ?: false).refreshView() + } + + fun onVoteDestroy() { + voteView.onDestroy() + } + + private fun setMultiChoice(voteView: VoteView, voteOption: VoteOption) { + if (voteOption.voted == true) { + voteOption.voted = false + ref()?.removeOptionIds(voteOption.id ?: 0) + } else { + val optionsIdSize = ref()?.optionIds?.size ?: 0 + val maxSelect = mainVote?.maxSelect ?: 0 + if (optionsIdSize < maxSelect) { + voteOption.voted = true + ref()?.addOptionIds(voteOption.id ?: 0) + } else { + Toast.makeText(ref()?.context, "最多可选${maxSelect}个", Toast.LENGTH_SHORT).show() + } + } + ref()?.setVoteBtnStatus() + voteView.setVoteIsSelected(voteOption.voted ?: false).refreshView() + } + + private fun setSingleChoice(voteView: VoteView, voteOption: VoteOption) { + voteOption.voted = true + ref()?.addOptionIds(voteOption.id ?: 0) + voteView.setVoteIsSelected(voteOption.voted ?: false).refreshView() + ref()?.onVoteClickListener?.onVoteCommitBtnClick(mainVote, ref()?.optionIds + ?: arrayListOf()) + } + + } + + private fun getVoteView(context: Context): VoteView { + val voteView = VoteView(context) + voteView.setVoteTextSize(voteView.sp2px(15)) + .setVoteUncheckedContentTextColor(resources.getColor(R.color.unchecked_content_text_color)) + .setVoteCheckedContentTextColor(resources.getColor(R.color.checked_content_text_color)) + .setVoteUncheckedResultTextColor(resources.getColor(R.color.unchecked_result_text_color)) + .setVoteCheckedResultTextColor(resources.getColor(R.color.checked_result_text_color)) + .setVoteUncheckedProgressColor(resources.getColor(R.color.unchecked_progress_color)) + .setVoteCheckedProgressColor(resources.getColor(R.color.checked_progress_color)) + .setVoteCheckedIcon(resources.getDrawable(R.mipmap.icon_vote_check)) + .setVoteBorderRadius(voteView.dp2px(3f)) + .setVoteBorderColor(resources.getColor(R.color.border_color)) + .setVoteRightIconSize(voteView.dp2px(18f).toInt()) + .setVoteAnimDuration(2000L) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + voteView.clipToOutline = true + voteView.outlineProvider = object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect(0, 0, view.width, view.height, voteView.dp2px(3f)) + } + } + } + + val layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, voteView.dp2px(40f).toInt()) + layoutParams.bottomMargin = voteView.dp2px(12f).toInt() + voteView.layoutParams = layoutParams + return voteView + } + + + interface OnVoteClickListener { + fun onVoteCommitBtnClick(mainVote: VoteBean?, optionIds: ArrayList) + fun onVoteItemClick(mainVote: VoteBean?, voteOption: VoteOption?) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteLayoutAdapter.kt b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteLayoutAdapter.kt new file mode 100644 index 0000000..95a7eca --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteLayoutAdapter.kt @@ -0,0 +1,89 @@ +package com.allen.androidcustomview.widget.vote + +import android.view.View +import android.view.ViewGroup +import com.allen.androidcustomview.bean.VoteBean +import com.allen.androidcustomview.bean.VoteOption +import java.lang.ref.WeakReference + +/** + *
+ *      @author : Allen
+ *      date    : 2019/08/03
+ *      desc    :
+ * 
+ */ +class VoteLayoutAdapter(private val viewGroup: ViewGroup) { + + private var viewHolders = mutableListOf() + + var onVoteClickListener: OnVoteClickListener? = null + + fun setData(vote: ArrayList?) { + viewGroup.removeAllViews() + viewHolders.clear() + if (vote == null || vote.size <= 0) { + viewGroup.visibility = View.GONE + } else { + viewGroup.visibility = View.VISIBLE + val size = vote.size + for (i in 0 until size) { + val viewHolder = onCreateViewHolder(viewGroup, i) + viewHolder.bind(vote[i]) + viewHolders.add(viewHolder) + viewGroup.addView(viewHolder.voteContainerView) + } + } + } + + fun refreshDataAfterVotedSuccess(position: Int) { + viewHolders[position].voteContainerView.refreshDataAfterVoteSuccess() + } + + fun refreshDataAfterVotedFailed(position: Int) { + viewHolders[position].voteContainerView.refreshDataAfterVoteFailed() + } + + fun onDestroy() { + viewHolders.forEach { + it.voteContainerView.onDestroy() + } + } + + private fun onCreateViewHolder(viewGroup: ViewGroup, position: Int): VoteViewHolder { + val view = VoteContainerView(viewGroup.context) + return VoteViewHolder(view, this, position) + } + + class VoteViewHolder(view: VoteContainerView, adapter: VoteLayoutAdapter, var position: Int) { + + private val adapterRef = WeakReference(adapter) + private fun ref(): VoteLayoutAdapter? { + return adapterRef.get() + } + + var voteContainerView = view + private var mMainVote: VoteBean? = null + + fun bind(mainVote: VoteBean) { + mMainVote = mainVote + voteContainerView.setVoteData(mainVote) + voteContainerView.onVoteClickListener = object : VoteContainerView.OnVoteClickListener { + override fun onVoteCommitBtnClick(mainVote: VoteBean?, optionIds: ArrayList) { + ref()?.onVoteClickListener?.onVoteCommitBtnClick(mainVote, optionIds, position) + } + + override fun onVoteItemClick(mainVote: VoteBean?, voteOption: VoteOption?) { + ref()?.onVoteClickListener?.onVoteItemClick(mainVote, voteOption, position) + } + + } + } + + } + + interface OnVoteClickListener { + fun onVoteCommitBtnClick(mainVote: VoteBean?, optionIds: ArrayList, position: Int) + fun onVoteItemClick(mainVote: VoteBean?, voteOption: VoteOption?, position: Int) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteView.kt b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteView.kt new file mode 100644 index 0000000..7a0845c --- /dev/null +++ b/app/src/main/java/com/allen/androidcustomview/widget/vote/VoteView.kt @@ -0,0 +1,419 @@ +package com.allen.androidcustomview.widget.vote + +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.* +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.util.TypedValue +import android.view.View +import android.view.animation.DecelerateInterpolator +import com.allen.androidcustomview.R +import kotlin.math.max + +/** + *
+ *      @author : Allen
+ *      date    : 2019/07/30
+ *      desc    :
+ * 
+ */ +class VoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { + + private var mWidth = 0 + private var mHeight = 0 + + private var bgRectF = RectF() + private var progressRectF = RectF() + private var voteContentRectF = Rect() + private var voteResultRectF = Rect() + private var voteRightIconRectF = Rect() + + private var voteResultBaseline = 0 + private var voteContentBaseline = 0 + + private var progressPaint: Paint? = null + private var iconPaint: Paint? = null + private var bgPaint: Paint? = null + private var borderPaint: Paint? = null + + private var voteContentTextPaint: Paint? = null + private var voteResultTextPaint: Paint? = null + + private var animDuration = 1000L + + private var mScale = 1f + + private var mProgress = -1f + private var mVoteContent: String? = null + private var mVoteResult: String? = null + + private var valueAnimator: ValueAnimator? = null + + private var textMarginLeft = 0 + private var voteResultMarginRight = 0 + + private var textPaintSize: Int = 0 + + private var rightCheckedBitmapRes: Bitmap? = null + + private var rightIconWidth = 0 + private var rightIconHeight = 0 + + private var checkedProgressColor = 0 + private var unCheckedProgressColor = 0 + + private var checkedContentTextColor = 0 + private var uncheckedContentTextColor = 0 + + private var checkedResultTextColor = 0 + private var uncheckedResultTextColor = 0 + + private var borderColor = 0 + private var borderRadius = 0f + + private var isVoteChecked = false + private var textWidth = 0 + + private val defaultCheckedProgressColor = Color.argb(1, 255, 124, 5) + private val defaultUncheckedProgressColor = Color.parseColor("#F3F3F3") + private val defaultCheckedTextColor = Color.parseColor("#FF7C05") + private val defaultUncheckedTextColor = Color.parseColor("#1a1a1a") + private val defaultBorderColor = Color.parseColor("#e6e6e6") + + init { + + textMarginLeft = dp2px(15f).toInt() + voteResultMarginRight = dp2px(15f).toInt() + + initAttr(context, attrs, defStyleAttr) + + initPaint() + + initVoteRightIcon() + + initColor() + } + + private fun initColor() { + voteContentTextPaint?.color = if (isVoteChecked) checkedContentTextColor else uncheckedContentTextColor + voteResultTextPaint?.color = if (isVoteChecked) checkedResultTextColor else uncheckedResultTextColor + progressPaint?.color = if (isVoteChecked) checkedProgressColor else unCheckedProgressColor + bgPaint?.color = if (isVoteChecked) checkedProgressColor else unCheckedProgressColor + } + + private fun initVoteRightIcon() { + if (rightCheckedBitmapRes != null) { + if (rightIconWidth == 0 || rightIconHeight == 0) { + rightIconWidth = dp2px(36f).toInt() + rightIconHeight = dp2px(36f).toInt() + } + } + } + + private fun initAttr(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.VoteView) + + checkedProgressColor = typedArray.getColor(R.styleable.VoteView_voteCheckedProgressColor, defaultCheckedProgressColor) + unCheckedProgressColor = typedArray.getColor(R.styleable.VoteView_voteUncheckedProgressColor, defaultUncheckedProgressColor) + + checkedContentTextColor = typedArray.getColor(R.styleable.VoteView_voteCheckedContentTextColor, defaultCheckedTextColor) + uncheckedContentTextColor = typedArray.getColor(R.styleable.VoteView_voteUncheckedContentTextColor, defaultUncheckedTextColor) + + checkedResultTextColor = typedArray.getColor(R.styleable.VoteView_voteCheckedResultTextColor, defaultCheckedTextColor) + uncheckedResultTextColor = typedArray.getColor(R.styleable.VoteView_voteUncheckedResultTextColor, defaultUncheckedTextColor) + + textPaintSize = typedArray.getDimensionPixelSize(R.styleable.VoteView_voteTextSize, sp2px(15)) + + borderColor = typedArray.getColor(R.styleable.VoteView_voteBorderColor, defaultBorderColor) + borderRadius = typedArray.getDimensionPixelOffset(R.styleable.VoteView_voteBorderRadius, dp2px(1f).toInt()).toFloat() + + animDuration = typedArray.getInt(R.styleable.VoteView_voteAnimDuration, 500).toLong() + + rightCheckedBitmapRes = (typedArray.getDrawable(R.styleable.VoteView_voteCheckedIcon) as? BitmapDrawable)?.bitmap + + rightIconWidth = typedArray.getDimensionPixelOffset(R.styleable.VoteView_voteRightIconWidth, 0) + rightIconHeight = typedArray.getDimensionPixelOffset(R.styleable.VoteView_voteRightIconHeight, 0) + + typedArray.recycle() + } + + private fun initPaint() { + iconPaint = Paint(Paint.ANTI_ALIAS_FLAG) + iconPaint?.isFilterBitmap = true + iconPaint?.isDither = true + + bgPaint = getPaint(dp2px(0.5f), Color.WHITE, Paint.Style.FILL) + progressPaint = getPaint(dp2px(0.5f), unCheckedProgressColor, Paint.Style.FILL) + borderPaint = getPaint(dp2px(0.5f), borderColor, Paint.Style.STROKE) + + voteContentTextPaint = getTextPaint(uncheckedContentTextColor, textPaintSize.toFloat()) + voteResultTextPaint = getTextPaint(uncheckedResultTextColor, textPaintSize.toFloat()) + } + + private fun getPaint(strokeWidth: Float, color: Int, style: Paint.Style): Paint { + val paint = Paint(Paint.ANTI_ALIAS_FLAG) + paint.strokeWidth = strokeWidth + paint.color = color + paint.isAntiAlias = true + paint.style = style + return paint + } + + private fun getTextPaint(color: Int, textSize: Float): Paint { + val textPaint = Paint(Paint.ANTI_ALIAS_FLAG) + textPaint.textSize = textSize + textPaint.color = color + textPaint.textAlign = Paint.Align.CENTER + textPaint.isAntiAlias = true +// textPaint.typeface = Typeface.DEFAULT_BOLD + return textPaint + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + mWidth = w + mHeight = h + + setBgRect() + setProgressRect() + + setVoteResultRect() + setVoteContentRect() + setVoteRightIconRect() + + } + + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + drawBg(canvas) + drawProgress(canvas) + drawBorder(canvas) + + drawVoteContentText(canvas) + drawVoteResultText(canvas) + drawVoteRightIcon(canvas) + } + + + private fun setBgRect() { + bgRectF.set(0f, 0f, mWidth.toFloat(), mHeight.toFloat()) + } + + private fun setProgressRect() { + progressRectF.set(0f, 0f, 0f, mHeight.toFloat()) + } + + private fun setVoteResultRect() { + if (mVoteResult.isNullOrBlank()) return + voteResultTextPaint!!.getTextBounds(mVoteResult, 0, mVoteResult!!.length, voteResultRectF) + + voteResultRectF.top = 0 + voteResultRectF.bottom = mHeight + + val fontMetrics = voteResultTextPaint!!.fontMetricsInt + voteResultBaseline = (voteResultRectF.bottom + voteResultRectF.top - fontMetrics.bottom - fontMetrics.top) / 2 + } + + private fun setVoteContentRect() { + if (mVoteContent.isNullOrBlank()) return + voteContentTextPaint!!.getTextBounds(mVoteContent, 0, mVoteContent!!.length, voteContentRectF) + + textWidth = (voteContentRectF.right - voteContentRectF.left) + voteContentRectF.top = 0 + voteContentRectF.bottom = mHeight + voteContentRectF.left = (mWidth - textWidth) / 2 + voteContentRectF.right = voteContentRectF.left + textWidth + + val fontMetrics = voteContentTextPaint!!.fontMetricsInt + voteContentBaseline = (voteContentRectF.bottom + voteContentRectF.top - fontMetrics.bottom - fontMetrics.top) / 2 + } + + private fun setVoteRightIconRect() { + voteRightIconRectF.set(voteContentRectF.right + voteResultMarginRight, (mHeight - rightIconHeight) / 2, voteContentRectF.right + voteResultMarginRight + rightIconWidth, (mHeight + rightIconHeight) / 2) + } + + + private fun drawBg(canvas: Canvas) { + if (mProgress != -1f) { + bgPaint?.color = Color.WHITE + } + canvas.drawRoundRect(bgRectF, 0f, 0f, bgPaint!!) + } + + private fun drawProgress(canvas: Canvas) { + if (mProgress == -1f) return + canvas.drawRoundRect(getProgressRectF(), 0f, 0f, progressPaint!!) + } + + private fun drawBorder(canvas: Canvas) { + borderPaint?.color = borderColor + canvas.drawRoundRect(bgRectF, borderRadius, borderRadius, borderPaint) + } + + private fun drawVoteContentText(canvas: Canvas) { + if (mVoteContent.isNullOrBlank()) return + //文字绘制到整个布局的中心位置 + if (mProgress == -1f) { + voteContentRectF.left = (mWidth - textWidth) / 2 + voteContentRectF.right = voteContentRectF.left + textWidth + } else { + voteContentRectF.left = max(((1 - mScale) * (mWidth - textWidth) / 2).toInt(), textMarginLeft) + voteContentRectF.right = voteContentRectF.left + textWidth + } + + canvas.drawText(mVoteContent, voteContentRectF.centerX().toFloat(), voteContentBaseline.toFloat(), voteContentTextPaint) + } + + private fun drawVoteResultText(canvas: Canvas) { + if (mProgress == -1f || mVoteResult.isNullOrBlank()) return + //文字绘制到整个布局的中心位置 + voteResultTextPaint?.alpha = (255 * mScale).toInt() + canvas.drawText(mVoteResult, mWidth - voteResultMarginRight - voteResultRectF.centerX().toFloat(), voteResultBaseline.toFloat(), voteResultTextPaint) + } + + private fun drawVoteRightIcon(canvas: Canvas) { + if (rightCheckedBitmapRes != null && isVoteChecked) { + voteRightIconRectF.left = voteContentRectF.right + voteResultMarginRight + voteRightIconRectF.right = voteRightIconRectF.left + rightIconWidth + canvas.drawBitmap(rightCheckedBitmapRes!!, null, voteRightIconRectF, iconPaint) + } + } + + private fun getProgressRectF(): RectF { +// val currentProgress = mProgress * mWidth * mScale / 100 + val currentProgress = mProgress * mWidth * mScale + progressRectF.set(0f, 0f, currentProgress, mHeight.toFloat()) + return progressRectF + } + + + fun setVoteCheckedProgressColor(color: Int): VoteView { + this.checkedProgressColor = color + return this + } + + fun setVoteUncheckedProgressColor(color: Int): VoteView { + this.unCheckedProgressColor = color + return this + } + + fun setVoteBorderRadius(radius: Float): VoteView { + this.borderRadius = radius + return this + } + + fun setVoteBorderColor(color: Int): VoteView { + this.borderColor = color + return this + } + + fun setVoteCheckedContentTextColor(color: Int): VoteView { + this.checkedContentTextColor = color + return this + } + + fun setVoteUncheckedContentTextColor(color: Int): VoteView { + this.uncheckedContentTextColor = color + return this + } + + + fun setVoteCheckedResultTextColor(color: Int): VoteView { + this.checkedResultTextColor = color + return this + } + + fun setVoteUncheckedResultTextColor(color: Int): VoteView { + this.uncheckedResultTextColor = color + return this + } + + fun setVoteCheckedIcon(iconBitmap: Drawable): VoteView { + this.rightCheckedBitmapRes = (iconBitmap as? BitmapDrawable)?.bitmap + return this + } + + fun setVoteRightIconSize(width_height: Int): VoteView { + this.rightIconWidth = width_height + this.rightIconHeight = width_height + return this + } + + fun setVoteTextSize(textSize: Int): VoteView { + this.textPaintSize = textSize + return this + } + + fun setVoteAnimDuration(duration: Long): VoteView { + this.animDuration = duration + return this + } + + fun setVoteContent(content: String?): VoteView { + mVoteContent = content ?: "" + setVoteContentRect() + return this + } + + fun setVoteResultText(voteResult: String?): VoteView { + mVoteResult = voteResult ?: "" + setVoteResultRect() + return this + } + + fun refreshView() { + initColor() + invalidate() + } + + + fun setProgress(progress: Float) { + mProgress = progress + if (mProgress != -1f) { + invalidate() + } + } + + fun setProgressWithAnim(progress: Float) { + mProgress = progress + startAnim() + } + + private fun startAnim() { + valueAnimator?.cancel() + if (valueAnimator == null) { + valueAnimator = ValueAnimator.ofFloat(0f, 1f) + } + valueAnimator?.duration = animDuration + valueAnimator?.interpolator = DecelerateInterpolator() + valueAnimator?.addUpdateListener { + mScale = it.animatedValue as Float + invalidate() + } + valueAnimator?.start() + } + + fun onDestroy() { + valueAnimator?.cancel() + valueAnimator = null + } + + fun setVoteIsSelected(isVoteSelected: Boolean): VoteView { + this.isVoteChecked = isVoteSelected + return this + } + + fun dp2px(dpVal: Float): Float { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + dpVal, resources.displayMetrics) + } + + fun sp2px(spVal: Int): Int { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, + spVal.toFloat(), resources.displayMetrics).toInt() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_bg_clickable.xml b/app/src/main/res/drawable/shape_bg_clickable.xml new file mode 100644 index 0000000..5994224 --- /dev/null +++ b/app/src/main/res/drawable/shape_bg_clickable.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_bg_un_clickable.xml b/app/src/main/res/drawable/shape_bg_un_clickable.xml new file mode 100644 index 0000000..902bdea --- /dev/null +++ b/app/src/main/res/drawable/shape_bg_un_clickable.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_circle_bg.xml b/app/src/main/res/drawable/shape_circle_bg.xml new file mode 100644 index 0000000..5c92444 --- /dev/null +++ b/app/src/main/res/drawable/shape_circle_bg.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_item_bg.xml b/app/src/main/res/drawable/shape_item_bg.xml new file mode 100644 index 0000000..a15600f --- /dev/null +++ b/app/src/main/res/drawable/shape_item_bg.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_search.xml b/app/src/main/res/drawable/shape_search.xml new file mode 100644 index 0000000..b45f287 --- /dev/null +++ b/app/src/main/res/drawable/shape_search.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_vote_item_bg.xml b/app/src/main/res/drawable/shape_vote_item_bg.xml new file mode 100644 index 0000000..594eaf2 --- /dev/null +++ b/app/src/main/res/drawable/shape_vote_item_bg.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_alipay_home.xml b/app/src/main/res/layout/activity_alipay_home.xml new file mode 100644 index 0000000..ff55fd7 --- /dev/null +++ b/app/src/main/res/layout/activity_alipay_home.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_banner.xml b/app/src/main/res/layout/activity_banner.xml new file mode 100644 index 0000000..7cfb9df --- /dev/null +++ b/app/src/main/res/layout/activity_banner.xml @@ -0,0 +1,13 @@ + + + + +