Custom ViewGroup for behaving like RecyclerView: TextView not displaying Text in RelativeLayout but show Text in LinearLayout

Viewed 34

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: diagram

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. Log ouput

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

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>

when i changed to LinearLayout

Can anyone point out what I did wrong here? I am looking forward to your enlightment.

0 Answers
Related