I am trying to implement a ViewGroup named MyRecyclerView that acts like a real RecyclerView. LayoutManager and ViewHolder are not implemented in MyReclerView, the whole layout process is done by MyRecyclerView on its own.
MyAdapter is a dummy interface for feeding data to MyRecyclerView:
val myAdapter = object: MyRecyclerAdapter{
override fun onCreateViewHolder(row: Int, convertView: View?, parent: ViewGroup): View {
val id = when(getItemViewType(row)) {
0 -> R.layout.item_custom_view0
1 -> R.layout.item_custom_view1
else -> -1
}
val resultView = convertView ?: layoutInflater.inflate(id, parent, false)
if(getItemViewType(row) == 1)
resultView.findViewById<TextView>(R.id.tv_item1).text = testList[row]
return resultView
}
override fun onBindViewHolder(row: Int, convertView: View?, parent: ViewGroup):View {
val id = when(getItemViewType(row)) {
0 -> R.layout.item_custom_view0
1 -> R.layout.item_custom_view1
else -> -1
}
val resultView = convertView ?: layoutInflater.inflate(id, parent, false)
if(getItemViewType(row) == 1)
resultView.findViewById<TextView>(R.id.tv_item1).text = testList[row]
return resultView
}
override fun getItemViewType(row: Int): Int = row%2
override fun getItemViewTypeCount(): Int {
return 2
}
override fun getItemCount(): Int = itemCount
override fun getItemHeight(row: Int): Int = itemHeight // fixed height
}
A diagram illustrates how I wish this MyRecyclerView to work out: 
I did not override onMeasure in MyRecyclerView, since I could not determine how many items should be put on screen when data first loaded. And this job is handled in onLayout. Items are set fixed heights. When the sum of heights of items greater than the height of MyRecyclerView, no more items are layout.
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
if(mNeedRelayout || changed) {
mNeedRelayout = false
mItemViewList.clear() // mItemViewList cached all views on Screen
removeAllViews()
if (mAdapter != null) {
// heights of each item is fixed (as 80dp) and accessed by MyAdapter
for(i in 0 until mItemCount)
mHeights[i] += mAdapter!!.getItemHeight(i)
// since onMeasure is not implemented
// mWidth: the width of MyRecyclerView
// mHeight: the height of MyRecyclerView
mWidth = r - l
mHeight = b - t
var itemViewTop = 0
var itemViewBottom = 0
// foreach whole data list, and determine how many items should be
// put on screen when data is first loaded
for(i in 0 until mItemCount){
itemViewBottom = itemViewTop + mHeights[i]
// itemView is layout in makeAndSetUpView
val itemView = makeAndSetupView(i, l,itemViewTop,r,itemViewBottom)
// there is no gap between item views
itemViewTop = itemViewBottom
mItemViewList.add(itemView)
addView(itemView, 0)
// if top of current item view is below screen, it should not be
// displayed on screen
if(itemViewTop > mHeight) break
}
}
}
}
I know itemView returned by makeAndSetUpView is a ViewGroup, and I call layout on it, all of its children will be measured and layout too.
private fun makeAndSetupView(
index: Int,
left: Int,
top: Int,
right: Int,
bottom: Int
): View{
// a simple reuse scheme
val itemView = obtain(index, right-left, bottom-top)
// layout children
itemView.layout(left, top, right, bottom)
return itemView
}
When I scroll down or up MyRecyclerView, Views out of screen will be reused and new data (text strings) are set in TextView, these views fill the blank. This behavior is carried out via relayoutViews:
private fun relayoutViews(){
val left = 0
var top = -mScrollY
val right = mWidth
var bottom = 0
mItemViewList.forEachIndexed { index, view ->
bottom = top + mHeights[index]
view.layout(left, top, right, bottom)
top = bottom
}
// Relayout is finished
mItemViewList.forEachIndexed { index, view ->
val lastView = (view as ViewGroup).getChildAt(0)
Log.d("TEST", " i: $index top: ${lastView.top} bottom: ${lastView.bottom} left: ${lastView.left} right: ${lastView.right}")
Log.d("TEST", " i: $index width: ${lastView.width} height: ${lastView.height}")
}
}
Debug output here is normal, views in cached mItemViewList seem to carry everything needed. And the text string is also set into TexView in the ItemView ViewGroup.

But it is so confusing that it seems no TextView is even created when I scroll and reuse these views.

ItemViews here are wrapped by a RelativeLayout:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/item_height">
<TextView
android:text=" i am a fixed Text"
android:id="@+id/tv_item0"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
But when I just changed it to LinearLayout, everything is ok and views are reused and text strings are set.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/item_height"
android:orientation="vertical"
android:gravity="center"
android:layout_marginTop="2dp">
<TextView
android:text="i am a fixed text"
android:id="@+id/tv_item0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Can anyone point out what I did wrong here? I am looking forward to your enlightment.
