Previously I used kotlin synthetics, but now I’m trying to upgrade to data binding.
I have a recyclerview in a fragment and it will have multiple view types all of which will be custom-draw with a canvas.
Here is some of my code:
open class EtaRecyclerViewData {
data class OnDutyHeaderMain(
val leg:Int,
val shift:Int,
val startTime: String?,
) : EtaRecyclerViewData()
data class OnDutyHeaderLocation(
val leg:Int,
val shift:Int,
var city: String,
var state: String,
var country: String
) : EtaRecyclerViewData()
data class OnDutyHeaderBottom(
val leg:Int,
val shift:Int
):EtaRecyclerViewData()
data class SegmentDriving(
val leg:Int,
val shift:Int,
val segment: Int,
val startTime: String?
):EtaRecyclerViewData()
}
The recyclerviewadapter:
val itemList = arrayListOf<Any>()
private const val TYPE_ON_DUTY_HEADER_MAIN = 0
private const val TYPE_ON_DUTY_HEADER_LOCATION = 1
private const val TYPE_ON_DUTY_HEADER_BOTTOM = 2
private const val TYPE_SEGMENT_DRIVING = 3
class ChatDiffUtil(private var oldList: List<Any>, private var newList: List<Any>) : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldList.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
}
class EtaRecyclerViewAdapter(private val cellClickListener: CellClickListener) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
inner class OnDutyHeaderMain(private val onDutyHeaderMainBinding: OnDutyHeaderMainBinding) :
RecyclerView.ViewHolder(onDutyHeaderMainBinding.root) {
fun bind(segmentData: EtaRecyclerViewData.OnDutyHeaderMain) {
onDutyHeaderMainBinding.startTimeText = segmentData
onDutyHeaderMainBinding.locationBtn.setOnClickListener {
Log.d(LogTag, "class OnDutyHeaderMain->fun bind->Click locationBtn")
cellClickListener.onDutyHeaderMainLocationClickListener(segmentData)
}
}
}
inner class OnDutyHeaderLocation(private val onDutyHeaderLocationBinding: OnDutyHeaderLocationBinding) :
RecyclerView.ViewHolder(onDutyHeaderLocationBinding.root) {
fun bind(segmentData: EtaRecyclerViewData.OnDutyHeaderLocation) {
onDutyHeaderLocationBinding.startTimeText = segmentData
Log.d(LogTag, "class OnDutyHeaderLocation->fun bind: segmentData=$segmentData")
// TODO: Click listeners
}
}
inner class OnDutyHeaderBottom(private val onDutyHeaderBottomBinding: OnDutyHeaderBottomBinding) :
RecyclerView.ViewHolder(onDutyHeaderBottomBinding.root) {
fun bind(segmentData: EtaRecyclerViewData.OnDutyHeaderBottom) {
onDutyHeaderBottomBinding.headerBottom = segmentData
onDutyHeaderBottomBinding.showSegmentsBtn.setOnClickListener {
Log.d(LogTag, "EtaRecyclerViewAdapter-> class OnDutyHeaderBottom->fun bind->Click")
cellClickListener.onDutyHeaderBottomClickListener(segmentData)
}
}
}
inner class SegmentDriving(private val segmentDrivingBinding: SegmentDrivingBinding):
RecyclerView.ViewHolder(segmentDrivingBinding.root) {
fun bind(segmentData: EtaRecyclerViewData.SegmentDriving) {
segmentDrivingBinding.startTimeText = segmentData
segmentDrivingBinding.startTimeTextView.setOnClickListener {
Log.d(LogTag, "class SegmentOnDutyViewHolder->fun bind->Click")
cellClickListener.segmentDrivingClickListener(segmentData)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
TYPE_ON_DUTY_HEADER_MAIN -> OnDutyHeaderMain(
OnDutyHeaderMainBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
TYPE_ON_DUTY_HEADER_LOCATION -> OnDutyHeaderLocation(
OnDutyHeaderLocationBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
TYPE_ON_DUTY_HEADER_BOTTOM -> OnDutyHeaderBottom(
OnDutyHeaderBottomBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
TYPE_SEGMENT_DRIVING -> SegmentDriving(
SegmentDrivingBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
else -> throw IllegalArgumentException("Invalid ViewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is OnDutyHeaderMain -> holder.bind(itemList[position] as EtaRecyclerViewData.OnDutyHeaderMain)
is OnDutyHeaderLocation -> holder.bind(itemList[position] as EtaRecyclerViewData.OnDutyHeaderLocation)
is OnDutyHeaderBottom -> holder.bind(itemList[position] as EtaRecyclerViewData.OnDutyHeaderBottom)
is SegmentDriving -> holder.bind(itemList[position] as EtaRecyclerViewData.SegmentDriving)
}
}
override fun getItemCount(): Int = itemList.size
override fun getItemViewType(position: Int): Int {
return when (itemList[position]) {
is EtaRecyclerViewData.OnDutyHeaderMain -> TYPE_ON_DUTY_HEADER_MAIN
is EtaRecyclerViewData.OnDutyHeaderLocation -> TYPE_ON_DUTY_HEADER_LOCATION
is EtaRecyclerViewData.OnDutyHeaderBottom -> TYPE_ON_DUTY_HEADER_BOTTOM
is EtaRecyclerViewData.SegmentDriving -> TYPE_SEGMENT_DRIVING
else -> throw IllegalArgumentException("Invalid Item")
}
}
}
The customview:
package com.example.otrt.ui.home
class OnDutyHeaderLocation @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr),GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {
val Float.toPx get() = this * Resources.getSystem().displayMetrics.density
val Double.toStandardAngle get() = if (this + 90 > 360) this - 270 else this + 90.0
private val typeface =
Typeface.create(ResourcesCompat.getFont(context, R.font.latoregular), 500, false)
private val logTag = "DrivingCard"
private var gDetector: GestureDetectorCompat
var areaClicked: String = "null"
private val locationText = LocationText()
private inner class LocationText {
var left = 0f
private set
var top = 0f
private set
var right = 0f
private set
var bottom = 0f
private set
//Public variables
var city = "Some City"
var state = "Some State"
var country = "Some Country"
// Colors
var displaytextColor = Color.BLACK
// Paints
// Paths
// Information
private val cityDisplay = TextDisplay()
private val stateDisplay = TextDisplay()
private val countryDisplay = TextDisplay()
//Click Areas
val cityClickArea = Rect()
val stateClickArea = Rect()
val countryClickArea = Rect()
fun measure(
leftSide: Float,
topSide: Float,
rightSide: Float,
bottomSide: Float
) {
Log.d("DrivingCard-TopTab", "DrivingCard-TopTab:Measure")
left = leftSide
top = topSide
right = rightSide
bottom = bottomSide
with(cityDisplay) {
val bounds = RectF(
left,
top,
(right - left / 2) + left,
((bottom - top) * .7f) + top
)
halign = "LEFT"
vAlign = "TOP"
text = city
textColor = displaytextColor
set(bounds, 0f.toPx, 0f.toPx)
cityClickArea.set(bounds.toRect())
}
with(stateDisplay) {
val bounds = RectF(
left,
((bottom - top) * .7f) + top,
(right - left / 2) + left,
bottom
)
halign = "LEFT"
vAlign = "CENTER"
text = state
textColor = displaytextColor
set(bounds, 0f.toPx, 0f.toPx)
stateClickArea.set(bounds.toRect())
}
with(countryDisplay) {
val bounds = RectF(
left,
((bottom - top) * .7f) + top,
right,
bottom
)
halign = "RIGHT"
vAlign = "CENTER"
text = country
textColor = displaytextColor
set(bounds, 5f.toPx, 0f.toPx)
countryClickArea.set(bounds.toRect())
}
}
fun draw(canvas: Canvas) {
Log.d(logTag, "OnDutyHeaderLocation->class LocationText->fun draw:Draw")
cityDisplay.draw(canvas)
stateDisplay.draw(canvas)
countryDisplay.draw(canvas)
}
}
init {
setupAttributes(attrs)
this.gDetector = GestureDetectorCompat(context, this)
gDetector.setOnDoubleTapListener(this)
}
private fun setupAttributes(attrs: AttributeSet?) {
val typedArray =
context.theme.obtainStyledAttributes(attrs, R.styleable.OnDutyHeaderLocation, 0, 0)
val cityLocation = typedArray.getString(R.styleable.OnDutyHeaderLocation_cityLocation)
if(!isInEditMode) {
typedArray.recycle()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val totalWidth = widthSize.toFloat()
val totalHeight = 40f.toPx
val bottomPadding = 5f.toPx
val outLineStroke = 1f.toPx
locationText.measure(
5f.toPx,
5f.toPx,
totalWidth,
totalHeight
)
setMeasuredDimension(totalWidth.toInt(), totalHeight.toInt() + bottomPadding.toInt())
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
locationText.draw(canvas)
}
fun update() {
//newTopTab.update()
}
fun areaClicked(): String {
return areaClicked
}
override fun onDown(e: MotionEvent): Boolean {
TODO("Not yet implemented")
}
override fun onShowPress(e: MotionEvent) {
TODO("Not yet implemented")
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
TODO("Not yet implemented")
}
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
TODO("Not yet implemented")
}
override fun onLongPress(e: MotionEvent) {
TODO("Not yet implemented")
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
TODO("Not yet implemented")
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
TODO("Not yet implemented")
}
override fun onDoubleTap(e: MotionEvent): Boolean {
TODO("Not yet implemented")
}
override fun onDoubleTapEvent(e: MotionEvent): Boolean {
TODO("Not yet implemented")
}
}
the layout for the custom view:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="startTimeText"
type="com.example.otrt.ui.home.EtaRecyclerViewData.OnDutyHeaderLocation" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="50dp">
<com.example.otrt.ui.home.OnDutyHeaderLocation
android:id="@+id/onDutyHeaderLocation_canvas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cityLocation="Test"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
The attributes for the custom view:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="OnDutyHeaderLocation">
<attr name="cityLocation" format="string" />
<attr name="stateLocation" format="string" />
<attr name="countryLocation" format="string" />
</declare-styleable>
</resources>
I got the databinding working just fine on ‘normal’ views, but I can’t seem to figure out how to do it with the custom stuff. I can’t really find any examples online comparable to my situation.
Anything that help point me in the right direction is appreciated. I’m kind of swimming around here with little actual understanding of what I need to do.
James is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.