1

I need to onDraw the items of a RecyclerView. Using an approach "discovered" at this SO link, I have gotten - um - partway there.

Note that I ultimately want to onDraw "over" the custom view. Meaning call super to let the default drawing occur, then paint over unused areas of the (view's) canvas.

Before starting down this "custom view to allow onDraw" road, I had what you see on the left below:

enter image description here enter image description here enter image description here

Afterward, I had all "invisible" views (middle image above). I say "invisible" because they were still there to be clicked. To help me visualize things (and a bit of onDraw proof of concept) I overrode onDraw in the custom PuzzleView view, simply calling canvas.drawRect to cover the entire canvas in Green, and now see the right image above.

I am not sure what I'm doing wrong here.

Also, if it occurs to you that - well, why don't I simply onDraw the whole thing - that's not practical for a variety of reasons.

So, here's my PuzzleAdapter as it is now:

class PuzzleAdapter(private val puzzles: List<Puzzle>) : RecyclerView.Adapter<PuzzleAdapter.PuzzleHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PuzzleAdapter.PuzzleHolder {
        //val v = LayoutInflater.from(parent.context).inflate(R.layout.item_puzzle, parent, false)
        //not inflating, creating PuzzleView (which is inflating itself)
        val v = PuzzleView(parent.context)
        v.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        return PuzzleHolder(v)
    }

    override fun getItemCount() = puzzles.size

    //unchanged between these two versions
    override fun onBindViewHolder(h: PuzzleAdapter.PuzzleHolder, pos: Int) {
        val p = puzzles[pos]
        h.view.puzzleItem_text_ndx.text = "# " + p.descr()
        h.view.puzzleItem_text_details.text = "Ndx: ${p.puzzleNdx}"
        h.view.setOnClickListener {
            Log.d("##", "PuzzleHolder.onClick (bunch#${p.parentBunch.bunchID}; puzzle#${p.puzzleNdx})")
            val action = BunchFragmentDirections.navBunchToPuzzle(PuzzleParcel(p.parentBunch.bunchID, p.puzzleNdx))
            it.findNavController().navigate(action)
        }
    }

    inner class PuzzleHolder(v: View) : RecyclerView.ViewHolder(v) {
        val view: PuzzleView
        init {
            view = v as PuzzleView
        }
    }
}

PuzzleAdapter before:

class PuzzleAdapter(private val puzzles: List<Puzzle>) : RecyclerView.Adapter<PuzzleAdapter.PuzzleHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PuzzleAdapter.PuzzleHolder {
        val v = LayoutInflater.from(parent.context).inflate(R.layout.item_puzzle, parent, false)
        return PuzzleHolder(v)
    }

    override fun getItemCount() = puzzles.size

    //unchanged between these two versions
    override fun onBindViewHolder(h: PuzzleAdapter.PuzzleHolder, pos: Int) {
        val p = puzzles[pos]
        h.view.puzzleItem_text_ndx.text = "# " + p.descr()
        h.view.puzzleItem_text_details.text = "Ndx: ${p.puzzleNdx}"
        h.view.setOnClickListener {
            Log.d("##", "PuzzleHolder.onClick (bunch#${p.parentBunch.bunchID}; puzzle#${p.puzzleNdx})")
            val action = BunchFragmentDirections.navBunchToPuzzle(PuzzleParcel(p.parentBunch.bunchID, p.puzzleNdx))
            it.findNavController().navigate(action)
        }
    }

    inner class PuzzleHolder(v: View) : RecyclerView.ViewHolder(v) {
        var view: View = v
    }
}

PuzzleView (the custom view I am using with the Adapter):

class PuzzleView : RelativeLayout {
    constructor (context: Context) : super(context) { init(context, null, 0) }
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { init(context, attrs, 0) }
    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { init(context, attrs, defStyle) }

    private fun init(context: Context, attrs: AttributeSet?, defStyle: Int) {
        inflate(getContext(), R.layout.item_puzzle, this)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        //this.layout(l, t, r, b)
    }

    val rect = Rect(0, 0, 0, 0)
    val paint = Paint()
    override fun onDraw(canvas: Canvas) {
        //super.onDraw(canvas)
        Log.d("##", "PuzzleView.onDraw()")
        rect.right = width - 10
        rect.bottom = height - 10
        val bkgrColor = ContextCompat.getColor(App.context, R.color.Green)
        paint.style = Paint.Style.FILL
        paint.color = bkgrColor
        canvas.drawRect(rect, paint)
    }
}

A few thoughts on the above class/code:

  • override fun onLayout is required
  • I have other custom views that work fine (not in relation to a RecyclerView) with an empty onLayout
  • I tried (it's commented out in the above code) this.layout(l, t, r, b) but get a stack overflow exception

My only real thoughts here are (1) that there's something I'm supposed to be doing in this onLayout method, but I can't think of what; or (2) there's something wrong with the way that I'm inflating item_puzzle, but - again - I can't think of what. (I tried a few things on this, to no avail). I cannot think of anything else!

And here's all the other code I think could possibly be relevant:

From the Fragment containing the RecyclerView (it's what is shown in the above three images):

    bunch_recycler.layoutManager = GridLayoutManager(this.context, 3)
    bunch_recycler.adapter = PuzzleAdapter(bunch.puzzles)

Finally, the XML for the item itself, item_puzzle:

enter image description here

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="wrap_content"
    android:layout_marginBottom="16dp"
    android:layout_marginStart="16dp"
    android:layout_marginEnd="16dp"
    app:cardBackgroundColor="@color/facadeLight"
    app:cardElevation="0dp"
    >

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/shape_puzzle"
        >

        <TextView
            android:id="@+id/puzzleItem_text_ndx"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_marginEnd="16dp"
            android:ellipsize="end"
            android:fontFamily="@font/showg"
            android:maxLines="1"
            android:text="17"
            android:textColor="@color/facadeDark"
            android:textSize="32sp"
            />

        <TextView
            android:id="@+id/puzzleItem_text_details"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/puzzleItem_text_ndx"
            android:layout_marginStart="8dp"
            android:text="details"
            android:textSize="12sp"
            />
    </RelativeLayout>

</androidx.cardview.widget.CardView>

Also, if "custom view to allow onDraw" isn't the correct (or best) way of accomplishing my goal here, please let me know that as well.

Using:

  • Windows 10
  • Android Studio 3.4
  • Kotlin 1.3.31
  • Gradle 3.4.0
  • and the following:
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0-alpha05'
    implementation 'androidx.core:core-ktx:1.2.0-alpha01'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0-beta01'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-beta01'

    implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0'
    implementation 'androidx.navigation:navigation-ui-ktx:2.0.0'

Afterthought

I saw (in a 4 1/2-year-old youtube video overriding getView to assign subviews in a similar-seeming situation. There is no getView method to override in today's RecyclerView. The closest thing I see is getItemViewType(position: Int): Int which doesn't seem promising either (I read up on it a bit). Just thought I'd mention this in case it triggers a thought for you (where it didn't for me).

halfer
  • 19,824
  • 17
  • 99
  • 186
Free Dorfman
  • 341
  • 1
  • 3
  • 13

0 Answers0