Quellcode durchsuchen

开始调用接口

liukai vor 8 Monaten
Ursprung
Commit
cc5bf0dca0
82 geänderte Dateien mit 3358 neuen und 199 gelöschten Zeilen
  1. 1 0
      app/build.gradle.kts
  2. 11 1
      app/src/main/AndroidManifest.xml
  3. 68 0
      app/src/main/java/vn/hongyegroup/yybusiness/adapter/AttendanceAdapter.java
  4. 38 0
      app/src/main/java/vn/hongyegroup/yybusiness/entity/AttendanceBean.java
  5. 7 0
      app/src/main/java/vn/hongyegroup/yybusiness/entity/CheckInOut.java
  6. 7 0
      app/src/main/java/vn/hongyegroup/yybusiness/entity/LoginBean.java
  7. 46 0
      app/src/main/java/vn/hongyegroup/yybusiness/http/ApiService.kt
  8. 11 0
      app/src/main/java/vn/hongyegroup/yybusiness/http/AppRetrofit.kt
  9. 51 0
      app/src/main/java/vn/hongyegroup/yybusiness/mvvm/AppRepository.kt
  10. 58 0
      app/src/main/java/vn/hongyegroup/yybusiness/mvvm/LoginViewModel.kt
  11. 87 0
      app/src/main/java/vn/hongyegroup/yybusiness/ui/LoginActivity.kt
  12. 80 1
      app/src/main/java/vn/hongyegroup/yybusiness/ui/MainActivity.kt
  13. 57 0
      app/src/main/java/vn/hongyegroup/yybusiness/ui/SplashActivity.kt
  14. BIN
      app/src/main/res/drawable-xhdpi/home_down_icon.webp
  15. BIN
      app/src/main/res/drawable-xhdpi/home_export_icon.webp
  16. BIN
      app/src/main/res/drawable-xhdpi/home_search_icon.webp
  17. BIN
      app/src/main/res/drawable-xhdpi/home_up_icon.webp
  18. BIN
      app/src/main/res/drawable-xhdpi/password_hide_icon.webp
  19. BIN
      app/src/main/res/drawable-xhdpi/password_show_icon.webp
  20. BIN
      app/src/main/res/drawable-xhdpi/yy_business_top_logo.webp
  21. BIN
      app/src/main/res/drawable-xxhdpi/home_down_icon.webp
  22. BIN
      app/src/main/res/drawable-xxhdpi/home_export_icon.webp
  23. BIN
      app/src/main/res/drawable-xxhdpi/home_search_icon.webp
  24. BIN
      app/src/main/res/drawable-xxhdpi/home_up_icon.webp
  25. BIN
      app/src/main/res/drawable-xxhdpi/password_hide_icon.webp
  26. BIN
      app/src/main/res/drawable-xxhdpi/password_show_icon.webp
  27. BIN
      app/src/main/res/drawable-xxhdpi/yy_business_top_logo.webp
  28. 11 0
      app/src/main/res/drawable/layer_start_window.xml
  29. 23 0
      app/src/main/res/drawable/selector_button_yellow_round.xml
  30. 22 0
      app/src/main/res/drawable/selector_check_password_hide.xml
  31. 6 0
      app/src/main/res/drawable/shape_button_bg_circle.xml
  32. 8 0
      app/src/main/res/drawable/shape_button_bg_round.xml
  33. 8 0
      app/src/main/res/drawable/shape_input_bg_round.xml
  34. 12 0
      app/src/main/res/drawable/shape_page_bg.xml
  35. 8 0
      app/src/main/res/drawable/shape_search_bg_round.xml
  36. 128 0
      app/src/main/res/layout/activity_login.xml
  37. 110 161
      app/src/main/res/layout/activity_main.xml
  38. 15 0
      app/src/main/res/layout/activity_splash.xml
  39. 42 0
      app/src/main/res/layout/item_contact.xml
  40. 18 0
      app/src/main/res/layout/item_index_contact.xml
  41. 5 5
      app/src/main/res/values/colors.xml
  42. 114 0
      app/src/main/res/values/string_contact.xml
  43. 7 0
      app/src/main/res/values/strings.xml
  44. 3 3
      buildSrc/src/main/kotlin/ProjectConfig.kt
  45. 1 7
      cs-baselib/src/main/java/com/android/basiclib/base/activity/AbsActivity.kt
  46. 0 6
      cs-baselib/src/main/java/com/android/basiclib/base/fragment/AbsFragment.kt
  47. 1 1
      cs-baselib/src/main/java/com/android/basiclib/core/BaseLibCore.kt
  48. 17 9
      cs-baselib/src/main/res/values/styles.xml
  49. 11 4
      cs-service/src/main/java/com/android/cs_service/CommApi.kt
  50. 1 0
      cs-service/src/main/java/com/android/cs_service/Constants.kt
  51. 1 0
      lib-indexablerecyclerview/.gitignore
  52. 15 0
      lib-indexablerecyclerview/build.gradle.kts
  53. 17 0
      lib-indexablerecyclerview/proguard-rules.pro
  54. 5 0
      lib-indexablerecyclerview/src/main/AndroidManifest.xml
  55. 188 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/AbstractHeaderFooterAdapter.java
  56. 111 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/EntityWrapper.java
  57. 187 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexBar.java
  58. 152 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexableAdapter.java
  59. 13 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexableEntity.java
  60. 39 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexableFooterAdapter.java
  61. 39 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexableHeaderAdapter.java
  62. 748 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexableLayout.java
  63. 13 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/InitialComparator.java
  64. 51 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/PinyinComparator.java
  65. 48 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/PinyinUtil.java
  66. 252 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/RealAdapter.java
  67. 34 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/SimpleFooterAdapter.java
  68. 36 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/SimpleHeaderAdapter.java
  69. 70 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/DataObservable.java
  70. 30 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/DataObserver.java
  71. 92 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/HeaderFooterDataObservable.java
  72. 30 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/HeaderFooterDataObserver.java
  73. 18 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/IndexBarDataObservable.java
  74. 16 0
      lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/IndexBarDataObserver.java
  75. BIN
      lib-indexablerecyclerview/src/main/res/drawable-hdpi/indexable_bg_md_overlay.png
  76. BIN
      lib-indexablerecyclerview/src/main/res/drawable-xhdpi/indexable_bg_md_overlay.png
  77. BIN
      lib-indexablerecyclerview/src/main/res/drawable-xxhdpi/indexable_bg_md_overlay.png
  78. 5 0
      lib-indexablerecyclerview/src/main/res/drawable/indexable_bg_center_overlay.xml
  79. 13 0
      lib-indexablerecyclerview/src/main/res/values/indexable_attrs.xml
  80. 9 0
      lib-indexablerecyclerview/src/main/res/values/indexable_default.xml
  81. 31 0
      lib-indexablerecyclerview/src/main/res/values/indexable_strings.xml
  82. 2 1
      settings.gradle.kts

+ 1 - 0
app/build.gradle.kts

@@ -17,4 +17,5 @@ android {
 
 dependencies {
 
+    api(project(":lib-indexablerecyclerview"))
 }

+ 11 - 1
app/src/main/AndroidManifest.xml

@@ -38,7 +38,8 @@
         android:theme="@style/AppTheme">
 
         <activity
-            android:name=".ui.MainActivity"
+            android:name=".ui.SplashActivity"
+            android:theme="@style/AppTheme_welcome"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -46,6 +47,15 @@
             </intent-filter>
         </activity>
 
+        <activity
+            android:name=".ui.LoginActivity"
+            android:exported="false">
+        </activity>
+
+        <activity
+            android:name=".ui.MainActivity"
+            android:exported="false">
+        </activity>
 
     </application>
 

+ 68 - 0
app/src/main/java/vn/hongyegroup/yybusiness/adapter/AttendanceAdapter.java

@@ -0,0 +1,68 @@
+package vn.hongyegroup.yybusiness.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import me.yokeyword.indexablerv.IndexableAdapter;
+import vn.hongyegroup.yybusiness.R;
+import vn.hongyegroup.yybusiness.entity.AttendanceBean;
+
+public class AttendanceAdapter extends IndexableAdapter<AttendanceBean> {
+
+    private LayoutInflater mInflater;
+
+    public AttendanceAdapter(Context context) {
+        mInflater = LayoutInflater.from(context);
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateTitleViewHolder(ViewGroup parent) {
+        View view = mInflater.inflate(R.layout.item_index_contact, parent, false);
+        return new IndexVH(view);
+    }
+
+    @Override
+    public void onBindTitleViewHolder(RecyclerView.ViewHolder holder, String indexTitle) {
+        IndexVH vh = (IndexVH) holder;
+        vh.tv.setText(indexTitle);
+    }
+
+
+    @Override
+    public RecyclerView.ViewHolder onCreateContentViewHolder(ViewGroup parent) {
+        View view = mInflater.inflate(R.layout.item_contact, parent, false);
+        return new ContentVH(view);
+    }
+
+    @Override
+    public void onBindContentViewHolder(RecyclerView.ViewHolder holder, AttendanceBean entity) {
+        ContentVH vh = (ContentVH) holder;
+        vh.tvName.setText(entity.staff_name);
+        vh.tvMobile.setText(entity.job_date);
+    }
+
+
+    private class IndexVH extends RecyclerView.ViewHolder {
+        TextView tv;
+
+        public IndexVH(View itemView) {
+            super(itemView);
+            tv = (TextView) itemView.findViewById(R.id.tv_index);
+        }
+    }
+
+    private class ContentVH extends RecyclerView.ViewHolder {
+        TextView tvName, tvMobile;
+
+        public ContentVH(View itemView) {
+            super(itemView);
+            tvName = (TextView) itemView.findViewById(R.id.tv_name);
+            tvMobile = (TextView) itemView.findViewById(R.id.tv_mobile);
+        }
+    }
+}

+ 38 - 0
app/src/main/java/vn/hongyegroup/yybusiness/entity/AttendanceBean.java

@@ -0,0 +1,38 @@
+package vn.hongyegroup.yybusiness.entity;
+
+import me.yokeyword.indexablerv.IndexableEntity;
+
+public class AttendanceBean implements IndexableEntity {
+
+    public String applied_id;
+    public String staff_id;  //用户ID
+    public String staff_name;  //用户名
+    public String job_date;  //工作日期
+    public String start_time;  //工作开始时间
+    public String end_time;    //工作结束时间
+    public String check_in_id;     //签到数据
+    public String check_in_time;
+    public String check_in_img;
+
+    public String check_out_id;   //签出数据
+    public String check_out_time;
+    public String status_show;
+
+    public int status;
+
+    @Override
+    public String getFieldIndexBy() {
+        return staff_name;
+    }
+
+    @Override
+    public void setFieldIndexBy(String indexField) {
+        this.staff_name = indexField;
+    }
+
+    @Override
+    public void setFieldPinyinIndexBy(String pinyin) {
+
+    }
+
+}

+ 7 - 0
app/src/main/java/vn/hongyegroup/yybusiness/entity/CheckInOut.java

@@ -0,0 +1,7 @@
+package vn.hongyegroup.yybusiness.entity;
+
+public class CheckInOut {
+    public String applied_id;
+    public String check_img;
+    public String check_time;
+}

+ 7 - 0
app/src/main/java/vn/hongyegroup/yybusiness/entity/LoginBean.java

@@ -0,0 +1,7 @@
+package vn.hongyegroup.yybusiness.entity;
+
+public class LoginBean {
+    public String auth_id;
+    public String token;
+    public String register_id;
+}

+ 46 - 0
app/src/main/java/vn/hongyegroup/yybusiness/http/ApiService.kt

@@ -0,0 +1,46 @@
+package vn.hongyegroup.yybusiness.http
+
+import com.android.basiclib.bean.BaseBean
+import com.android.basiclib.bean.PageInfo
+import com.android.cs_service.CommApi
+import retrofit2.http.FieldMap
+import retrofit2.http.FormUrlEncoded
+
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.Query
+import vn.hongyegroup.yybusiness.entity.AttendanceBean
+import vn.hongyegroup.yybusiness.entity.CheckInOut
+import vn.hongyegroup.yybusiness.entity.LoginBean
+
+interface ApiService {
+
+    /**
+     *
+     * 登录
+     */
+    @FormUrlEncoded
+    @POST(CommApi.LOGIN_IN)
+    suspend fun requestLogin(@FieldMap params: Map<String, String>): BaseBean<LoginBean>
+
+    /**
+     * 登出
+     */
+    @POST(CommApi.LOG_OUT)
+    suspend fun requestLogout(@Query("token") token: String): BaseBean<Boolean>
+
+
+    /**
+     * 待签到列表
+     */
+    @GET(CommApi.APPLIED_LIST)
+    suspend fun fetchAppliedList(): BaseBean<PageInfo<AttendanceBean>>
+
+
+    /**
+     * 签到签出
+     */
+    @POST(CommApi.CHECK_IN_OUT)
+    suspend fun checkInOrOut(): BaseBean<CheckInOut>
+
+}

+ 11 - 0
app/src/main/java/vn/hongyegroup/yybusiness/http/AppRetrofit.kt

@@ -0,0 +1,11 @@
+package vn.hongyegroup.yybusiness.http
+
+import com.android.basiclib.base.BaseRetrofitClient
+import com.android.cs_service.CommApi
+
+object AppRetrofit : BaseRetrofitClient() {
+
+    //默认的ApiService
+    val apiService by lazy { getService(ApiService::class.java, CommApi.BASE_URL) }
+
+}

+ 51 - 0
app/src/main/java/vn/hongyegroup/yybusiness/mvvm/AppRepository.kt

@@ -0,0 +1,51 @@
+package vn.hongyegroup.yybusiness.mvvm
+
+import com.android.basiclib.base.vm.BaseRepository
+import com.android.basiclib.bean.OkResult
+import com.android.basiclib.engine.network.httpRequest
+import com.android.basiclib.utils.CheckUtil
+import com.android.basiclib.utils.log.MyLogUtils
+import vn.hongyegroup.yybusiness.entity.LoginBean
+import vn.hongyegroup.yybusiness.http.AppRetrofit
+import javax.inject.Inject
+import javax.inject.Singleton
+
+
+@Singleton
+class AppRepository @Inject constructor() : BaseRepository() {
+
+    /**
+     * 登录
+     */
+    suspend inline fun requestLogin(code: String, password: String, registerId: String?): OkResult<LoginBean> {
+
+        val params = hashMapOf<String, String>()
+        params["code"] = code
+        params["password"] = password
+
+        if (!CheckUtil.isEmpty(registerId)) {
+            params["register_id"] = registerId!!
+        }
+
+        MyLogUtils.w("请求参数:$params")
+
+        return httpRequest {
+            AppRetrofit.apiService.requestLogin(params)
+        }
+
+    }
+
+    /**
+     * 登出
+     */
+    suspend inline fun requestLogout(token: String): OkResult<Boolean> {
+
+        return httpRequest {
+            AppRetrofit.apiService.requestLogout(token)
+        }
+
+    }
+
+
+
+}

+ 58 - 0
app/src/main/java/vn/hongyegroup/yybusiness/mvvm/LoginViewModel.kt

@@ -0,0 +1,58 @@
+package vn.hongyegroup.yybusiness.mvvm
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.viewModelScope
+import com.android.basiclib.base.mvi.BaseEISViewModel
+import com.android.basiclib.base.vm.BaseViewModel
+import com.android.basiclib.bean.OkResult
+import com.android.basiclib.engine.preferences.EasyDataStore
+import com.android.basiclib.engine.toast.toast
+import com.android.basiclib.utils.log.MyLogUtils
+import com.android.cs_service.Constants
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+
+@HiltViewModel
+class LoginViewModel @Inject constructor(
+    private val repository: AppRepository,
+    val savedState: SavedStateHandle
+) : BaseViewModel() {
+
+
+    /**
+     * 执行登录接口
+     */
+    fun doLogin(code: String, password: String): LiveData<Boolean> {
+        val liveData = MutableLiveData<Boolean>()
+        launchOnUI {
+
+            loadStartLoading()
+
+            val loginResult = repository.requestLogin(code, password, "")
+
+            if (loginResult is OkResult.Success) {
+                //成功
+                loadSuccess()
+
+                val loginBean = loginResult.data
+                EasyDataStore.put(Constants.KEY_TOKEN, loginBean.token)
+
+                liveData.value = true
+            } else {
+                val message = (loginResult as OkResult.Error).exception.message
+                loadError(message)
+                toast(message)
+
+                liveData.value = false
+            }
+
+        }
+        return liveData
+    }
+
+
+}
+

+ 87 - 0
app/src/main/java/vn/hongyegroup/yybusiness/ui/LoginActivity.kt

@@ -0,0 +1,87 @@
+package vn.hongyegroup.yybusiness.ui
+
+import android.os.Build
+import android.os.Bundle
+import android.text.method.HideReturnsTransformationMethod
+import android.text.method.PasswordTransformationMethod
+import android.view.WindowManager
+import android.widget.CompoundButton
+import androidx.lifecycle.Observer
+import com.android.basiclib.base.activity.BaseVVDActivity
+import com.android.basiclib.base.vm.EmptyViewModel
+import com.android.basiclib.engine.toast.toast
+import com.android.basiclib.ext.click
+import com.android.basiclib.ext.gotoActivity
+import com.android.basiclib.utils.CheckUtil
+import com.android.basiclib.utils.StatusBarUtils
+import dagger.hilt.android.AndroidEntryPoint
+import vn.hongyegroup.yybusiness.R
+import vn.hongyegroup.yybusiness.databinding.ActivityLoginBinding
+import vn.hongyegroup.yybusiness.mvvm.LoginViewModel
+
+/**
+ * 登录页面
+ */
+@AndroidEntryPoint
+class LoginActivity : BaseVVDActivity<LoginViewModel, ActivityLoginBinding>() {
+
+    override fun init(savedInstanceState: Bundle?) {
+        //沉浸式
+        StatusBarUtils.immersive(this)
+
+        //允许 window 的内容可以上移到刘海屏状态栏
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            val lp = window.attributes
+            lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+            window.setAttributes(lp)
+        }
+
+        initListener()
+
+    }
+
+    private fun initListener() {
+        mBinding.cbPsd.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
+            if (isChecked) {
+                mBinding.etPassword.transformationMethod = HideReturnsTransformationMethod.getInstance()
+            } else {
+                mBinding.etPassword.transformationMethod = PasswordTransformationMethod.getInstance()
+            }
+
+            // 确保光标移动到文本末尾
+            mBinding.etPassword.setSelection(mBinding.etPassword.text.length)
+        }
+
+
+        mBinding.buttonLogin.click {
+            val code = mBinding.etLoginCode.text.toString()
+            val password = mBinding.etPassword.text.toString()
+            checkParams(code, password)
+        }
+    }
+
+    //校验参数并提交请求
+    private fun checkParams(code: String, password: String) {
+        if (CheckUtil.isEmpty(code)) {
+            toast(R.string.please_enter_your_login_code)
+            return
+        }
+
+        if (CheckUtil.isEmpty(code)) {
+            toast(R.string.please_enter_your_password)
+            return
+        }
+
+        mViewModel.doLogin(code, password).observe(this) {
+            if (it != null && it) {
+                gotoMainPage()
+            }
+        }
+    }
+
+    private fun gotoMainPage() {
+        gotoActivity<MainActivity>()
+        finish()
+    }
+
+}

+ 80 - 1
app/src/main/java/vn/hongyegroup/yybusiness/ui/MainActivity.kt

@@ -1,23 +1,102 @@
 package vn.hongyegroup.yybusiness.ui
 
+import android.os.Build
 import android.os.Bundle
+import android.view.WindowManager
+import androidx.recyclerview.widget.LinearLayoutManager
 import com.android.basiclib.base.activity.BaseVVDActivity
 import com.android.basiclib.base.vm.EmptyViewModel
+import com.android.basiclib.engine.toast.toast
+import com.android.basiclib.utils.CommUtils
+import com.android.basiclib.utils.StatusBarUtils
+import com.android.basiclib.utils.log.MyLogUtils
+import dagger.hilt.android.AndroidEntryPoint
+import me.yokeyword.indexablerv.IndexableAdapter.OnItemContentClickListener
+import vn.hongyegroup.yybusiness.R
+import vn.hongyegroup.yybusiness.adapter.AttendanceAdapter
 import vn.hongyegroup.yybusiness.databinding.ActivityMainBinding
+import vn.hongyegroup.yybusiness.entity.AttendanceBean
 
 
 /**
  * 应用的首页
  */
+@AndroidEntryPoint
 class MainActivity : BaseVVDActivity<EmptyViewModel, ActivityMainBinding>() {
 
+    private var mAdapter: AttendanceAdapter? = null
+
     override fun init(savedInstanceState: Bundle?) {
+
+        //沉浸式
+        StatusBarUtils.immersive(this)
+
+        //允许 window 的内容可以上移到刘海屏状态栏
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            val lp = window.attributes
+            lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+            window.setAttributes(lp)
+        }
+
+        StatusBarUtils.setMargin(mContext, mBinding.statusBarView)
+
+
+        initRV()
         initListener()
+    }
+
+    private fun initRV() {
+        mBinding.indexableLayout.setLayoutManager(LinearLayoutManager(this))
+        // 设置数据适配器
+        mAdapter = AttendanceAdapter(this)
+        mBinding.indexableLayout.setAdapter(mAdapter)
+        // 设置数据源
+        mAdapter?.setDatas(initDatas());
+        initDatas()
+
+        // 设置右侧Index的滚动风格
+        mBinding.indexableLayout.setOverlayStyle_MaterialDesign(CommUtils.getColor(R.color.button_box_bg))
+        mBinding.indexableLayout.setStickyEnable(false)
+
+        // 设置Item监听
+        mAdapter?.setOnItemContentClickListener { _, originalPosition, currentPosition, entity ->
+            if (originalPosition >= 0) {
+                toast("选中:" + entity.staff_name + "  当前位置:" + currentPosition + "  原始所在数组位置:" + originalPosition)
+
+            } else {
+                toast("选中Header/Footer:" + entity.staff_name + "  当前位置:" + currentPosition)
+            }
+
+        }
+
+        mAdapter!!.setOnItemTitleClickListener { _, currentPosition, indexTitle ->
+            toast("选中:$indexTitle  当前位置:$currentPosition")
+        }
+
 
     }
 
-    override fun isShatterEnable(): Boolean = true
+    //提供RV的数据
+    private fun initDatas(): MutableList<AttendanceBean> {
+        val list = arrayListOf<AttendanceBean>()
+        // 初始化数据
+        val contactArray = getResources().getStringArray(R.array.contact_array)
+        val mobileArray = getResources().getStringArray(R.array.mobile_array)
+
+        contactArray.forEachIndexed { index, name ->
+            val contactEntity = AttendanceBean().apply {
+                staff_name = name
+                job_date = mobileArray[index]
+            }
+            list.add(contactEntity)
+        }
+
+        MyLogUtils.w("list数据量:${list.size}")
+
+        return list
+    }
 
+    //其他的监听
     private fun initListener() {
 
     }

+ 57 - 0
app/src/main/java/vn/hongyegroup/yybusiness/ui/SplashActivity.kt

@@ -0,0 +1,57 @@
+package vn.hongyegroup.yybusiness.ui
+
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.Bundle
+import android.view.WindowManager
+import com.android.basiclib.base.activity.BaseVVDActivity
+import com.android.basiclib.base.vm.EmptyViewModel
+import com.android.basiclib.ext.countDown
+import com.android.basiclib.ext.gotoActivity
+import com.android.basiclib.utils.StatusBarUtils
+import com.android.basiclib.utils.log.MyLogUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import vn.hongyegroup.yybusiness.databinding.ActivitySplashBinding
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SuppressLint("CustomSplashScreen")
+class SplashActivity : BaseVVDActivity<EmptyViewModel, ActivitySplashBinding>() {
+
+    private var countDownScope: CoroutineScope? = null
+
+    override fun init(savedInstanceState: Bundle?) {
+
+        //沉浸式
+        StatusBarUtils.immersive(this)
+
+        //允许 window 的内容可以上移到刘海屏状态栏
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            val lp = window.attributes
+            lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+            window.setAttributes(lp)
+        }
+
+        countDown(time = 1,
+            start = { scope ->
+                countDownScope = scope
+            },
+            end = {
+                //去登录页面还是去首页
+                gotoActivity<LoginActivity>()
+                finish()
+            },
+            next = { time ->
+                MyLogUtils.w("倒计时:$time")
+            })
+
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        MyLogUtils.w("onDestroy")
+        countDownScope?.cancel("Count Down Canceled By Destroy")
+    }
+
+}

BIN
app/src/main/res/drawable-xhdpi/home_down_icon.webp


BIN
app/src/main/res/drawable-xhdpi/home_export_icon.webp


BIN
app/src/main/res/drawable-xhdpi/home_search_icon.webp


BIN
app/src/main/res/drawable-xhdpi/home_up_icon.webp


BIN
app/src/main/res/drawable-xhdpi/password_hide_icon.webp


BIN
app/src/main/res/drawable-xhdpi/password_show_icon.webp


BIN
app/src/main/res/drawable-xhdpi/yy_business_top_logo.webp


BIN
app/src/main/res/drawable-xxhdpi/home_down_icon.webp


BIN
app/src/main/res/drawable-xxhdpi/home_export_icon.webp


BIN
app/src/main/res/drawable-xxhdpi/home_search_icon.webp


BIN
app/src/main/res/drawable-xxhdpi/home_up_icon.webp


BIN
app/src/main/res/drawable-xxhdpi/password_hide_icon.webp


BIN
app/src/main/res/drawable-xxhdpi/password_show_icon.webp


BIN
app/src/main/res/drawable-xxhdpi/yy_business_top_logo.webp


+ 11 - 0
app/src/main/res/drawable/layer_start_window.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/shape_page_bg"/>
+
+    <item >
+        <bitmap
+            android:gravity="center"
+            android:src="@drawable/yy_business_top_logo"/>
+    </item>
+
+</layer-list>

+ 23 - 0
app/src/main/res/drawable/selector_button_yellow_round.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true" >
+        <shape>
+
+            <solid android:color="#BF8C14"/>
+
+            <corners android:radius="@dimen/d_22dp"/>
+
+        </shape>
+    </item>
+
+    <item>
+        <shape>
+            <solid android:color="#FFBB1B"/>
+
+            <corners android:radius="@dimen/d_22dp" />
+
+        </shape>
+    </item>
+
+</selector>

+ 22 - 0
app/src/main/res/drawable/selector_check_password_hide.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+    <item android:state_checked="true">
+        <layer-list>
+            <item >
+                <bitmap android:src="@drawable/password_show_icon" android:tint="@color/white"/>
+            </item>
+        </layer-list>
+    </item>
+
+    <item android:state_checked="false">
+        <layer-list>
+            <item >
+                <bitmap android:src="@drawable/password_hide_icon" android:tint="@color/white"/>
+            </item>
+        </layer-list>
+    </item>
+
+</selector>
+

+ 6 - 0
app/src/main/res/drawable/shape_button_bg_circle.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+
+    <solid android:color="@color/button_box_bg"/>
+
+</shape>

+ 8 - 0
app/src/main/res/drawable/shape_button_bg_round.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <corners android:radius="17.5dp"/>
+
+    <solid android:color="@color/button_box_bg"/>
+
+</shape>

+ 8 - 0
app/src/main/res/drawable/shape_input_bg_round.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <corners android:radius="@dimen/d_5dp"/>
+
+    <solid android:color="@color/input_box_bg"/>
+
+</shape>

+ 12 - 0
app/src/main/res/drawable/shape_page_bg.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <gradient
+        android:startColor="#091D44"
+        android:centerColor="#245A8A"
+        android:endColor="#7F7CEC"
+        android:type="linear"
+        android:angle="270" />
+
+</shape>

+ 8 - 0
app/src/main/res/drawable/shape_search_bg_round.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <corners android:radius="17.5dp"/>
+
+    <solid android:color="@color/input_box_bg"/>
+
+</shape>

+ 128 - 0
app/src/main/res/layout/activity_login.xml

@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/shape_page_bg"
+    android:orientation="vertical">
+
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="@dimen/d_80dp"
+        android:layout_marginBottom="@dimen/d_45dp"
+        android:src="@drawable/yy_business_top_logo" />
+
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="@dimen/d_15dp"
+        android:layout_marginRight="@dimen/d_15dp"
+        android:background="@drawable/shape_input_bg_round"
+        android:orientation="vertical"
+        android:paddingLeft="@dimen/d_20dp"
+        android:paddingTop="@dimen/d_33dp"
+        android:paddingRight="@dimen/d_20dp"
+        android:paddingBottom="@dimen/d_33dp">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="sans-serif-medium"
+            android:text="@string/login_code"
+            android:textColor="@color/white"
+            android:textSize="@dimen/d_16sp"
+            android:textStyle="normal" />
+
+        <EditText
+            android:id="@+id/et_login_code"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/transparent"
+            android:fontFamily="sans-serif-medium"
+            android:hint="@string/please_enter_your_login_code"
+            android:paddingTop="@dimen/d_3dp"
+            android:paddingBottom="@dimen/d_3dp"
+            android:layout_marginTop="@dimen/d_5dp"
+            android:layout_marginBottom="@dimen/d_5dp"
+            android:textColor="@color/white"
+            android:textColorHint="@color/hint_text"
+            android:textSize="@dimen/d_15sp"
+            android:textStyle="normal" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/d_0.5dp"
+            android:layout_marginTop="@dimen/d_10dp"
+            android:background="@color/main_divider" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/d_18dp"
+            android:fontFamily="sans-serif-medium"
+            android:text="@string/password"
+            android:textColor="@color/white"
+            android:textSize="@dimen/d_16sp"
+            android:textStyle="normal" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal">
+
+            <EditText
+                android:id="@+id/et_password"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@color/transparent"
+                android:fontFamily="sans-serif-medium"
+                android:hint="@string/please_enter_your_password"
+                android:paddingTop="@dimen/d_8dp"
+                android:paddingBottom="@dimen/d_8dp"
+                android:inputType="textPassword"
+                android:textColor="@color/white"
+                android:textColorHint="@color/hint_text"
+                android:textSize="@dimen/d_15sp"
+                android:textStyle="normal" />
+
+            <CheckBox
+                android:id="@+id/cb_psd"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/d_5dp"
+                android:layout_marginRight="@dimen/d_5dp"
+                android:button="@drawable/selector_check_password_hide" />
+
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/d_0.5dp"
+            android:layout_marginTop="@dimen/d_10dp"
+            android:background="@color/main_divider" />
+
+
+        <Button
+            android:id="@+id/button_login"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/d_45dp"
+            android:layout_marginTop="@dimen/d_32dp"
+            android:background="@drawable/selector_button_yellow_round"
+            android:fontFamily="sans-serif-medium"
+            android:gravity="center"
+            android:text="@string/log_in"
+            android:textAllCaps="false"
+            android:textColor="@color/white"
+            android:textSize="@dimen/d_16sp"
+            android:textStyle="normal" />
+
+    </LinearLayout>
+
+
+</LinearLayout>

+ 110 - 161
app/src/main/res/layout/activity_main.xml

@@ -3,192 +3,141 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/white"
+    android:focusableInTouchMode="true"
+    android:focusable="true"
+    android:background="@drawable/shape_page_bg"
     android:orientation="vertical">
 
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_margin="@dimen/d_10dp"
-        android:text="测试弹窗" />
-
-    <LinearLayout
+    <View
+        android:id="@+id/status_bar_view"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-
-        <Button
-            android:id="@+id/btn_filter_popup"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="筛选"
-            android:textAllCaps="false" />
-
-        <Button
-            android:id="@+id/btn_dropdown_popup"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="下拉选"
-            android:textAllCaps="false" />
-
-        <Button
-            android:id="@+id/btn_keyboard_popup"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="软键盘弹窗"
-            android:textAllCaps="false" />
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-
-        <Button
-            android:id="@+id/btn_bottom_popup"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="底部弹窗"
-            android:textAllCaps="false" />
-
-        <Button
-            android:id="@+id/btn_center_popup"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="居中弹窗"
-            android:textAllCaps="false" />
-
-        <Button
-            android:id="@+id/btn_fullscreen_popup"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="全屏弹窗"
-            android:textAllCaps="false" />
-
-    </LinearLayout>
-
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_margin="@dimen/d_10dp"
-        android:text="首页的其他功能" />
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-
-        <Button
-            android:id="@+id/btn_Login"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="去登录页面" />
-
-        <Button
-            android:id="@+id/btn_push_id"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="获取PushId"
-            android:textAllCaps="false" />
-
-
-        <Button
-            android:id="@+id/btn_news"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="Pager页面"
-            android:textAllCaps="false" />
-
-    </LinearLayout>
+        android:layout_height="0dp"
+        android:background="@color/transparent"
+        android:visibility="visible" />
 
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-
-        <Button
-            android:id="@+id/btn_permission_camera"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="获取相机权限"
-            android:textAllCaps="false" />
+        android:layout_height="@dimen/d_60dp"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/d_15dp"
+        android:paddingRight="@dimen/d_15dp">
+
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="@dimen/d_35dp"
+            android:layout_weight="1"
+            android:background="@drawable/shape_search_bg_round"
+            android:gravity="center_vertical"
+            android:orientation="horizontal">
+
+
+            <EditText
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/d_14dp"
+                android:layout_marginRight="@dimen/d_14dp"
+                android:layout_weight="1"
+                android:background="@color/transparent"
+                android:hint="@string/name_or_mobile"
+                android:textColor="@color/white"
+                android:textColorHint="@color/hint_text"
+                android:textSize="@dimen/d_15sp"
+                android:textStyle="normal" />
+
+
+            <ImageView
+                android:padding="@dimen/d_5dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="@dimen/d_9dp"
+                android:src="@drawable/home_search_icon" />
+
+        </LinearLayout>
 
         <Button
-            android:id="@+id/btn_permission_phone"
+            android:id="@+id/btn_reset"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="获取电话权限"
-            android:textAllCaps="false" />
+            android:layout_height="@dimen/d_35dp"
+            android:layout_marginLeft="@dimen/d_10dp"
+            android:background="@drawable/shape_button_bg_round"
+            android:gravity="center"
+            android:paddingLeft="@dimen/d_11dp"
+            android:paddingRight="@dimen/d_11dp"
+            android:text="@string/reset"
+            android:textAllCaps="false"
+            android:textColor="@color/white"
+            android:textSize="@dimen/d_15sp"
+            android:textStyle="normal" />
+
+        <FrameLayout
+            android:id="@+id/btn_logout"
+            android:layout_width="@dimen/d_35dp"
+            android:layout_height="@dimen/d_35dp"
+            android:layout_marginLeft="@dimen/d_10dp"
+            android:background="@drawable/shape_button_bg_circle">
+
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:src="@drawable/home_export_icon" />
+
+        </FrameLayout>
 
     </LinearLayout>
 
-
-    <LinearLayout
+    <View
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-
-        <Button
-            android:id="@+id/btn_choose_camera"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="选择相机"
-            android:textAllCaps="false" />
-
-        <Button
-            android:id="@+id/btn_choose_album"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="选择相册"
-            android:textAllCaps="false" />
-
-    </LinearLayout>
+        android:layout_height="@dimen/d_0.5dp"
+        android:background="#415B7C" />
 
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="@dimen/d_35dp"
+        android:layout_margin="@dimen/d_15dp"
+        android:background="@drawable/shape_search_bg_round"
+        android:gravity="center_vertical"
         android:orientation="horizontal">
 
-        <Button
-            android:id="@+id/btn_deep_link"
-            android:layout_width="wrap_content"
+        <TextView
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="Navigation Deep Link"
-            android:textAllCaps="false" />
-
-        <Button
-            android:id="@+id/btn_load_shatter"
-            android:layout_width="wrap_content"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:text="2024-6-27"
+            android:textSize="@dimen/d_16sp"
+            android:textColor="@color/white" />
+
+        <View
+            android:layout_width="@dimen/d_0.5dp"
+            android:layout_height="match_parent"
+            android:layout_marginTop="@dimen/d_7dp"
+            android:layout_marginBottom="@dimen/d_7dp"
+            android:background="#52739C" />
+
+        <TextView
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_margin="@dimen/d_10dp"
-            android:text="加载Shatter"
-            android:textAllCaps="false" />
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/d_16sp"
+            android:text="2024-6-27"
+            android:textColor="@color/white" />
 
     </LinearLayout>
 
-    <LinearLayout
-        android:id="@+id/fl_content"
+    <me.yokeyword.indexablerv.IndexableLayout
+        android:id="@+id/indexableLayout"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical" >
-
-    </LinearLayout>
+        android:layout_height="match_parent"
+        app:indexBar_selectedTextColor="@color/selected_text"
+        app:indexBar_textColor="@color/white"
+        app:indexBar_textSize="15sp"
+        app:indexBar_textSpace="6dp" />
 
 </LinearLayout>

+ 15 - 0
app/src/main/res/layout/activity_splash.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/shape_page_bg"
+    android:orientation="vertical">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:src="@drawable/yy_business_top_logo" />
+
+</FrameLayout>

+ 42 - 0
app/src/main/res/layout/item_contact.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingBottom="16dp"
+    android:layout_marginLeft="@dimen/d_15dp"
+    android:layout_marginRight="@dimen/d_30dp"
+    android:layout_marginBottom="@dimen/d_10dp"
+    android:background="@drawable/shape_input_bg_round"
+    android:paddingLeft="16dp"
+    android:paddingRight="16dp"
+    android:paddingTop="8dp">
+
+    <ImageView
+        android:id="@+id/img_avatar"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/d_7dp"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="16dp"
+        android:src="@drawable/home_export_icon" />
+
+    <TextView
+        android:id="@+id/tv_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/img_avatar"
+        tools:text="张三"
+        android:textColor="@color/white"
+        android:textSize="16sp" />
+
+    <TextView
+        android:id="@+id/tv_mobile"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/tv_name"
+        android:layout_marginTop="12dp"
+        android:layout_toRightOf="@id/img_avatar"
+        tools:text="18712345678"
+        android:textColor="@color/white" />
+
+</RelativeLayout>

+ 18 - 0
app/src/main/res/layout/item_index_contact.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/tv_index"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="24dp"
+        android:paddingTop="8dp"
+        android:paddingRight="@dimen/d_30dp"
+        android:paddingBottom="8dp"
+        android:textColor="@color/white"
+        android:textSize="14sp"
+        android:textStyle="bold" />
+
+</FrameLayout>

+ 5 - 5
app/src/main/res/values/colors.xml

@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <color name="purple_200">#FFBB86FC</color>
-    <color name="purple_500">#FF6200EE</color>
-    <color name="purple_700">#FF3700B3</color>
-    <color name="teal_200">#FF03DAC5</color>
-    <color name="teal_700">#FF018786</color>
     <color name="black">#FF000000</color>
     <color name="white">#FFFFFFFF</color>
+    <color name="main_divider">#7BABC8</color>
+    <color name="input_box_bg">#334DCFF6</color>
+    <color name="button_box_bg">#802BA9F9</color>
+    <color name="selected_text">#2BA9F9</color>
+    <color name="hint_text">#AECAE5</color>
 </resources>

+ 114 - 0
app/src/main/res/values/string_contact.xml

@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string-array name="contact_array">
+        <item>Abigail</item>
+        <item>Alexander</item>
+        <item>Benjamin</item>
+        <item>Brian</item>
+        <item>Catherine</item>
+        <item>Christopher</item>
+        <item>David</item>
+        <item>Daniel</item>
+        <item>Emily</item>
+        <item>Elizabeth</item>
+        <item>Frank</item>
+        <item>Felicia</item>
+        <item>Grace</item>
+        <item>Gabriel</item>
+        <item>Henry</item>
+        <item>Hannah</item>
+        <item>Isabella</item>
+        <item>Isaac</item>
+        <item>John</item>
+        <item>Jessica</item>
+        <item>Katherine</item>
+        <item>Kevin</item>
+        <item>Leo</item>
+        <item>Laura</item>
+        <item>Mary</item>
+        <item>Matthew</item>
+        <item>Nathan</item>
+        <item>Nicole</item>
+        <item>Olivia</item>
+        <item>Oscar</item>
+        <item>Paul</item>
+        <item>Patricia</item>
+        <item>Quinn</item>
+        <item>Quentin</item>
+        <item>Rachel</item>
+        <item>Richard</item>
+        <item>Samuel</item>
+        <item>Sophia</item>
+        <item>Tiffany</item>
+        <item>Thomas</item>
+        <item>Ulysses</item>
+        <item>Una</item>
+        <item>Victoria</item>
+        <item>Vincent</item>
+        <item>William</item>
+        <item>Wendy</item>
+        <item>Xavier</item>
+        <item>Xenia</item>
+        <item>Yolanda</item>
+        <item>Yvonne</item>
+        <item>Zachary</item>
+        <item>Zoe</item>
+    </string-array>
+
+    <string-array name="mobile_array">
+        <item>13912345678</item>
+        <item>15801234567</item>
+        <item>18678901234</item>
+        <item>13545678901</item>
+        <item>18923456789</item>
+        <item>15034567890</item>
+        <item>13698765432</item>
+        <item>18811223344</item>
+        <item>17745678901</item>
+        <item>13856789012</item>
+        <item>15901234567</item>
+        <item>18523456789</item>
+        <item>13765432198</item>
+        <item>18098765432</item>
+        <item>15612345678</item>
+        <item>13478901234</item>
+        <item>18323456789</item>
+        <item>15701234567</item>
+        <item>13345678901</item>
+        <item>18523456789</item>
+        <item>18278901234</item>
+        <item>15398765432</item>
+        <item>13212345678</item>
+        <item>18187654321</item>
+        <item>15234567890</item>
+        <item>13165432198</item>
+        <item>18056789012</item>
+        <item>15101234567</item>
+        <item>13078901234</item>
+        <item>17923456789</item>
+        <item>15098765432</item>
+        <item>12912345678</item>
+        <item>17876543219</item>
+        <item>14834567890</item>
+        <item>12765432198</item>
+        <item>17612345678</item>
+        <item>14601234567</item>
+        <item>12578901234</item>
+        <item>17423456789</item>
+        <item>14498765432</item>
+        <item>15101234567</item>
+        <item>12312345678</item>
+        <item>17287654321</item>
+        <item>14234567890</item>
+        <item>12165432198</item>
+        <item>17056789012</item>
+        <item>14001234567</item>
+        <item>12078901234</item>
+        <item>16923456789</item>
+        <item>13998765432</item>
+        <item>15234567890</item>
+        <item>12165432198</item>
+    </string-array>
+
+
+</resources>

+ 7 - 0
app/src/main/res/values/strings.xml

@@ -1,3 +1,10 @@
 <resources>
     <string name="app_name">YY Business</string>
+    <string name="login_code">Login Code</string>
+    <string name="please_enter_your_login_code">Please enter your login code</string>
+    <string name="password">Password</string>
+    <string name="please_enter_your_password">Please enter your password</string>
+    <string name="log_in">Log in</string>
+    <string name="reset">Reset</string>
+    <string name="name_or_mobile">Name/Mobile</string>
 </resources>

+ 3 - 3
buildSrc/src/main/kotlin/ProjectConfig.kt

@@ -11,7 +11,7 @@ object BuildConfig {
     const val versionCode = 100
     const val versionName = "1.0.0"
 
-    const val applicationId = "com.newki.template"
+    const val applicationId = "vn.hongyegroup.yybusiness"
     const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
 
 }
@@ -23,10 +23,10 @@ object ProjectConfig {
     const val isReleaseServer = false      //服务器环境,测试环境设置 false
 
     // 测试环境的域名
-    const val baseUrl_dev = "http://www.wanandroid.com/"
+    const val baseUrl_dev = "http://vietnam-dev.casualabour.com"
 
     // 正式环境的域名
-    const val baseUrl_rel = "https://www.wanandroid.com/"
+    const val baseUrl_rel = "http://vietnam.casualabour.com"
 }
 
 //签名文件信息配置

+ 1 - 7
cs-baselib/src/main/java/com/android/basiclib/base/activity/AbsActivity.kt

@@ -42,8 +42,7 @@ abstract class AbsActivity : AppCompatActivity(), ConnectivityReceiver.Connectiv
      * 设置顶部状态栏的颜色(默认为白色背景-黑色文字)
      */
     protected fun setStatusBarColor(): Int {
-        //如果状态栏文字能变黑那么背景设置为白色,否则返回背景灰色文本默认为白色
-        return if (StatusBarUtils.setStatusBarBlackText(this)) {
+        return if (StatusBarUtils.setStatusBarWhiteText(this)) {
             Color.WHITE
         } else {
             Color.parseColor("#B0B0B0")
@@ -121,11 +120,6 @@ abstract class AbsActivity : AppCompatActivity(), ConnectivityReceiver.Connectiv
         return false
     }
 
-    //此页面是否启动Shatter
-    protected open fun isShatterEnable(): Boolean {
-        return false
-    }
-
     override fun onNewIntent(intent: Intent?) {
         super.onNewIntent(intent)
     }

+ 0 - 6
cs-baselib/src/main/java/com/android/basiclib/base/fragment/AbsFragment.kt

@@ -12,7 +12,6 @@ import com.android.basiclib.receiver.ConnectivityReceiver
 import com.android.basiclib.utils.StatusBarUtils
 
 
-
 /**
  * 普通的Fragment,基类Fragment
  */
@@ -75,11 +74,6 @@ abstract class AbsFragment : Fragment(), ConnectivityReceiver.ConnectivityReceiv
         return false
     }
 
-    //此页面是否启动Shatter
-    protected open fun isShatterEnable(): Boolean {
-        return false
-    }
-
     /**
      * 设置状态栏的文本颜色
      */

+ 1 - 1
cs-baselib/src/main/java/com/android/basiclib/core/BaseLibCore.kt

@@ -29,7 +29,7 @@ import java.io.File
  */
 object BaseLibCore {
 
-    const val successCode = 0   //指定网络请求成功的 Code,一般为 200 或者 0
+    const val successCode = 200   //指定网络请求成功的 Code,一般为 200 或者 0
 
     /**
      * 初始化全局工具类和图片加载引擎

+ 17 - 9
cs-baselib/src/main/res/values/styles.xml

@@ -3,27 +3,35 @@
     <!-- Base application theme. -->
     <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
         <!-- Customize your theme here. -->
-        <item name="colorPrimary">@color/colorPrimary</item>
-        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
-        <item name="colorAccent">@color/colorAccent</item>
+        <item name="colorPrimary">@color/white</item>
+        <item name="colorPrimaryDark">@color/white</item>
+        <item name="colorAccent">@color/white</item>
         <item name="android:windowAnimationStyle">@style/my_animation_activity</item>
         <item name="android:windowBackground">@null</item>
     </style>
 
     <style name="AppThemeLight" parent="Theme.AppCompat.DayNight.NoActionBar">
         <!-- Customize your theme here. -->
-        <item name="colorPrimary">@color/colorPrimary</item>
-        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
-        <item name="colorAccent">@color/colorAccent</item>
+        <item name="colorPrimary">@color/white</item>
+        <item name="colorPrimaryDark">@color/white</item>
+        <item name="colorAccent">@color/white</item>
         <item name="android:windowAnimationStyle">@style/my_animation_activity</item>
     </style>
 
+    <!--app-splash的启动theme,不带状态栏-->
+    <style name="AppTheme_welcome" parent="Theme.AppCompat.Light.NoActionBar">
+        <item name="android:windowBackground">@drawable/layer_start_window</item>
+        <item name="colorPrimary">@color/white</item>
+        <item name="colorPrimaryDark">@color/white</item>
+        <item name="colorAccent">@color/white</item>
+        <item name="android:windowAnimationStyle">@style/my_animation_activity</item>
+    </style>
 
     <style name="AppThemeLightDialog" parent="Theme.AppCompat.DayNight.Dialog">
         <!-- Customize your theme here. -->
-        <item name="colorPrimary">@color/colorPrimary</item>
-        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
-        <item name="colorAccent">@color/colorAccent</item>
+        <item name="colorPrimary">@color/white</item>
+        <item name="colorPrimaryDark">@color/white</item>
+        <item name="colorAccent">@color/white</item>
     </style>
 
 

+ 11 - 4
cs-service/src/main/java/com/android/cs_service/CommApi.kt

@@ -9,11 +9,18 @@ class CommApi {
         //服务器域名环境
         const val BASE_URL = BuildConfig.BASE_URL
 
-        //首页Banner
-        const val BANNER_URL = "/banner/json"
+        //登录
+        const val LOGIN_IN = "/index.php/api/v1/sign/login"
+
+        //登出
+        const val LOG_OUT = "/index.php/api/v1/sign/logout"
+
+        //申请列表
+        const val APPLIED_LIST = "/index.php/api/v1/sign/applied"
+
+        //签到签出
+        const val CHECK_IN_OUT = "/index.php/api/v1/sign/applied/clock"
 
-        //置顶文章
-        const val TOP_ARTICLE_URL = "/article/top/json"
 
     }
 

+ 1 - 0
cs-service/src/main/java/com/android/cs_service/Constants.kt

@@ -6,6 +6,7 @@ class Constants {
         //应用编译环境
         var IS_DEBUG = BuildConfig.DEBUG
 
+        const val KEY_TOKEN = "key_token"
     }
 
 }

+ 1 - 0
lib-indexablerecyclerview/.gitignore

@@ -0,0 +1 @@
+/build

+ 15 - 0
lib-indexablerecyclerview/build.gradle.kts

@@ -0,0 +1,15 @@
+plugins {
+    id("com.android.library")
+}
+
+// 使用自定义插件
+apply<DefaultGradlePlugin>()
+
+android {
+    namespace = "me.yokeyword.indexablerv"
+}
+
+dependencies {
+    api("com.github.promeg:tinypinyin:2.0.3")
+//    api("com.github.promeg:tinypinyin-lexicons-android-cncity:2.0.3")
+}

+ 17 - 0
lib-indexablerecyclerview/proguard-rules.pro

@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/YoKeyword/Documents/android-sdk-macosx/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 5 - 0
lib-indexablerecyclerview/src/main/AndroidManifest.xml

@@ -0,0 +1,5 @@
+<manifest package="me.yokeyword.indexablerv">
+
+    <application />
+
+</manifest>

+ 188 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/AbstractHeaderFooterAdapter.java

@@ -0,0 +1,188 @@
+package me.yokeyword.indexablerv;
+
+import androidx.recyclerview.widget.RecyclerView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import me.yokeyword.indexablerv.database.HeaderFooterDataObservable;
+import me.yokeyword.indexablerv.database.HeaderFooterDataObserver;
+import me.yokeyword.indexablerv.database.IndexBarDataObservable;
+import me.yokeyword.indexablerv.database.IndexBarDataObserver;
+
+/**
+ * Created by YoKey on 16/10/16.
+ */
+
+abstract class AbstractHeaderFooterAdapter<T> {
+    private final HeaderFooterDataObservable mDataSetObservable = new HeaderFooterDataObservable();
+    private final IndexBarDataObservable mIndexBarDataSetObservable = new IndexBarDataObservable();
+
+    ArrayList<EntityWrapper<T>> mEntityWrapperList = new ArrayList<>();
+    protected OnItemClickListener<T> mListener;
+    protected OnItemLongClickListener<T> mLongListener;
+
+    private String mIndex, mIndexTitle;
+
+    /**
+     * 不想显示哪个就传null
+     *
+     * @param index      IndexBar的字母索引
+     * @param indexTitle IndexTitle
+     * @param datas      数据源
+     */
+    public AbstractHeaderFooterAdapter(String index, String indexTitle, List<T> datas) {
+        this.mIndex = index;
+        this.mIndexTitle = indexTitle;
+
+        if (indexTitle != null) {
+            EntityWrapper<T> wrapper = wrapEntity();
+            wrapper.setItemType(EntityWrapper.TYPE_TITLE);
+        }
+        for (int i = 0; i < datas.size(); i++) {
+            EntityWrapper<T> wrapper = wrapEntity();
+            wrapper.setData(datas.get(i));
+        }
+    }
+
+    private EntityWrapper<T> wrapEntity() {
+        EntityWrapper<T> wrapper = new EntityWrapper<>();
+        wrapper.setIndex(mIndex);
+        wrapper.setIndexTitle(mIndexTitle);
+        wrapper.setHeaderFooterType(getHeaderFooterType());
+        mEntityWrapperList.add(wrapper);
+        return wrapper;
+    }
+
+    private EntityWrapper<T> wrapEntity(int pos) {
+        EntityWrapper<T> wrapper = new EntityWrapper<>();
+        wrapper.setIndex(mIndex);
+        wrapper.setIndexTitle(mIndexTitle);
+        wrapper.setHeaderFooterType(getHeaderFooterType());
+        mEntityWrapperList.add(pos, wrapper);
+        return wrapper;
+    }
+
+    public abstract int getItemViewType();
+
+    public abstract RecyclerView.ViewHolder onCreateContentViewHolder(ViewGroup parent);
+
+    public abstract void onBindContentViewHolder(RecyclerView.ViewHolder holder, T entity);
+
+    /**
+     * Notifies the attached observers that the underlying data has been changed
+     * and any View reflecting the data set should refresh itself.
+     */
+    public void notifyDataSetChanged() {
+        mDataSetObservable.notifyChanged();
+    }
+
+    public void addData(T data) {
+        int size = mEntityWrapperList.size();
+
+        EntityWrapper<T> wrapper = wrapEntity();
+        wrapper.setItemType(getItemViewType());
+        wrapper.setData(data);
+
+        if (size > 0) {
+            mDataSetObservable.notifyAdd(getHeaderFooterType() == EntityWrapper.TYPE_HEADER, mEntityWrapperList.get(size - 1), wrapper);
+            mIndexBarDataSetObservable.notifyChanged();
+        }
+    }
+
+    public void removeData(T data) {
+        for (EntityWrapper wrapper : mEntityWrapperList) {
+            if (wrapper.getData() == data) {
+                mEntityWrapperList.remove(wrapper);
+                mDataSetObservable.notifyRemove(getHeaderFooterType() == EntityWrapper.TYPE_HEADER, wrapper);
+                mIndexBarDataSetObservable.notifyChanged();
+                return;
+            }
+        }
+    }
+
+    int getHeaderFooterType() {
+        return EntityWrapper.TYPE_HEADER;
+    }
+
+    public void addData(int position, T data) {
+        int size = mEntityWrapperList.size();
+        if (position >= size) {
+            return;
+        }
+
+        EntityWrapper<T> wrapper = wrapEntity(position + 1);
+        wrapper.setItemType(getItemViewType());
+        wrapper.setData(data);
+
+        if (size > 0) {
+            mDataSetObservable.notifyAdd(getHeaderFooterType() == EntityWrapper.TYPE_HEADER, mEntityWrapperList.get(position), wrapper);
+            mIndexBarDataSetObservable.notifyChanged();
+        }
+    }
+
+    public void addDatas(List<T> datas) {
+        for (int i = 0; i < datas.size(); i++) {
+            addData(datas.get(i));
+        }
+    }
+
+    public void addDatas(int position, List<T> datas) {
+        int size = mEntityWrapperList.size();
+        if (position >= size) {
+            return;
+        }
+
+        for (int i = datas.size() - 1; i >= 0; i--) {
+            addData(position, datas.get(i));
+        }
+    }
+
+//    public void removeAll(List<T> datas) {
+//        // TODO: 16/10/27
+//    }
+
+    OnItemClickListener<T> getOnItemClickListener() {
+        return mListener;
+    }
+
+
+    OnItemLongClickListener getOnItemLongClickListener() {
+        return mLongListener;
+    }
+
+    ArrayList<EntityWrapper<T>> getDatas() {
+        for (EntityWrapper<T> wrapper : mEntityWrapperList) {
+            if (wrapper.getItemType() == EntityWrapper.TYPE_CONTENT) {
+                wrapper.setItemType(getItemViewType());
+            }
+        }
+        return mEntityWrapperList;
+    }
+
+    void registerDataSetObserver(HeaderFooterDataObserver observer) {
+        mDataSetObservable.registerObserver(observer);
+    }
+
+    void unregisterDataSetObserver(HeaderFooterDataObserver observer) {
+        mDataSetObservable.unregisterObserver(observer);
+    }
+
+    void registerIndexBarDataSetObserver(IndexBarDataObserver observer) {
+        mIndexBarDataSetObservable.registerObserver(observer);
+    }
+
+    void unregisterIndexBarDataSetObserver(IndexBarDataObserver observer) {
+        mIndexBarDataSetObservable.unregisterObserver(observer);
+    }
+
+    interface OnItemClickListener<T> {
+        void onItemClick(View v, int currentPosition, T entity);
+    }
+
+    interface OnItemLongClickListener<T> {
+        boolean onItemLongClick(View v, int currentPosition, T entity);
+    }
+}

+ 111 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/EntityWrapper.java

@@ -0,0 +1,111 @@
+package me.yokeyword.indexablerv;
+
+/**
+ * Created by YoKey on 16/10/6.
+ */
+public class EntityWrapper<T> {
+    static final int TYPE_TITLE = Integer.MAX_VALUE - 1;
+    static final int TYPE_CONTENT = Integer.MAX_VALUE;
+
+    static final int TYPE_HEADER = 1;
+    static final int TYPE_FOOTER = 2;
+
+    private String index;
+    private String indexTitle;
+    private String pinyin;
+    private String indexByField;
+    private T data;
+    private int originalPosition = -1;
+    private int itemType = TYPE_CONTENT;
+    private int headerFooterType;
+
+    EntityWrapper() {
+    }
+
+    EntityWrapper(String index, int itemType) {
+        this.index = index;
+        this.indexTitle = index;
+        this.pinyin = index;
+        this.itemType = itemType;
+    }
+
+    public String getIndex() {
+        return index;
+    }
+
+    void setIndex(String index) {
+        this.index = index;
+    }
+
+    public String getIndexTitle() {
+        return indexTitle;
+    }
+
+    void setIndexTitle(String indexTitle) {
+        this.indexTitle = indexTitle;
+    }
+
+    public String getPinyin() {
+        return pinyin;
+    }
+
+    void setPinyin(String pinyin) {
+        this.pinyin = pinyin;
+    }
+
+    public String getIndexByField() {
+        return indexByField;
+    }
+
+    void setIndexByField(String indexByField) {
+        this.indexByField = indexByField;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    void setData(T data) {
+        this.data = data;
+    }
+
+    public int getOriginalPosition() {
+        return originalPosition;
+    }
+
+    void setOriginalPosition(int originalPosition) {
+        this.originalPosition = originalPosition;
+    }
+
+    int getItemType() {
+        return itemType;
+    }
+
+    void setItemType(int itemType) {
+        this.itemType = itemType;
+    }
+
+    int getHeaderFooterType() {
+        return headerFooterType;
+    }
+
+    void setHeaderFooterType(int headerFooterType) {
+        this.headerFooterType = headerFooterType;
+    }
+
+    public boolean isTitle(){
+        return itemType == TYPE_TITLE;
+    }
+
+    public boolean isContent(){
+        return itemType == TYPE_CONTENT;
+    }
+
+    public boolean isHeader(){
+        return headerFooterType == TYPE_HEADER;
+    }
+
+    public boolean isFooter(){
+        return headerFooterType == TYPE_FOOTER;
+    }
+}

+ 187 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexBar.java

@@ -0,0 +1,187 @@
+package me.yokeyword.indexablerv;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.TypedValue;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+
+/**
+ * Created by YoKey on 16/10/6.
+ */
+class IndexBar extends View {
+    private int mTotalHeight;
+    private float mTextSpace;
+
+    private List<String> mIndexList = new ArrayList<>();
+    // 首字母 到 mIndexList 的映射
+    private HashMap<String, Integer> mMapping = new HashMap<>();
+    private ArrayList<EntityWrapper> mDatas;
+
+    private int mSelectionPosition;
+    private float mIndexHeight;
+
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private Paint mFocusPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+    public IndexBar(Context context) {
+        super(context);
+    }
+
+    void init(Drawable barBg, int barTextColor, int barFocusTextColor, float barTextSize, float textSpace) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            setBackground(barBg);
+        } else {
+            setBackgroundDrawable(barBg);
+        }
+
+        this.mTextSpace = textSpace;
+
+        mPaint.setColor(barTextColor);
+        mPaint.setTextAlign(Paint.Align.CENTER);
+        mPaint.setTextSize(barTextSize);
+
+        mFocusPaint.setTextAlign(Paint.Align.CENTER);
+        mFocusPaint.setTextSize(barTextSize + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()));
+        mFocusPaint.setColor(barFocusTextColor);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int mode = MeasureSpec.getMode(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+
+        if (mIndexList.size() > 0) {
+            mTotalHeight = (int) (((mIndexList.size() - 1) * mPaint.getTextSize()
+                    + mFocusPaint.getTextSize())
+                    + (mIndexList.size() + 1) * mTextSpace);
+        }
+
+        if (mTotalHeight > height) {
+            mTotalHeight = height;
+        }
+
+//        // TODO: 16/10/8  Measure AT_MOST
+//        if (mode == MeasureSpec.AT_MOST) {
+//            int maxWidth = (int) getResources().getDimension(R.dimen.default_indexBar_layout_width);
+//            super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mTotalHeight, MeasureSpec.EXACTLY));
+//            return;
+//        }
+        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(mTotalHeight, MeasureSpec.EXACTLY));
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mIndexList.size() == 0) return;
+
+        mIndexHeight = ((float) getHeight()) / mIndexList.size();
+
+        for (int i = 0; i < mIndexList.size(); i++) {
+            if (mSelectionPosition == i) {
+                canvas.drawText(mIndexList.get(i), getWidth() / 2, mIndexHeight * 0.85f + mIndexHeight * i, mFocusPaint);
+            } else {
+                canvas.drawText(mIndexList.get(i), getWidth() / 2, mIndexHeight * 0.85f + mIndexHeight * i, mPaint);
+            }
+        }
+    }
+
+    int getPositionForPointY(float y) {
+        if (mIndexList.size() <= 0) return -1;
+
+        int position = (int) (y / mIndexHeight);
+
+        if (position < 0) {
+            position = 0;
+        } else if (position > mIndexList.size() - 1) {
+            position = mIndexList.size() - 1;
+        }
+
+        return position;
+    }
+
+
+    int getSelectionPosition() {
+        return mSelectionPosition;
+    }
+
+    void setSelectionPosition(int position) {
+        this.mSelectionPosition = position;
+        invalidate();
+    }
+
+    int getFirstRecyclerViewPositionBySelection() {
+        String index = mIndexList.get(mSelectionPosition);
+        if (mMapping.containsKey(index)) {
+            return mMapping.get(index);
+        }
+        return -1;
+    }
+
+    List<String> getIndexList() {
+        return mIndexList;
+    }
+
+    void setDatas(boolean showAllLetter, ArrayList<EntityWrapper> datas) {
+        this.mDatas = datas;
+        this.mIndexList.clear();
+        this.mMapping.clear();
+
+        ArrayList<String> tempHeaderList = null;
+        if (showAllLetter) {
+            mIndexList = Arrays.asList(getResources().getStringArray(R.array.indexable_letter));
+            mIndexList = new ArrayList<>(mIndexList);
+            tempHeaderList = new ArrayList<>();
+        }
+        for (int i = 0; i < datas.size(); i++) {
+            EntityWrapper wrapper = datas.get(i);
+            if (wrapper.getItemType() == EntityWrapper.TYPE_TITLE || wrapper.getIndexTitle() == null) {
+                String index = wrapper.getIndex();
+                if (!TextUtils.isEmpty(index)) {
+                    if (!showAllLetter) {
+                        mIndexList.add(index);
+                    } else {
+                        if (IndexableLayout.INDEX_SIGN.equals(index)) {
+                            mIndexList.add(IndexableLayout.INDEX_SIGN);
+                        } else if (mIndexList.indexOf(index) < 0) {
+                            if (wrapper.getHeaderFooterType() == EntityWrapper.TYPE_HEADER && tempHeaderList.indexOf(index) < 0) {
+                                tempHeaderList.add(index);
+                            } else if (wrapper.getHeaderFooterType() == EntityWrapper.TYPE_FOOTER) {
+                                mIndexList.add(index);
+                            }
+                        }
+                    }
+                    if (!mMapping.containsKey(index)) {
+                        mMapping.put(index, i);
+                    }
+                }
+            }
+        }
+        if (showAllLetter) {
+            mIndexList.addAll(0, tempHeaderList);
+        }
+        requestLayout();
+    }
+
+    void setSelection(int firstVisibleItemPosition) {
+        if (mDatas == null || mDatas.size() <= firstVisibleItemPosition || firstVisibleItemPosition < 0)
+            return;
+        EntityWrapper wrapper = mDatas.get(firstVisibleItemPosition);
+        int position = mIndexList.indexOf(wrapper.getIndex());
+
+        if (mSelectionPosition != position && position >= 0) {
+            mSelectionPosition = position;
+            invalidate();
+        }
+    }
+}

+ 152 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexableAdapter.java

@@ -0,0 +1,152 @@
+package me.yokeyword.indexablerv;
+
+import androidx.recyclerview.widget.RecyclerView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+import me.yokeyword.indexablerv.database.DataObservable;
+import me.yokeyword.indexablerv.database.DataObserver;
+
+/**
+ * Created by YoKey on 16/10/6.
+ */
+public abstract class IndexableAdapter<T extends IndexableEntity> {
+    static final int TYPE_ALL = 0;
+    static final int TYPE_CLICK_TITLE = 1;
+    static final int TYPE_CLICK_CONTENT = 2;
+    static final int TYPE_LONG_CLICK_TITLE = 3;
+    static final int TYPE_LONG_CLICK_CONTENT = 4;
+    private final DataObservable mDataSetObservable = new DataObservable();
+
+    private List<T> mDatas;
+
+    private IndexCallback<T> mCallback;
+    private OnItemTitleClickListener mTitleClickListener;
+    private OnItemContentClickListener mContentClickListener;
+    private OnItemTitleLongClickListener mTitleLongClickListener;
+    private OnItemContentLongClickListener mContentLongClickListener;
+
+    public abstract RecyclerView.ViewHolder onCreateTitleViewHolder(ViewGroup parent);
+
+    public abstract RecyclerView.ViewHolder onCreateContentViewHolder(ViewGroup parent);
+
+    public abstract void onBindTitleViewHolder(RecyclerView.ViewHolder holder, String indexTitle);
+
+    public abstract void onBindContentViewHolder(RecyclerView.ViewHolder holder, T entity);
+
+    public void setDatas(List<T> datas) {
+        setDatas(datas, null);
+    }
+
+    /**
+     * @param callback Register a callback to be invoked when this datas is processed.
+     */
+    public void setDatas(List<T> datas, IndexCallback<T> callback) {
+        this.mCallback = callback;
+        mDatas = datas;
+        notifyInited();
+    }
+
+    /**
+     * set Index-ItemView click listener
+     */
+    public void setOnItemTitleClickListener(OnItemTitleClickListener listener) {
+        this.mTitleClickListener = listener;
+        notifySetListener(TYPE_CLICK_TITLE);
+    }
+
+    /**
+     * set Content-ItemView click listener
+     */
+    public void setOnItemContentClickListener(OnItemContentClickListener<T> listener) {
+        this.mContentClickListener = listener;
+        notifySetListener(TYPE_CLICK_CONTENT);
+    }
+
+    /**
+     * set Index-ItemView longClick listener
+     */
+    public void setOnItemTitleLongClickListener(OnItemTitleLongClickListener listener) {
+        this.mTitleLongClickListener = listener;
+        notifySetListener(TYPE_LONG_CLICK_TITLE);
+    }
+
+    /**
+     * set Content-ItemView longClick listener
+     */
+    public void setOnItemContentLongClickListener(OnItemContentLongClickListener<T> listener) {
+        this.mContentLongClickListener = listener;
+        notifySetListener(TYPE_LONG_CLICK_CONTENT);
+    }
+
+    /**
+     * Notifies the attached observers that the underlying data has been changed
+     * and any View reflecting the data set should refresh itself.
+     */
+    public void notifyDataSetChanged() {
+        mDataSetObservable.notifyInited();
+//        mDataSetObservable.notifyChanged();
+    }
+
+    private void notifyInited() {
+        mDataSetObservable.notifyInited();
+    }
+
+    private void notifySetListener(int type) {
+        mDataSetObservable.notifySetListener(type);
+    }
+
+    public List<T> getItems() {
+        return mDatas;
+    }
+
+    IndexCallback<T> getIndexCallback() {
+        return mCallback;
+    }
+
+    OnItemTitleClickListener getOnItemTitleClickListener() {
+        return mTitleClickListener;
+    }
+
+    OnItemTitleLongClickListener getOnItemTitleLongClickListener() {
+        return mTitleLongClickListener;
+    }
+
+    OnItemContentClickListener getOnItemContentClickListener() {
+        return mContentClickListener;
+    }
+
+    OnItemContentLongClickListener getOnItemContentLongClickListener() {
+        return mContentLongClickListener;
+    }
+
+    void registerDataSetObserver(DataObserver observer) {
+        mDataSetObservable.registerObserver(observer);
+    }
+
+    void unregisterDataSetObserver(DataObserver observer) {
+        mDataSetObservable.unregisterObserver(observer);
+    }
+
+    public interface IndexCallback<T> {
+        void onFinished(List<EntityWrapper<T>> datas);
+    }
+
+    public interface OnItemTitleClickListener {
+        void onItemClick(View v, int currentPosition, String indexTitle);
+    }
+
+    public interface OnItemContentClickListener<T> {
+        void onItemClick(View v, int originalPosition, int currentPosition, T entity);
+    }
+
+    public interface OnItemTitleLongClickListener {
+        boolean onItemLongClick(View v, int currentPosition, String indexTitle);
+    }
+
+    public interface OnItemContentLongClickListener<T> {
+        boolean onItemLongClick(View v, int originalPosition, int currentPosition, T entity);
+    }
+}

+ 13 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexableEntity.java

@@ -0,0 +1,13 @@
+package me.yokeyword.indexablerv;
+
+/**
+ * Created by YoKey on 16/10/9.
+ */
+public interface IndexableEntity {
+
+    String getFieldIndexBy();
+
+    void setFieldIndexBy(String indexField);
+
+    void setFieldPinyinIndexBy(String pinyin);
+}

+ 39 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexableFooterAdapter.java

@@ -0,0 +1,39 @@
+package me.yokeyword.indexablerv;
+
+import java.util.List;
+
+/**
+ * FooterView Adapter
+ * Created by YoKey on 16/10/14.
+ */
+public abstract class IndexableFooterAdapter<T> extends AbstractHeaderFooterAdapter<T> {
+
+    public IndexableFooterAdapter(String index, String indexTitle, List<T> datas) {
+        super(index, indexTitle, datas);
+    }
+
+    @Override
+    int getHeaderFooterType() {
+        return EntityWrapper.TYPE_FOOTER;
+    }
+
+    /**
+     * set Content-ItemView click listener
+     */
+    public void setOnItemFooterClickListener(OnItemFooterClickListener<T> listener) {
+        this.mListener = listener;
+    }
+
+    /**
+     * set Content-ItemView longClick listener
+     */
+    public void setOnItemFooterLongClickListener(OnItemFooterLongClickListener<T> listener) {
+        this.mLongListener = listener;
+    }
+
+    public interface OnItemFooterClickListener<T> extends OnItemClickListener<T>{
+    }
+
+    public interface OnItemFooterLongClickListener<T> extends OnItemLongClickListener<T>{
+    }
+}

+ 39 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexableHeaderAdapter.java

@@ -0,0 +1,39 @@
+package me.yokeyword.indexablerv;
+
+import java.util.List;
+
+/**
+ * HeaderView Adapter
+ * Created by YoKey on 16/10/8.
+ */
+public abstract class IndexableHeaderAdapter<T> extends AbstractHeaderFooterAdapter<T>{
+
+    public IndexableHeaderAdapter(String index, String indexTitle, List<T> datas) {
+        super(index, indexTitle, datas);
+    }
+
+    @Override
+    int getHeaderFooterType() {
+        return EntityWrapper.TYPE_HEADER;
+    }
+
+    /**
+     * set Content-ItemView click listener
+     */
+    public void setOnItemHeaderClickListener(OnItemHeaderClickListener<T> listener) {
+        this.mListener = listener;
+    }
+
+    /**
+     * set Content-ItemView longClick listener
+     */
+    public void setOnItemHeaderLongClickListener(OnItemHeaderLongClickListener<T> listener) {
+        this.mLongListener = listener;
+    }
+
+    public interface OnItemHeaderClickListener<T> extends OnItemClickListener<T>{
+    }
+
+    public interface OnItemHeaderLongClickListener<T> extends OnItemLongClickListener<T>{
+    }
+}

+ 748 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/IndexableLayout.java

@@ -0,0 +1,748 @@
+package me.yokeyword.indexablerv;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import androidx.annotation.IntDef;
+import androidx.appcompat.widget.AppCompatTextView;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import me.yokeyword.indexablerv.database.DataObserver;
+import me.yokeyword.indexablerv.database.HeaderFooterDataObserver;
+import me.yokeyword.indexablerv.database.IndexBarDataObserver;
+
+/**
+ * RecyclerView + IndexBar
+ * Created by YoKey on 16/10/6.
+ */
+@SuppressWarnings("unchecked")
+public class IndexableLayout extends FrameLayout {
+    // 快速排序,只比对首字母(默认)
+    public static final int MODE_FAST = 0;
+    // 全字母比较排序, 效率最低
+    public static final int MODE_ALL_LETTERS = 1;
+    // 每个字母模块内:无需排序,效率最高
+    public static final int MODE_NONE = 2;
+
+    private static int PADDING_RIGHT_OVERLAY;
+    static final String INDEX_SIGN = "#";
+
+    private Context mContext;
+    private boolean mShowAllLetter = true;
+
+    private ExecutorService mExecutorService;
+    private Future mFuture;
+
+    private RecyclerView mRecy;
+    private IndexBar mIndexBar;
+    /**
+     * 保存正在Invisible的ItemView
+     * <p>
+     * 使用mLastInvisibleRecyclerViewItemView来保存当前Invisible的ItemView,
+     * 每次有新的ItemView需要Invisible的时候,把旧的Invisible的ItemView设为Visible。
+     * 这样就修复了View复用导致的Invisible状态传递的问题。
+     */
+    private View mLastInvisibleRecyclerViewItemView;
+
+    private boolean mSticyEnable = true;
+    private RecyclerView.ViewHolder mStickyViewHolder;
+    private String mStickyTitle;
+
+    private RealAdapter mRealAdapter;
+    private RecyclerView.LayoutManager mLayoutManager;
+
+    private IndexableAdapter mIndexableAdapter;
+
+    private TextView mCenterOverlay, mMDOverlay;
+
+    private int mBarTextColor, mBarFocusTextColor;
+    private float mBarTextSize, mBarTextSpace, mBarWidth;
+    private Drawable mBarBg;
+
+    private DataObserver mDataSetObserver;
+
+    private int mCompareMode = MODE_FAST;
+    private Comparator mComparator;
+    private Handler mHandler;
+
+    private HeaderFooterDataObserver<EntityWrapper> mHeaderFooterDataSetObserver = new HeaderFooterDataObserver<EntityWrapper>() {
+        @Override
+        public void onChanged() {
+            if (mRealAdapter == null) return;
+            mRealAdapter.notifyDataSetChanged();
+        }
+
+        @Override
+        public void onAdd(boolean header, EntityWrapper preData, EntityWrapper data) {
+            if (mRealAdapter == null) return;
+            mRealAdapter.addHeaderFooterData(header, preData, data);
+        }
+
+        @Override
+        public void onRemove(boolean header, EntityWrapper data) {
+            if (mRealAdapter == null) return;
+            mRealAdapter.removeHeaderFooterData(header, data);
+        }
+    };
+
+    private IndexBarDataObserver mIndexBarDataSetObserver = new IndexBarDataObserver() {
+        @Override
+        public void onChanged() {
+            mIndexBar.setDatas(mShowAllLetter, mRealAdapter.getItems());
+        }
+    };
+
+    public IndexableLayout(Context context) {
+        this(context, null);
+    }
+
+    public IndexableLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public IndexableLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs);
+    }
+
+    /**
+     * set RealAdapter
+     * {@link #setLayoutManager(RecyclerView.LayoutManager)}
+     */
+    public <T extends IndexableEntity> void setAdapter(final IndexableAdapter<T> adapter) {
+
+        if (mLayoutManager == null) {
+            throw new NullPointerException("You must set the LayoutManager first");
+        }
+
+        this.mIndexableAdapter = adapter;
+
+        if (mDataSetObserver != null) {
+            adapter.unregisterDataSetObserver(mDataSetObserver);
+        }
+        mDataSetObserver = new DataObserver() {
+
+            @Override
+            public void onInited() {
+                onSetListener(IndexableAdapter.TYPE_ALL);
+                onDataChanged();
+            }
+
+            @Override
+            public void onChanged() {
+                if (mRealAdapter != null) {
+                    mRealAdapter.notifyDataSetChanged();
+                }
+            }
+
+            @Override
+            public void onSetListener(int type) {
+                // set listeners
+                if ((type == IndexableAdapter.TYPE_CLICK_TITLE || type == IndexableAdapter.TYPE_ALL) && adapter.getOnItemTitleClickListener() != null) {
+                    mRealAdapter.setOnItemTitleClickListener(adapter.getOnItemTitleClickListener());
+                }
+                if ((type == IndexableAdapter.TYPE_LONG_CLICK_TITLE || type == IndexableAdapter.TYPE_ALL) && adapter.getOnItemTitleLongClickListener() != null) {
+                    mRealAdapter.setOnItemTitleLongClickListener(adapter.getOnItemTitleLongClickListener());
+                }
+                if ((type == IndexableAdapter.TYPE_CLICK_CONTENT || type == IndexableAdapter.TYPE_ALL) && adapter.getOnItemContentClickListener() != null) {
+                    mRealAdapter.setOnItemContentClickListener(adapter.getOnItemContentClickListener());
+                }
+                if ((type == IndexableAdapter.TYPE_LONG_CLICK_CONTENT || type == IndexableAdapter.TYPE_ALL) && adapter.getOnItemContentLongClickListener() != null) {
+                    mRealAdapter.setOnItemContentLongClickListener(adapter.getOnItemContentLongClickListener());
+                }
+            }
+        };
+
+        adapter.registerDataSetObserver(mDataSetObserver);
+        mRealAdapter.setIndexableAdapter(adapter);
+        if (mSticyEnable) {
+            initStickyView(adapter);
+        }
+    }
+
+    /**
+     * add HeaderView Adapter
+     */
+    public <T> void addHeaderAdapter(IndexableHeaderAdapter<T> adapter) {
+        adapter.registerDataSetObserver(mHeaderFooterDataSetObserver);
+        adapter.registerIndexBarDataSetObserver(mIndexBarDataSetObserver);
+        mRealAdapter.addIndexableHeaderAdapter(adapter);
+    }
+
+    /**
+     * removeData HeaderView Adapter
+     */
+    public <T> void removeHeaderAdapter(IndexableHeaderAdapter<T> adapter) {
+        try {
+            adapter.unregisterDataSetObserver(mHeaderFooterDataSetObserver);
+            adapter.unregisterIndexBarDataSetObserver(mIndexBarDataSetObserver);
+            mRealAdapter.removeIndexableHeaderAdapter(adapter);
+        } catch (Exception ignored) {
+        }
+    }
+
+    /**
+     * add FooterView Adapter
+     */
+    public <T> void addFooterAdapter(IndexableFooterAdapter<T> adapter) {
+        adapter.registerDataSetObserver(mHeaderFooterDataSetObserver);
+        adapter.registerIndexBarDataSetObserver(mIndexBarDataSetObserver);
+        mRealAdapter.addIndexableFooterAdapter(adapter);
+    }
+
+    /**
+     * removeData FooterView Adapter
+     */
+    public <T> void removeFooterAdapter(IndexableFooterAdapter<T> adapter) {
+        try {
+            adapter.unregisterDataSetObserver(mHeaderFooterDataSetObserver);
+            adapter.unregisterIndexBarDataSetObserver(mIndexBarDataSetObserver);
+            mRealAdapter.removeIndexableFooterAdapter(adapter);
+        } catch (Exception ignored) {
+        }
+    }
+
+    /**
+     * set sort-mode
+     * Deprecated {@link #setCompareMode(int)}
+     */
+    @Deprecated
+    public void setFastCompare(boolean fastCompare) {
+        setCompareMode(fastCompare ? MODE_FAST : MODE_ALL_LETTERS);
+    }
+
+    @IntDef({MODE_FAST, MODE_ALL_LETTERS, MODE_NONE})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface CompareMode {
+    }
+
+    /**
+     * set sort-mode
+     */
+    public void setCompareMode(@CompareMode int mode) {
+        this.mCompareMode = mode;
+    }
+
+    /**
+     * set sort-mode
+     */
+    public <T extends IndexableEntity> void setComparator(Comparator<EntityWrapper<T>> comparator) {
+        this.mComparator = comparator;
+    }
+
+    /**
+     * set Sticky Enable
+     */
+    public void setStickyEnable(boolean enable) {
+        this.mSticyEnable = enable;
+    }
+
+    /**
+     * display all letter-index
+     */
+    public void showAllLetter(boolean show) {
+        mShowAllLetter = show;
+    }
+
+    /**
+     * display Material Design OverlayView
+     */
+    public void setOverlayStyle_MaterialDesign(int color) {
+        if (mMDOverlay == null) {
+            initMDOverlay(color);
+        } else {
+            ViewCompat.setBackgroundTintList(mMDOverlay, ColorStateList.valueOf(color));
+        }
+        mCenterOverlay = null;
+    }
+
+    /**
+     * display Center OverlayView
+     */
+    public void setOverlayStyle_Center() {
+        if (mCenterOverlay == null) {
+            initCenterOverlay();
+        }
+        mMDOverlay = null;
+    }
+
+    /**
+     * get OverlayView
+     */
+    public TextView getOverlayView() {
+        return mMDOverlay != null ? mMDOverlay : mCenterOverlay;
+    }
+
+    /**
+     * get RecyclerView
+     */
+    public RecyclerView getRecyclerView() {
+        return mRecy;
+    }
+
+    /**
+     * Set the enabled state of this IndexBar.
+     */
+    public void setIndexBarVisibility(boolean visible) {
+        mIndexBar.setVisibility(visible ? VISIBLE : GONE);
+    }
+
+    private void init(Context context, AttributeSet attrs) {
+        this.mContext = context;
+        this.mExecutorService = Executors.newSingleThreadExecutor();
+        PADDING_RIGHT_OVERLAY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, getResources().getDisplayMetrics());
+
+        if (attrs != null) {
+            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IndexableRecyclerView);
+            mBarTextColor = a.getColor(R.styleable.IndexableRecyclerView_indexBar_textColor, ContextCompat.getColor(context, R.color.default_indexBar_textColor));
+            mBarTextSize = a.getDimension(R.styleable.IndexableRecyclerView_indexBar_textSize, getResources().getDimension(R.dimen.default_indexBar_textSize));
+            mBarFocusTextColor = a.getColor(R.styleable.IndexableRecyclerView_indexBar_selectedTextColor, ContextCompat.getColor(context, R.color.default_indexBar_selectedTextColor));
+            mBarTextSpace = a.getDimension(R.styleable.IndexableRecyclerView_indexBar_textSpace, getResources().getDimension(R.dimen.default_indexBar_textSpace));
+            mBarBg = a.getDrawable(R.styleable.IndexableRecyclerView_indexBar_background);
+            mBarWidth = a.getDimension(R.styleable.IndexableRecyclerView_indexBar_layout_width, getResources().getDimension(R.dimen.default_indexBar_layout_width));
+            a.recycle();
+        }
+
+        if (mContext instanceof Activity) {
+            ((Activity) mContext).getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
+        }
+
+        mRecy = new RecyclerView(context);
+        mRecy.setVerticalScrollBarEnabled(false);
+        mRecy.setOverScrollMode(View.OVER_SCROLL_NEVER);
+        addView(mRecy, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+        mIndexBar = new IndexBar(context);
+        mIndexBar.init(mBarBg, mBarTextColor, mBarFocusTextColor, mBarTextSize, mBarTextSpace);
+        LayoutParams params = new LayoutParams((int) mBarWidth, LayoutParams.WRAP_CONTENT);
+        params.gravity = Gravity.END | Gravity.CENTER_VERTICAL;
+        addView(mIndexBar, params);
+
+        mRealAdapter = new RealAdapter();
+        mRecy.setHasFixedSize(true);
+        mRecy.setAdapter(mRealAdapter);
+
+        initListener();
+    }
+
+    /**
+     * {@link #setAdapter(IndexableAdapter)}
+     *
+     * @param layoutManager One of LinearLayoutManager and GridLayoutManager
+     */
+    public void setLayoutManager(RecyclerView.LayoutManager layoutManager) {
+        if (layoutManager == null)
+            throw new NullPointerException("LayoutManager == null");
+
+        mLayoutManager = layoutManager;
+        if (layoutManager instanceof GridLayoutManager) {
+            final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
+            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
+                @Override
+                public int getSpanSize(int position) {
+                    int spanSize = 0;
+                    if (mRealAdapter.getItemViewType(position) == EntityWrapper.TYPE_TITLE) {
+                        spanSize = gridLayoutManager.getSpanCount();
+                    } else if (mRealAdapter.getItemViewType(position) == EntityWrapper.TYPE_CONTENT) {
+                        spanSize = 1;
+                    }
+                    return spanSize;
+                }
+            });
+        }
+
+        mRecy.setLayoutManager(mLayoutManager);
+    }
+
+    private void initListener() {
+        mRecy.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                super.onScrolled(recyclerView, dx, dy);
+                processScrollListener();
+            }
+        });
+
+        mIndexBar.setOnTouchListener(new OnTouchListener() {
+
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                int touchPos = mIndexBar.getPositionForPointY(event.getY());
+                if (touchPos < 0) return true;
+
+                if (!(mLayoutManager instanceof LinearLayoutManager)) return true;
+                LinearLayoutManager linearLayoutManager = (LinearLayoutManager) mLayoutManager;
+
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                    case MotionEvent.ACTION_MOVE:
+                        showOverlayView(event.getY(), touchPos);
+
+                        if (touchPos != mIndexBar.getSelectionPosition()) {
+                            mIndexBar.setSelectionPosition(touchPos);
+
+                            if (touchPos == 0) {
+                                linearLayoutManager.scrollToPositionWithOffset(0, 0);
+                            } else {
+                                linearLayoutManager.scrollToPositionWithOffset(mIndexBar.getFirstRecyclerViewPositionBySelection(), 0);
+                            }
+                        }
+                        break;
+                    case MotionEvent.ACTION_UP:
+                    case MotionEvent.ACTION_CANCEL:
+                        if (mCenterOverlay != null) mCenterOverlay.setVisibility(GONE);
+                        if (mMDOverlay != null) mMDOverlay.setVisibility(GONE);
+                        break;
+                }
+                return true;
+            }
+        });
+    }
+
+    private void processScrollListener() {
+        if (!(mLayoutManager instanceof LinearLayoutManager)) return;
+
+        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) mLayoutManager;
+
+        int firstItemPosition;
+        firstItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
+        if (firstItemPosition == RecyclerView.NO_POSITION) return;
+
+        mIndexBar.setSelection(firstItemPosition);
+
+        if (!mSticyEnable) return;
+        ArrayList<EntityWrapper> list = mRealAdapter.getItems();
+        if (mStickyViewHolder != null && list.size() > firstItemPosition) {
+            EntityWrapper wrapper = list.get(firstItemPosition);
+            String wrapperTitle = wrapper.getIndexTitle();
+
+            if (EntityWrapper.TYPE_TITLE == wrapper.getItemType()) {
+                if (mLastInvisibleRecyclerViewItemView != null && mLastInvisibleRecyclerViewItemView.getVisibility() == INVISIBLE) {
+                    mLastInvisibleRecyclerViewItemView.setVisibility(VISIBLE);
+                    mLastInvisibleRecyclerViewItemView = null;
+                }
+
+                mLastInvisibleRecyclerViewItemView = linearLayoutManager.findViewByPosition(firstItemPosition);
+
+                if (mLastInvisibleRecyclerViewItemView != null) {
+                    mLastInvisibleRecyclerViewItemView.setVisibility(INVISIBLE);
+                }
+            }
+
+            // hide -> show
+            if (wrapperTitle == null && mStickyViewHolder.itemView.getVisibility() == VISIBLE) {
+                mStickyTitle = null;
+                mStickyViewHolder.itemView.setVisibility(INVISIBLE);
+            } else {
+                stickyNewViewHolder(wrapperTitle);
+            }
+
+            // GirdLayoutManager
+            if (mLayoutManager instanceof GridLayoutManager) {
+                GridLayoutManager gridLayoutManager = (GridLayoutManager) mLayoutManager;
+                if (firstItemPosition + gridLayoutManager.getSpanCount() < list.size()) {
+                    for (int i = firstItemPosition + 1; i <= firstItemPosition + gridLayoutManager.getSpanCount(); i++) {
+                        processScroll(linearLayoutManager, list, i, wrapperTitle);
+                    }
+                }
+            } else {   // LinearLayoutManager
+                if (firstItemPosition + 1 < list.size()) {
+                    processScroll(linearLayoutManager, list, firstItemPosition + 1, wrapperTitle);
+                }
+            }
+        }
+    }
+
+    private void processScroll(LinearLayoutManager layoutManager, ArrayList<EntityWrapper> list, int position, String title) {
+        EntityWrapper nextWrapper = list.get(position);
+        View nextTitleView = layoutManager.findViewByPosition(position);
+        if (nextTitleView == null) return;
+        if (nextWrapper.getItemType() == EntityWrapper.TYPE_TITLE) {
+            if (nextTitleView.getTop() <= mStickyViewHolder.itemView.getHeight() && title != null) {
+                mStickyViewHolder.itemView.setTranslationY(nextTitleView.getTop() - mStickyViewHolder.itemView.getHeight());
+            }
+            if (INVISIBLE == nextTitleView.getVisibility()) {
+                //特殊情况:手指向下滑动的时候,需要及时把成为第二个可见View的TitleView设置Visible,
+                // 这样才能配合StickyView制造两个TitleView切换的动画。
+                nextTitleView.setVisibility(VISIBLE);
+            }
+            return;
+        } else if (mStickyViewHolder.itemView.getTranslationY() != 0) {
+            mStickyViewHolder.itemView.setTranslationY(0);
+        }
+        return;
+    }
+
+    private void stickyNewViewHolder(String wrapperTitle) {
+        if ((wrapperTitle != null && !wrapperTitle.equals(mStickyTitle))) {
+
+            if (mStickyViewHolder.itemView.getVisibility() != VISIBLE) {
+                mStickyViewHolder.itemView.setVisibility(VISIBLE);
+            }
+
+            mStickyTitle = wrapperTitle;
+            mIndexableAdapter.onBindTitleViewHolder(mStickyViewHolder, wrapperTitle);
+        }
+    }
+
+    private <T extends IndexableEntity> void initStickyView(final IndexableAdapter<T> adapter) {
+        mStickyViewHolder = adapter.onCreateTitleViewHolder(mRecy);
+        mStickyViewHolder.itemView.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (adapter.getOnItemTitleClickListener() != null) {
+                    int position = mIndexBar.getFirstRecyclerViewPositionBySelection();
+                    ArrayList<EntityWrapper> datas = mRealAdapter.getItems();
+                    if (datas.size() > position && position >= 0) {
+                        adapter.getOnItemTitleClickListener().onItemClick(
+                                v, position, datas.get(position).getIndexTitle());
+                    }
+                }
+            }
+        });
+        mStickyViewHolder.itemView.setOnLongClickListener(new OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                if (adapter.getOnItemTitleLongClickListener() != null) {
+                    int position = mIndexBar.getFirstRecyclerViewPositionBySelection();
+                    ArrayList<EntityWrapper> datas = mRealAdapter.getItems();
+                    if (datas.size() > position && position >= 0) {
+                        return adapter.getOnItemTitleLongClickListener().onItemLongClick(
+                                v, position, datas.get(position).getIndexTitle());
+                    }
+                }
+                return false;
+            }
+        });
+        for (int i = 0; i < getChildCount(); i++) {
+            if (getChildAt(i) == mRecy) {
+                mStickyViewHolder.itemView.setVisibility(INVISIBLE);
+                addView(mStickyViewHolder.itemView, i + 1);
+                return;
+            }
+        }
+    }
+
+
+    private void showOverlayView(float y, final int touchPos) {
+        if (mIndexBar.getIndexList().size() <= touchPos) return;
+
+        if (mMDOverlay != null) {
+            if (mMDOverlay.getVisibility() != VISIBLE) {
+                mMDOverlay.setVisibility(VISIBLE);
+            }
+
+            if (y < PADDING_RIGHT_OVERLAY - mIndexBar.getTop() && y >= 0) {
+                y = PADDING_RIGHT_OVERLAY - mIndexBar.getTop();
+            } else if (y < 0) {
+                if (mIndexBar.getTop() > PADDING_RIGHT_OVERLAY) {
+                    y = 0;
+                } else {
+                    y = PADDING_RIGHT_OVERLAY - mIndexBar.getTop();
+                }
+            } else if (y > mIndexBar.getHeight()) {
+                y = mIndexBar.getHeight();
+            }
+            mMDOverlay.setY(mIndexBar.getTop() + y - PADDING_RIGHT_OVERLAY);
+
+            String index = mIndexBar.getIndexList().get(touchPos);
+            if (!mMDOverlay.getText().equals(index)) {
+                if (index.length() > 1) {
+                    mMDOverlay.setTextSize(30);
+                }
+                mMDOverlay.setText(index);
+            }
+        }
+        if (mCenterOverlay != null) {
+            if (mCenterOverlay.getVisibility() != VISIBLE) {
+                mCenterOverlay.setVisibility(VISIBLE);
+            }
+            String index = mIndexBar.getIndexList().get(touchPos);
+            if (!mCenterOverlay.getText().equals(index)) {
+                if (index.length() > 1) {
+                    mCenterOverlay.setTextSize(32);
+                }
+                mCenterOverlay.setText(index);
+            }
+        }
+    }
+
+    private void initCenterOverlay() {
+        mCenterOverlay = new TextView(mContext);
+        mCenterOverlay.setBackgroundResource(R.drawable.indexable_bg_center_overlay);
+        mCenterOverlay.setTextColor(Color.WHITE);
+        mCenterOverlay.setTextSize(40);
+        mCenterOverlay.setGravity(Gravity.CENTER);
+        int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics());
+        LayoutParams params = new LayoutParams(size, size);
+        params.gravity = Gravity.CENTER;
+        mCenterOverlay.setLayoutParams(params);
+        mCenterOverlay.setVisibility(INVISIBLE);
+
+        addView(mCenterOverlay);
+    }
+
+    private void initMDOverlay(int color) {
+        mMDOverlay = new AppCompatTextView(mContext);
+        mMDOverlay.setBackgroundResource(R.drawable.indexable_bg_md_overlay);
+        ((AppCompatTextView) mMDOverlay).setSupportBackgroundTintList(ColorStateList.valueOf(color));
+        mMDOverlay.setSingleLine();
+        mMDOverlay.setTextColor(Color.WHITE);
+        mMDOverlay.setTextSize(38);
+        mMDOverlay.setGravity(Gravity.CENTER);
+        int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics());
+        LayoutParams params = new LayoutParams(size, size);
+        params.rightMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 33, getResources().getDisplayMetrics());
+        params.gravity = Gravity.END;
+        mMDOverlay.setLayoutParams(params);
+        mMDOverlay.setVisibility(INVISIBLE);
+
+        addView(mMDOverlay);
+    }
+
+    void onDataChanged() {
+        if (mFuture != null) {
+            mFuture.cancel(true);
+        }
+        mFuture = mExecutorService.submit(new Runnable() {
+            @Override
+            public void run() {
+                final ArrayList<EntityWrapper> datas = transform(mIndexableAdapter.getItems());
+                if (datas == null) return;
+
+                getSafeHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mRealAdapter.setDatas(datas);
+                        mIndexBar.setDatas(mShowAllLetter, mRealAdapter.getItems());
+
+                        if (mIndexableAdapter.getIndexCallback() != null) {
+                            mIndexableAdapter.getIndexCallback().onFinished(datas);
+                        }
+
+                        processScrollListener();
+                    }
+                });
+            }
+        });
+    }
+
+    /**
+     * List<T> -> List<Indexable<T>
+     */
+    private <T extends IndexableEntity> ArrayList<EntityWrapper<T>> transform(final List<T> datas) {
+        try {
+            TreeMap<String, List<EntityWrapper<T>>> map = new TreeMap<>(new Comparator<String>() {
+                @Override
+                public int compare(String lhs, String rhs) {
+                    if (lhs.equals(INDEX_SIGN)) {
+                        return rhs.equals(INDEX_SIGN) ? 0 : 1;
+                    } else if (rhs.equals(INDEX_SIGN)) {
+                        return -1;
+                    }
+                    return lhs.compareTo(rhs);
+                }
+            });
+
+            for (int i = 0; i < datas.size(); i++) {
+                EntityWrapper<T> entity = new EntityWrapper<>();
+                T item = datas.get(i);
+                String indexName = item.getFieldIndexBy();
+                String pinyin = PinyinUtil.getPingYin(indexName);
+                entity.setPinyin(pinyin);
+
+                // init EntityWrapper
+                if (PinyinUtil.matchingLetter(pinyin)) {
+                    entity.setIndex(pinyin.substring(0, 1).toUpperCase());
+                    entity.setIndexByField(item.getFieldIndexBy());
+                } else if (PinyinUtil.matchingPolyphone(pinyin)) {
+                    entity.setIndex(PinyinUtil.gePolyphoneInitial(pinyin).toUpperCase());
+                    entity.setPinyin(PinyinUtil.getPolyphoneRealPinyin(pinyin));
+                    String hanzi = PinyinUtil.getPolyphoneRealHanzi(indexName);
+                    entity.setIndexByField(hanzi);
+                    // 把多音字的真实indexField重新赋值
+                    item.setFieldIndexBy(hanzi);
+                } else {
+                    entity.setIndex(INDEX_SIGN);
+                    entity.setIndexByField(item.getFieldIndexBy());
+                }
+                entity.setIndexTitle(entity.getIndex());
+                entity.setData(item);
+                entity.setOriginalPosition(i);
+                item.setFieldPinyinIndexBy(entity.getPinyin());
+
+                String inital = entity.getIndex();
+
+                List<EntityWrapper<T>> list;
+                if (!map.containsKey(inital)) {
+                    list = new ArrayList<>();
+                    list.add(new EntityWrapper<T>(entity.getIndex(), EntityWrapper.TYPE_TITLE));
+                    map.put(inital, list);
+                } else {
+                    list = map.get(inital);
+                }
+
+                list.add(entity);
+            }
+
+            ArrayList<EntityWrapper<T>> list = new ArrayList<>();
+            for (List<EntityWrapper<T>> indexableEntities : map.values()) {
+                if (mComparator != null) {
+                    Collections.sort(indexableEntities, mComparator);
+                } else {
+                    Comparator comparator;
+                    if (mCompareMode == MODE_FAST) {
+                        comparator = new InitialComparator<T>();
+                        Collections.sort(indexableEntities, comparator);
+                    } else if (mCompareMode == MODE_ALL_LETTERS) {
+                        comparator = new PinyinComparator<T>();
+                        Collections.sort(indexableEntities, comparator);
+                    }
+                }
+
+                list.addAll(indexableEntities);
+            }
+            return list;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private Handler getSafeHandler() {
+        if (mHandler == null) {
+            mHandler = new Handler(Looper.getMainLooper());
+        }
+        return mHandler;
+    }
+}

+ 13 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/InitialComparator.java

@@ -0,0 +1,13 @@
+package me.yokeyword.indexablerv;
+
+import java.util.Comparator;
+
+/**
+ * Created by YoKey on 16/10/14.
+ */
+class InitialComparator<T extends IndexableEntity> implements Comparator<EntityWrapper<T>> {
+    @Override
+    public int compare(EntityWrapper<T> lhs, EntityWrapper<T> rhs) {
+        return lhs.getIndex().compareTo(rhs.getIndex());
+    }
+}

+ 51 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/PinyinComparator.java

@@ -0,0 +1,51 @@
+package me.yokeyword.indexablerv;
+
+
+import androidx.annotation.NonNull;
+
+import java.util.Comparator;
+
+/**
+ * Created by YoKey on 16/10/7.
+ */
+class PinyinComparator<T extends IndexableEntity> implements Comparator<EntityWrapper<T>> {
+
+    @Override
+    public int compare(EntityWrapper<T> lhs, EntityWrapper<T> rhs) {
+        String lhsIndexName = lhs.getIndexByField();
+        String rhsIndexName = rhs.getIndexByField();
+
+        if (lhsIndexName == null) {
+            lhsIndexName = "";
+        }
+        if (rhsIndexName == null) {
+            rhsIndexName = "";
+        }
+        return compareIndexName(lhsIndexName.trim(), rhsIndexName.trim());
+    }
+
+    private int compareIndexName(String lhs, String rhs) {
+        int index = 0;
+
+        String lhsWord = getWord(lhs, index);
+        String rhsWord = getWord(rhs, index);
+        while (lhsWord.equals(rhsWord) && !lhsWord.equals("")) {
+            index++;
+            lhsWord = getWord(lhs, index);
+            rhsWord = getWord(rhs, index);
+        }
+        return lhsWord.compareTo(rhsWord);
+    }
+
+    @NonNull
+    private String getWord(String indexName, int index) {
+        if (indexName.length() < (index + 1)) return "";
+        String firstWord;
+        if (PinyinUtil.matchingPolyphone(indexName)) {
+            firstWord = PinyinUtil.getPingYin(PinyinUtil.getPolyphoneRealHanzi(indexName).substring(index, index + 1));
+        } else {
+            firstWord = PinyinUtil.getPingYin(indexName.substring(index, index + 1));
+        }
+        return firstWord;
+    }
+}

+ 48 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/PinyinUtil.java

@@ -0,0 +1,48 @@
+package me.yokeyword.indexablerv;
+
+import com.github.promeg.pinyinhelper.Pinyin;
+
+import java.util.regex.Pattern;
+
+/**
+ * Created by YoKey on 16/3/20.
+ */
+public class PinyinUtil {
+    private static final String PATTERN_POLYPHONE = "^#[a-zA-Z]+#.+";
+    private static final String PATTERN_LETTER = "^[a-zA-Z].*+";
+
+    /**
+     * Chinese character -> Pinyin
+     */
+    public static String getPingYin(String inputString) {
+        if (inputString == null) return "";
+        return Pinyin.toPinyin(inputString, "").toLowerCase();
+    }
+
+    /**
+     * Are start with a letter
+     *
+     * @return if return false, index should be #
+     */
+    static boolean matchingLetter(String inputString) {
+        return Pattern.matches(PATTERN_LETTER, inputString);
+    }
+
+    static boolean matchingPolyphone(String inputString) {
+        return Pattern.matches(PATTERN_POLYPHONE, inputString);
+    }
+
+    static String gePolyphoneInitial(String inputString) {
+        return inputString.substring(1, 2);
+    }
+
+    static String getPolyphoneRealPinyin(String inputString) {
+        String[] splits = inputString.split("#");
+        return splits[1];
+    }
+
+    static String getPolyphoneRealHanzi(String inputString) {
+        String[] splits = inputString.split("#");
+        return splits[2];
+    }
+}

+ 252 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/RealAdapter.java

@@ -0,0 +1,252 @@
+package me.yokeyword.indexablerv;
+
+import androidx.recyclerview.widget.RecyclerView;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Created by YoKey on 16/10/6.
+ */
+@SuppressWarnings("unchecked")
+class RealAdapter<T extends IndexableEntity> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+    private ArrayList<EntityWrapper<T>> mDatasList = new ArrayList<>();
+    private ArrayList<EntityWrapper<T>> mDatas;
+    private ArrayList<EntityWrapper<T>> mHeaderDatasList = new ArrayList<>();
+    private ArrayList<EntityWrapper<T>> mFooterDatasList = new ArrayList<>();
+    private IndexableAdapter<T> mAdapter;
+
+    private SparseArray<IndexableHeaderAdapter> mHeaderAdapterMap = new SparseArray<>();
+    private SparseArray<IndexableFooterAdapter> mFooterAdapterMap = new SparseArray<>();
+
+    private IndexableAdapter.OnItemTitleClickListener mTitleClickListener;
+    private IndexableAdapter.OnItemContentClickListener<T> mContentClickListener;
+    private IndexableAdapter.OnItemTitleLongClickListener mTitleLongClickListener;
+    private IndexableAdapter.OnItemContentLongClickListener<T> mContentLongClickListener;
+
+    void setIndexableAdapter(IndexableAdapter<T> adapter) {
+        this.mAdapter = adapter;
+    }
+
+    void addIndexableHeaderAdapter(IndexableHeaderAdapter adapter) {
+        mHeaderDatasList.addAll(0, adapter.getDatas());
+        mDatasList.addAll(0, adapter.getDatas());
+        mHeaderAdapterMap.put(adapter.getItemViewType(), adapter);
+        notifyDataSetChanged();
+    }
+
+    void removeIndexableHeaderAdapter(IndexableHeaderAdapter adapter) {
+        mHeaderDatasList.removeAll(adapter.getDatas());
+        if (mDatasList.size() > 0) {
+            mDatasList.removeAll(adapter.getDatas());
+        }
+        mHeaderAdapterMap.remove(adapter.getItemViewType());
+        notifyDataSetChanged();
+    }
+
+    void addIndexableFooterAdapter(IndexableFooterAdapter adapter) {
+        mFooterDatasList.addAll(adapter.getDatas());
+        mDatasList.addAll(adapter.getDatas());
+        mFooterAdapterMap.put(adapter.getItemViewType(), adapter);
+        notifyDataSetChanged();
+    }
+
+    void removeIndexableFooterAdapter(IndexableFooterAdapter adapter) {
+        mFooterDatasList.removeAll(adapter.getDatas());
+        if (mDatasList.size() > 0) {
+            mDatasList.removeAll(adapter.getDatas());
+        }
+        mFooterAdapterMap.remove(adapter.getItemViewType());
+        notifyDataSetChanged();
+    }
+
+    void setDatas(ArrayList<EntityWrapper<T>> datas) {
+        if (mDatas != null && mDatasList.size() > mHeaderDatasList.size() + mFooterDatasList.size()) {
+            mDatasList.removeAll(mDatas);
+        }
+
+        this.mDatas = datas;
+
+        mDatasList.addAll(mHeaderDatasList.size(), datas);
+        notifyDataSetChanged();
+    }
+
+    ArrayList<EntityWrapper<T>> getItems() {
+        return mDatasList;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mDatasList.get(position).getItemType();
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, final int viewType) {
+        final RecyclerView.ViewHolder holder;
+
+        if (viewType == EntityWrapper.TYPE_TITLE) {
+            holder = mAdapter.onCreateTitleViewHolder(parent);
+        } else if (viewType == EntityWrapper.TYPE_CONTENT) {
+            holder = mAdapter.onCreateContentViewHolder(parent);
+        } else {
+            AbstractHeaderFooterAdapter adapter;
+            if (mHeaderAdapterMap.indexOfKey(viewType) >= 0) {
+                adapter = mHeaderAdapterMap.get(viewType);
+            } else {
+                adapter = mFooterAdapterMap.get(viewType);
+            }
+            holder = adapter.onCreateContentViewHolder(parent);
+        }
+
+        holder.itemView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                int position = holder.getAdapterPosition();
+                if (position == RecyclerView.NO_POSITION) return;
+                EntityWrapper<T> wrapper = mDatasList.get(position);
+                if (viewType == EntityWrapper.TYPE_TITLE) {
+                    if (mTitleClickListener != null) {
+                        mTitleClickListener.onItemClick(v, position, wrapper.getIndexTitle());
+                    }
+                } else if (viewType == EntityWrapper.TYPE_CONTENT) {
+                    if (mContentClickListener != null) {
+                        mContentClickListener.onItemClick(v, wrapper.getOriginalPosition(), position, wrapper.getData());
+                    }
+                } else {
+                    AbstractHeaderFooterAdapter adapter;
+                    if (mHeaderAdapterMap.indexOfKey(viewType) >= 0) {
+                        adapter = mHeaderAdapterMap.get(viewType);
+                    } else {
+                        adapter = mFooterAdapterMap.get(viewType);
+                    }
+
+                    if (adapter != null) {
+                        AbstractHeaderFooterAdapter.OnItemClickListener listener = adapter.getOnItemClickListener();
+                        if (listener != null) {
+                            listener.onItemClick(v, position, wrapper.getData());
+                        }
+                    }
+                }
+            }
+        });
+
+        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                int position = holder.getAdapterPosition();
+                EntityWrapper<T> wrapper = mDatasList.get(position);
+                if (viewType == EntityWrapper.TYPE_TITLE) {
+                    if (mTitleLongClickListener != null) {
+                        return mTitleLongClickListener.onItemLongClick(v, position, wrapper.getIndexTitle());
+                    } else {
+                        return true;
+                    }
+                } else if (viewType == EntityWrapper.TYPE_CONTENT) {
+                    if (mContentLongClickListener != null) {
+                        return mContentLongClickListener.onItemLongClick(v, wrapper.getOriginalPosition(), position, wrapper.getData());
+                    } else {
+                        return true;
+                    }
+                } else {
+                    AbstractHeaderFooterAdapter adapter;
+                    if (mHeaderAdapterMap.indexOfKey(viewType) >= 0) {
+                        adapter = mHeaderAdapterMap.get(viewType);
+                    } else {
+                        adapter = mFooterAdapterMap.get(viewType);
+                    }
+
+                    if (adapter != null) {
+                        AbstractHeaderFooterAdapter.OnItemLongClickListener listener = adapter.getOnItemLongClickListener();
+                        if (listener != null) {
+                            return listener.onItemLongClick(v, position, wrapper.getData());
+                        }
+                    }
+                }
+                return false;
+            }
+        });
+        return holder;
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+        EntityWrapper<T> item = mDatasList.get(position);
+
+        int viewType = getItemViewType(position);
+        if (viewType == EntityWrapper.TYPE_TITLE) {
+            if (View.INVISIBLE == holder.itemView.getVisibility()) {
+                holder.itemView.setVisibility(View.VISIBLE);
+            }
+            mAdapter.onBindTitleViewHolder(holder, item.getIndexTitle());
+        } else if (viewType == EntityWrapper.TYPE_CONTENT) {
+            mAdapter.onBindContentViewHolder(holder, item.getData());
+        } else {
+            AbstractHeaderFooterAdapter adapter;
+            if (mHeaderAdapterMap.indexOfKey(viewType) >= 0) {
+                adapter = mHeaderAdapterMap.get(viewType);
+            } else {
+                adapter = mFooterAdapterMap.get(viewType);
+            }
+            adapter.onBindContentViewHolder(holder, item.getData());
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return mDatasList.size();
+    }
+
+    void setOnItemTitleClickListener(IndexableAdapter.OnItemTitleClickListener listener) {
+        this.mTitleClickListener = listener;
+    }
+
+    void setOnItemContentClickListener(IndexableAdapter.OnItemContentClickListener<T> listener) {
+        this.mContentClickListener = listener;
+    }
+
+    void setOnItemTitleLongClickListener(IndexableAdapter.OnItemTitleLongClickListener listener) {
+        this.mTitleLongClickListener = listener;
+    }
+
+    void setOnItemContentLongClickListener(IndexableAdapter.OnItemContentLongClickListener<T> listener) {
+        this.mContentLongClickListener = listener;
+    }
+
+    void addHeaderFooterData(boolean header, EntityWrapper preData, EntityWrapper data) {
+        processAddHeaderFooterData(header ? mHeaderDatasList : mFooterDatasList, preData, data);
+    }
+
+    private void processAddHeaderFooterData(ArrayList<EntityWrapper<T>> list, EntityWrapper preData, EntityWrapper data) {
+        for (int i = 0; i < list.size(); i++) {
+            EntityWrapper wrapper = list.get(i);
+            if (wrapper == preData) {
+                int index = i + 1;
+                list.add(index, data);
+                if (list == mFooterDatasList) {
+                    index += mDatasList.size() - mFooterDatasList.size() + 1;
+                }
+                mDatasList.add(index, data);
+                notifyItemInserted(i + 1);
+                return;
+            }
+        }
+    }
+
+    void removeHeaderFooterData(boolean header, EntityWrapper data) {
+        processremoveHeaderFooterData(header ? mHeaderDatasList : mFooterDatasList, data);
+    }
+
+    private void processremoveHeaderFooterData(ArrayList<EntityWrapper<T>> list, EntityWrapper data) {
+        for (int i = 0; i < list.size(); i++) {
+            EntityWrapper wrapper = list.get(i);
+            if (wrapper == data) {
+                list.remove(data);
+                mDatasList.remove(data);
+                notifyItemRemoved(i);
+                return;
+            }
+        }
+    }
+}

+ 34 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/SimpleFooterAdapter.java

@@ -0,0 +1,34 @@
+package me.yokeyword.indexablerv;
+
+import androidx.recyclerview.widget.RecyclerView;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+/**
+ * 该HeaderAdapter 接收一个IndexableAdapter, 使其布局以及点击事件和IndexableAdapter一致
+ * Created by YoKey on 16/10/14.
+ */
+public class SimpleFooterAdapter<T extends IndexableEntity> extends IndexableFooterAdapter<T> {
+    private IndexableAdapter<T> mAdapter;
+
+    public SimpleFooterAdapter(IndexableAdapter<T> adapter, String index, String indexTitle, List<T> datas) {
+        super(index, indexTitle, datas);
+        this.mAdapter = adapter;
+    }
+
+    @Override
+    public int getItemViewType() {
+        return EntityWrapper.TYPE_CONTENT;
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateContentViewHolder(ViewGroup parent) {
+        return mAdapter.onCreateContentViewHolder(parent);
+    }
+
+    @Override
+    public void onBindContentViewHolder(RecyclerView.ViewHolder holder, T entity) {
+        mAdapter.onBindContentViewHolder(holder, entity);
+    }
+}

+ 36 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/SimpleHeaderAdapter.java

@@ -0,0 +1,36 @@
+package me.yokeyword.indexablerv;
+
+
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.List;
+
+/**
+ * 该HeaderAdapter 接收一个IndexableAdapter, 使其布局以及点击事件和IndexableAdapter一致
+ * Created by YoKey on 16/10/8.
+ */
+public class SimpleHeaderAdapter<T extends IndexableEntity> extends IndexableHeaderAdapter<T> {
+    private IndexableAdapter<T> mAdapter;
+
+    public SimpleHeaderAdapter(IndexableAdapter<T> adapter, String index, String indexTitle, List<T> datas) {
+        super(index, indexTitle, datas);
+        this.mAdapter = adapter;
+    }
+
+    @Override
+    public int getItemViewType() {
+        return EntityWrapper.TYPE_CONTENT;
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateContentViewHolder(ViewGroup parent) {
+        return mAdapter.onCreateContentViewHolder(parent);
+    }
+
+    @Override
+    public void onBindContentViewHolder(RecyclerView.ViewHolder holder, T entity) {
+        mAdapter.onBindContentViewHolder(holder, entity);
+    }
+}

+ 70 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/DataObservable.java

@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.yokeyword.indexablerv.database;
+
+import android.database.Observable;
+
+/**
+ * A specialization of {@link Observable} for {@link DataObserver}
+ * that provides methods for sending notifications to a list of
+ * {@link DataObserver} objects.
+ */
+public class DataObservable extends Observable<DataObserver> {
+
+    /**
+     * Invokes {@link DataObserver#onInited()}  on each observer.
+     * Called when the data set is no longer valid and cannot be queried again,
+     * such as when the data set has been closed.
+     */
+    public void notifyInited() {
+        synchronized (mObservers) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onInited();
+            }
+        }
+    }
+
+    /**
+     * Invokes {@link DataObserver#onChanged} on each observer.
+     * Called when the contents of the data set have changed.  The recipient
+     * will obtain the new contents the next time it queries the data set.
+     */
+    public void notifyChanged() {
+        synchronized (mObservers) {
+            // since onChanged() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onChanged();
+            }
+        }
+    }
+
+    /**
+     * Invokes {@link DataObserver#onSetListener(int)} on each observer.
+     * Called when the data set is no longer valid and cannot be queried again,
+     * such as when the data set has been closed.
+     */
+    public void notifySetListener(int type) {
+        synchronized (mObservers) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onSetListener(type);
+            }
+        }
+    }
+}

+ 30 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/DataObserver.java

@@ -0,0 +1,30 @@
+package me.yokeyword.indexablerv.database;
+
+/**
+ * Created by YoKey on 16/10/13.
+ */
+public class DataObserver {
+    /**
+     * This method is called when the entire data set has changed,
+     * init datas
+     */
+    public void onInited() {
+        // Do nothing
+    }
+
+    /**
+     * This method is called when the entire data set has changed,
+     * refresh UI
+     */
+    public void onChanged() {
+        // Do nothing
+    }
+
+    /**
+     * This method is called when the entire data becomes invalid,
+     * setListener
+     */
+    public void onSetListener(int type) {
+        // Do nothing
+    }
+}

+ 92 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/HeaderFooterDataObservable.java

@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.yokeyword.indexablerv.database;
+
+import android.database.Observable;
+
+/**
+ * A specialization of {@link Observable} for {@link DataObserver}
+ * that provides methods for sending notifications to a list of
+ * {@link DataObserver} objects.
+ */
+public class HeaderFooterDataObservable extends Observable<HeaderFooterDataObserver> {
+
+    /**
+     * Invokes {@link DataObserver#onChanged} on each observer.
+     * Called when the contents of the data set have changed.  The recipient
+     * will obtain the new contents the next time it queries the data set.
+     */
+    public void notifyChanged() {
+        synchronized (mObservers) {
+            // since onChanged() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onChanged();
+            }
+        }
+    }
+
+    public void notifyAdd(boolean header, Object preData, Object data) {
+        synchronized (mObservers) {
+            // since onChanged() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onAdd(header, preData, data);
+            }
+        }
+    }
+
+    public void notifyRemove(boolean header, Object object) {
+        synchronized (mObservers) {
+            // since onChanged() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onRemove(header, object);
+            }
+        }
+    }
+//
+//    public void notifyAddAll(Object preData, Object data) {
+//        synchronized (mObservers) {
+//            // since onChanged() is implemented by the app, it could do anything, including
+//            // removing itself from {@link mObservers} - and that could cause problems if
+//            // an iterator is used on the ArrayList {@link mObservers}.
+//            // to avoid such problems, just march thru the list in the reverse order.
+//            for (int i = mObservers.size() - 1; i >= 0; i--) {
+//                mObservers.get(i).onAddAll(itemType, position, datas);
+//            }
+//        }
+//    }
+//
+//    public void notifyRemoveAll(Collection datas) {
+//        synchronized (mObservers) {
+//            // since onChanged() is implemented by the app, it could do anything, including
+//            // removing itself from {@link mObservers} - and that could cause problems if
+//            // an iterator is used on the ArrayList {@link mObservers}.
+//            // to avoid such problems, just march thru the list in the reverse order.
+//            for (int i = mObservers.size() - 1; i >= 0; i--) {
+//                mObservers.get(i).onRemoveAll(itemType, datas);
+//            }
+//        }
+//    }
+}

+ 30 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/HeaderFooterDataObserver.java

@@ -0,0 +1,30 @@
+package me.yokeyword.indexablerv.database;
+
+/**
+ * Created by YoKey on 16/10/13.
+ */
+public class HeaderFooterDataObserver<T> {
+    /**
+     * This method is called when the entire data set has changed,
+     * refresh UI
+     */
+    public void onChanged() {
+        // Do nothing
+    }
+
+    public void onAdd(boolean header, T preData, T data) {
+        // Do nothing
+    }
+
+    public void onRemove(boolean header, T object) {
+        // Do nothing
+    }
+//
+//    public void onAddAll(int itemType, int position, Collection<T> datas) {
+//        // Do nothing
+//    }
+//
+//    public void onRemoveAll(int itemType, Collection<T> datas) {
+//        // Do nothing
+//    }
+}

+ 18 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/IndexBarDataObservable.java

@@ -0,0 +1,18 @@
+package me.yokeyword.indexablerv.database;
+
+import android.database.Observable;
+
+/**
+ * Created by Sun on 16/10/13.
+ */
+public class IndexBarDataObservable extends Observable<IndexBarDataObserver> {
+
+    public void notifyChanged() {
+        synchronized (mObservers) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onChanged();
+            }
+        }
+    }
+
+}

+ 16 - 0
lib-indexablerecyclerview/src/main/java/me/yokeyword/indexablerv/database/IndexBarDataObserver.java

@@ -0,0 +1,16 @@
+package me.yokeyword.indexablerv.database;
+
+/**
+ * Created by Sun on 16/10/13.
+ */
+public class IndexBarDataObserver {
+
+    /**
+     * This method is called when the entire data set has changed,
+     * refresh UI
+     */
+    public void onChanged() {
+        // Do nothing
+    }
+
+}

BIN
lib-indexablerecyclerview/src/main/res/drawable-hdpi/indexable_bg_md_overlay.png


BIN
lib-indexablerecyclerview/src/main/res/drawable-xhdpi/indexable_bg_md_overlay.png


BIN
lib-indexablerecyclerview/src/main/res/drawable-xxhdpi/indexable_bg_md_overlay.png


+ 5 - 0
lib-indexablerecyclerview/src/main/res/drawable/indexable_bg_center_overlay.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#70000000"/>
+    <corners android:radius="4dp"/>
+</shape>

+ 13 - 0
lib-indexablerecyclerview/src/main/res/values/indexable_attrs.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <declare-styleable name="IndexableRecyclerView">
+        <attr name="indexBar_selectedTextColor" format="color" />
+        <attr name="indexBar_textColor" format="color" />
+        <attr name="indexBar_textSize" format="dimension" />
+        <attr name="indexBar_textSpace" format="dimension" />
+        <attr name="indexBar_background" format="reference|color" />
+        <attr name="indexBar_layout_width" format="dimension">
+            <!--<enum name="wrap_content" value="-2" /> 暂不支持 -->
+        </attr>
+    </declare-styleable>
+</resources>

+ 9 - 0
lib-indexablerecyclerview/src/main/res/values/indexable_default.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="default_indexBar_textColor">#8c8c8c</color>
+    <dimen name="default_indexBar_textSize">14sp</dimen>
+    <color name="default_indexBar_selectedTextColor">#f33737</color>
+    <dimen name="default_indexBar_textSpace">4dp</dimen>
+    <drawable name="dafault_indexBar_background">@android:color/transparent</drawable>
+    <dimen name="default_indexBar_layout_width">24dp</dimen>
+</resources>

+ 31 - 0
lib-indexablerecyclerview/src/main/res/values/indexable_strings.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string-array name="indexable_letter">
+        <item>A</item>
+        <item>B</item>
+        <item>C</item>
+        <item>D</item>
+        <item>E</item>
+        <item>F</item>
+        <item>G</item>
+        <item>H</item>
+        <item>I</item>
+        <item>J</item>
+        <item>K</item>
+        <item>L</item>
+        <item>M</item>
+        <item>N</item>
+        <item>O</item>
+        <item>P</item>
+        <item>Q</item>
+        <item>R</item>
+        <item>S</item>
+        <item>T</item>
+        <item>U</item>
+        <item>V</item>
+        <item>W</item>
+        <item>X</item>
+        <item>Y</item>
+        <item>Z</item>
+    </string-array>
+</resources>

+ 2 - 1
settings.gradle.kts

@@ -20,9 +20,10 @@ dependencyResolutionManagement {
     }
 }
 
-rootProject.name = "2024AndroidTemplatePrivate"
+rootProject.name = "YYBusiness-ER-VN"
 include(":app")
 include(":cs-baselib")
 include(":cs-service")
+include(":lib-indexablerecyclerview")