抽空介紹一下如何使用RecyclerView來實現(xiàn)分組列表以及時間軸的顯示,先看下效果圖:
作為Android的小伙伴,在需求方面上,難免遇到實現(xiàn)類似的功能實現(xiàn),實現(xiàn)起來有一定的難度,RecyclerView進行分組,和時間軸的顯示。
重點講下,RecyclerView如何進行分組,其實就是對數(shù)據(jù)源集合進行分組:
BuildListDataUtil.kt
object BuildListDataUtil {
/**
* 集合進行分組
*/
fun buildListData(sourceList:MutableList<AppInfo>?): MutableList<AppInfo>{
val tempData = ArrayList<AppInfo>()
val appInfo = createEmptyObject(sourceList!![0])
tempData.add(appInfo)
tempData.add(sourceList[0])
var preDate = DateUtils.getTimeStampConvertToDate(sourceList[0]
.orderInfoApp?.publicFirstTime!!, DateUtils.PARAMETER_ALL_DATE_TYPE)
for (index in 1 until sourceList.size) {
val curDate = DateUtils.getTimeStampConvertToDate(sourceList[index]
.orderInfoApp?.publicFirstTime!!, DateUtils.PARAMETER_ALL_DATE_TYPE)
//日期一致的話,就添加至集合
if (TextUtils.equals(preDate, curDate)) {
tempData.add(sourceList[index])
} else {
// 日期不一致,則創(chuàng)建新的對象并添加到集合中
val curAppInfo = createEmptyObject(sourceList[index])
tempData.add(curAppInfo)
tempData.add(sourceList[index])
preDate = curDate
}
}
return tempData
}
/**
* 創(chuàng)建空對象
*/
private fun createEmptyObject(appInfo: AppInfo): AppInfo {
var tempInfo = AppInfo()
var orderInfoApp = OrderInfoApp()
orderInfoApp.publicFirstTime = appInfo.orderInfoApp?.publicFirstTime!!
tempInfo.orderInfoApp = orderInfoApp
return tempInfo
}
}
我封裝了BuildListDataUtil工具類,主要是對數(shù)據(jù)源集合邏輯進行分組處理,首先根據(jù)集合的索引值拿到第一個對象,進行創(chuàng)建空的對象,同時根據(jù)時間戳進行賦值,并返回空的對象作為用來展示標題布局的日期標題,并添加到臨時的集合中,根據(jù)當前的對象的時間戳返回當前的preDate變量,接下來sourceList進行遍歷,遍歷下一個對象的時間戳返回當前的curDate 變量,和上一個preDate變量進行對比,如果日期一致,就添加到同一集合里面,否則就創(chuàng)建空的對象并添加到集合里面,并且把curDate值賦值給preDate,以此類推。
我打了斷點,如下:
接下來就是把集合分好組的tempData變量返回給外部調用。
重點看下RecycleView的Adapter做了什么。
BuildGroupDateAdapter.kt
class BuildGroupDateAdapter constructor(datas:MutableList<AppInfo>?): Adapter<ViewHolder>() {
//標題
val ITEM_TITLE_TYPE = 1
//內容
val ITEM_CONTENT_TYPE = 2
private var datas:MutableList<AppInfo>? = null
private lateinit var itemTitleBing :ItemTitleLayoutBinding
private lateinit var itemContentBinding: ItemContentLayoutBinding
init {
this.datas = datas
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
var view :View
return when(viewType) {
ITEM_TITLE_TYPE -> {
itemTitleBing = ItemTitleLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
view = itemTitleBing.root
TitleViewHolder(view)
}
else -> {
itemContentBinding = ItemContentLayoutBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
view = itemContentBinding.root
ContentViewHolder(view)
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when(holder) {
is TitleViewHolder -> {
val titleDate = getTimeStampConvertToDate(
datas
?.get(position)!!.orderInfoApp!!.publicFirstTime,
DateUtils.PARAMETER_ALL_DATE_TYPE
)
itemTitleBing.tvTitle.text = titleDate
dealWithTitleTimeLine(position)
}
is ContentViewHolder -> {
itemContentBinding.tvContent.text = datas!![position].name
dealWithContentTimeLine(position)
}
}
}
/**
* 標題時間軸
*/
private fun dealWithTitleTimeLine(index: Int) {
when(index) {
0 -> {
itemTitleBing.aboveLineTitle.visibility = View.INVISIBLE
itemTitleBing.belowLineTitle.visibility = View.VISIBLE
}
else -> {
itemTitleBing.aboveLineTitle.visibility = View.VISIBLE
itemTitleBing.belowLineTitle.visibility = View.VISIBLE
}
}
}
/**
* 內容時間軸
*/
private fun dealWithContentTimeLine(index: Int) {
when(index) {
datas!!.size - 1 -> {
itemContentBinding.timeLineContent.renderBg(R.color.time_line_color, true)
}
else -> {
itemContentBinding.timeLineContent.renderBg(R.color.time_line_color, false)
}
}
}
override fun getItemCount(): Int {
return datas!!.size
}
override fun getItemViewType(position: Int): Int {
var id = datas!![position].id
//id是空的 說明該對象是用來展示標題item布局的
if(id.isNullOrEmpty()) {
return ITEM_TITLE_TYPE
}
return ITEM_CONTENT_TYPE
}
class TitleViewHolder(view: View) : ViewHolder(view)
class ContentViewHolder(view: View) : ViewHolder(view)
}
上面代碼邏輯不是很復雜,其實就是創(chuàng)建了兩個ViewHolder,一個是用來展示標題的ViewHolder,一個是來展示內容的ViewHolder。
重點看下這個,
根據(jù)position獲取當前對象的id,id是空的用來展示標題item布局的。因為剛講過,
創(chuàng)建空對象的時候,僅僅是把時間戳賦值給它,并沒有賦值給id,因此id是空的話,是用來展示標題的type。
BuildGroupDateActivity.kt
class BuildGroupDateActivity : AppCompatActivity() {
lateinit var adapter: BuildGroupDateAdapter
lateinit var binding: ActivityMainBuildGroupDateBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBuildGroupDateBinding.inflate(layoutInflater)
setContentView(binding.root)
adapter = BuildGroupDateAdapter(getDatas())
binding.baseRecyclerView.layoutManager = LinearLayoutManager(this)
binding.baseRecyclerView.adapter = adapter
}
private fun getDatas() : MutableList<AppInfo> {
return BuildListDataUtil.buildListData(TestBuildData.datas())
}
}
運行此項目,界面上就可以正常展示RecyclerView日期分組了。
接下來,我們看下時間軸怎么實現(xiàn)。
在adapter里面創(chuàng)建了兩個ViewHolder,每個ViewHolder創(chuàng)建了item布局,時間線的顯示其實就是在布局里面做。
item_title_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="36dp"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
>
<RelativeLayout
android:layout_width="30dp"
android:layout_height="match_parent"
>
<View
android:id="@+id/above_line_title"
android:layout_width="1dp"
android:layout_height="18dp"
android:background="@color/time_line_color"
android:layout_centerHorizontal="true"
android:layout_above="@+id/middle_line"
/>
<View
android:id="@+id/middle_line"
android:layout_width="5dp"
android:layout_height="5dp"
android:background="@drawable/time_line_round_bg"
android:layout_centerInParent="true"
/>
<View
android:id="@+id/below_line_title"
android:layout_width="1dp"
android:layout_height="18dp"
android:layout_below="@id/middle_line"
android:layout_centerHorizontal="true"
android:background="@color/time_line_color" />
</RelativeLayout>
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:layout_gravity="center_vertical"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
item_content_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
>
<RelativeLayout
android:layout_width="30dp"
android:layout_height="match_parent"
>
<com.xilianke.mainapp_master.view.TimeLineView
android:id="@+id/time_line_content"
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:background="@color/teal_700" />
</RelativeLayout>
<TextView
android:id="@+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:layout_gravity="center_vertical"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
在該布局里面需要自定義一個View,也就是TimeLineView,需要處理時間線漸變。
TimeLineView.kt
class TimeLineView : View {
constructor(context: Context) : this(context, null)
constructor(context: Context, attributeSet: AttributeSet?) :this (context, attributeSet, 0)
constructor(context: Context, attributeSet: AttributeSet?, def: Int):super(context, attributeSet, def)
fun renderBg(color: Int,lastItem:Boolean) {
if (lastItem) {
val gradient = gradientDrawable(color)
background = gradient
} else {
background = context.resources.getDrawable(color)
}
post {
invalidate()
}
}
private fun gradientDrawable(arg: Int): GradientDrawable {
val colors = intArrayOf(
// arg and 0xFFFFFFFF.toInt(),//0%
// arg and 0x7FFFFFFF,//50%
arg and 0x33FFFFFF,//80%
arg and 0x00FFFFFF,//100%
)
val gradientDrawable = GradientDrawable()
gradientDrawable.colors = colors
gradientDrawable.orientation = GradientDrawable
.Orientation.TOP_BOTTOM//從上到下漸變
return gradientDrawable
}
}
運行此項目,最終的效果就是文章開頭的效果圖。
我把這次案例進行總結,方便后續(xù)可能會使用到,難免會使用RecyclerView分組實現(xiàn),例如RecyclerView聯(lián)系人分組實現(xiàn)。