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:
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 onLayoutis 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:
<?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).



