Is it possible to add a footer to the MaterialDateRange Picker? - android

I'm pretty sure this is impossible, but I just wanted to check before I use a 3rd party library. The DatePicker doesn't really let you customize the UI, and it's basically a full screen DialogFragment so putting something above or below (or even on top) doesn't seem to be an option.
My calendar currently looks like this:
But I would like it to look like this design doc with a footer at the bottom. Let me know if you have any ideas, because I'm out of them. Thanks!
val builder = MaterialDatePicker.Builder.dateRangePicker()
.setTheme(R.style.ThemeOverlay_MaterialComponents_MaterialCalendar_Fullscreen)
.setCalendarConstraints(
CalendarConstraints.Builder()
.setStart(Date().time)
.setEnd(oneYearFrom().time)
.setValidator(AvailabilityValidator(it.unavailableDays)).build()
)
builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR)
builder.build().show(requireActivity().supportFragmentManager, TAG_DATE_RANGE_PICKER)

The only way to add a footer to the MaterialDateRangePicker is to get access to the Picker root View and from there you can find the Picker FrameLayout Container (with Id app:id/mtrl_calendar_frame) and get access to its child View the Vertical LinearLayout Container which contains of three children: (a Days-GridView, a Separator-View and a RecyclerView) in a vertical orientation. What you want is to add a footer below the RecyclerView. Because the parent is a LinearLayout you can add the weight attribute to all its children to be weight=0 except the RecyclerView (which render the months) to be weight=1 to get all the remaining space.
To access the Picker root View you have to observe the MaterialDatePicker (DialogFragment) lifecycle and add the footer View in onStart() of DialogFragment. For this purpose you can use the DefaultLifecycleObserver.
Below is full working example to add a footer View:
private fun showRangeDatePicker() {
//initialize dateRangePicker
val builder = MaterialDatePicker.Builder.dateRangePicker()
builder.setTheme(R.style.ThemeOverlay_MaterialComponents_MaterialCalendar_Fullscreen)
//set CalendarConstraints
val constraintsBuilder = CalendarConstraints.Builder()
val c: Calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
constraintsBuilder.setStart(c.getTimeInMillis())
c.add(Calendar.YEAR, 1)
constraintsBuilder.setEnd(c.getTimeInMillis())
builder.setCalendarConstraints(constraintsBuilder.build())
//set InputMode and Title
builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR)
builder.setTitleText("Pick dates")
//get MaterialDatePicker instance and set a lifecycle Observer to listen for DialogFragment lifecycle
//DefaultLifecycleObserver needs androidx.appcompat:appcompat:1.4.0 and add the FooterView in onStart of DialogFragment
val picker: MaterialDatePicker<*> = builder.build()
picker.lifecycle.addObserver(object : DefaultLifecycleObserver
{
override fun onCreate(owner: LifecycleOwner) {
}
override fun onStart(owner: LifecycleOwner) {
//add footerView in onStart of DialogFragment so you can access the picker.view
addMyFooterView(picker)
}
override fun onResume(owner: LifecycleOwner) {
}
override fun onDestroy(owner: LifecycleOwner) {
//remove Lifecycle Observer
picker.lifecycle.removeObserver(this)
}
})
picker.show(supportFragmentManager, picker.toString())
}
private fun addMyFooterView(picker: MaterialDatePicker<*>) {
//get the MaterialDatePicker root View
val rootView = picker.view as LinearLayout
//find the Picker FrameLayout Container with ID: app:id/mtrl_calendar_frame
val frameLayout = rootView.findViewById<FrameLayout>(resources.getIdentifier("mtrl_calendar_frame", "id", packageName))
//and get the Picker LinearLayout Container (which contains of 3 children: Days-GridView, Separator-View and a RecyclerView)
val linearContainer = frameLayout.getChildAt(0) as LinearLayout
//for each child in Vertical LinearLayout Container set the weight to RecyclerView to 1 and 0 to all others.
//(This is necessary before adding the footerView to distribute correctly each children Height)
for (i in 0 until linearContainer.childCount) {
val v = linearContainer.getChildAt(i)
val params = v.layoutParams as LinearLayout.LayoutParams
params.weight = if (v is RecyclerView) 1f else 0f
v.layoutParams = params
}
//and finally add the 4th child in the above linearContainer to be the FooterView
val myFooterView = layoutInflater.inflate(R.layout.footer_layout, null)
val myFooterViewHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt()
myFooterView.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, myFooterViewHeight, 0f)
myFooterView.setBackgroundColor(Color.WHITE)
linearContainer.addView(myFooterView)
//set any FooterView click listeners
myFooterView.findViewById<View>(R.id.textView2).setOnClickListener {}
}
Where footer_layout is your custom footer xml layout like below:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp">
<View
android:id="#+id/separatorView"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentTop="true"
android:background="#android:color/darker_gray"/>
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:textStyle="bold"
android:layout_marginEnd="10dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:text="Match exact dates" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:textStyle="normal"
android:layout_marginEnd="10dp"
android:layout_centerVertical="true"
android:layout_toEndOf="#+id/textView1"
android:text="±1 day" />
<TextView
android:id="#+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:textStyle="normal"
android:layout_marginEnd="10dp"
android:layout_centerVertical="true"
android:layout_toEndOf="#+id/textView2"
android:text="±3 days" />
</RelativeLayout>
Result:

Related

Can I access a button in a TableLayout(10x10) without having to give each button an id?

I have crated a TableLayout which is populated with buttons.
I want the buttons to change color once clicked. But there are going to be 100 buttons so I do not think creating an id and a onClickListener() is the correct way of going about it for each button.
Is there a way I can tell which button is clicked without delegating id's to each button?
<TableRow
android:layout_height="0dp"
android:layout_weight="1"
android:id="#+id/row1"
>
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button 1"
/>
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button 2"
/>
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button 3"
/>
</TableRow>
<TableRow
android:layout_height="0dp"
android:layout_weight="1"
>
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button 4"
/>
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button 5"
/>
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button 6"
/>
</TableRow>
<TableRow
android:layout_height="0dp"
android:layout_weight="1"
>
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button 7"
/>
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button 8"
/>
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button 9"
/>
</TableRow>
I have seen a few similar question's but when I implement them nothing seems to happen. This is in my Main Activity and my attempt of logging a button click but nothing is logged.
val table : TableLayout = findViewById(R.id.table1)
val row : TableRow = findViewById(R.id.row1)
row.setOnClickListener(object : View.OnClickListener{
override fun onClick(p0: View?) {
val tr = p0?.getParent() as TableRow
val rowIndex: Int = table.indexOfChild(p0)
Log.i("TAG!!!!!!!!!!!!!!!", "onClick: " + tr)
}
})
You have a few ways of doing it - it depends what you want to do, but you could create a View lookup (so can associate each button with some data) or you could programmatically assign some data to the button itself (like with setTag(Object)).
Here's a simple "give each button an index" approach:
lateinit var buttons: List<Button>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// basically get each table row, and turn them into a stream of buttons.
// flatMap means that instead of mapping each row to a List<Button>,
// so we get a list of lists, they're unpacked into a single list of all the contents
buttons = view.findViewById<TableLayout>(R.id.table1)
.children.filterIsInstance<TableRow>()
.flatMap { row -> row.children.filterIsInstance<Button>() }
.onEach { it.setOnClickListener(::onButtonClick) } // set this here why not
.toList()
}
private fun onButtonClick(view: View) {
val button = view as Button
Toast.makeText(requireContext(), "Button ${buttons.indexOf(button) + 1} clicked!", Toast.LENGTH_SHORT).show()
}
Here's how you could nest your iterations so you can keep track of which row and column a button is in, and you can set that info on the button itself using its tag:
data class TableButton(val row: Int, val column: Int)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// could be neater but you get the idea!
view.findViewById<TableLayout>(R.id.table1)
.children.filterIsInstance<TableRow>()
.mapIndexed { rowNum, row ->
row.children.filterIsInstance<Button>()
.forEachIndexed { colNum, button ->
button.tag = TableButton(rowNum, colNum)
button.setOnClickListener(::onButtonClick)
}
}
}
private fun onButtonClick(view: View) {
val position = view.tag as TableButton
Toast.makeText(requireContext(), "Row ${position.row}, column: ${position.column}", Toast.LENGTH_SHORT).show()
}
I made the onButtonClick function match the click listener signature (takes a View parameter) so you can just pass it as a function reference (::onButtonClick) - so the cast to Button happens in there. You could also create a single click listener function and do it in there instead:
val clickListener = { v: View -> onButtonClick(v as Button) }
buttons.forEach { it.setOnClickListener(clickListener) }
}
private fun onButtonClick(button: Button) {
Toast.makeText(requireContext(), "Button ${buttons.indexOf(button) + 1} clicked!", Toast.LENGTH_SHORT).show()
}
which keeps onButtonClick a bit "cleaner" since it only takes Buttons now. Or you could just remove the separate function entirely and do it all in the lambda function - these are just ways to avoid creating 100 click listener functions, and just reuse the same one to handle it all!
your TableRow constains some clickable = true items (Buttons), so when you press any then it swallows touch event and doesn't pass it to parent, on which you are setting listener. in fact your current listener never fire as there is no possibility of TableRow click - it is fulfilled completely by clickable items
best approach would be to use custom ids for all buttons, but there is another way - just set same OnClickListener for all childrens of this view/row
val onClick = object : View.OnClickListener{
override fun onClick(p0: View?) { // p0 will be a button
val tr = p0?.getParent() as TableRow // child of TableRow for shure
val rowIndex = tr.indexOfChild(p0) // index of clicked child in its parent (row)
val tableIndex = table.indexOfChild(tr) // index of this row in whole table
Log.i("TAG!!!!!!!!!!!!!!!",
"onClick rowIndex:" + rowIndex + " tableIndex:" + tableIndex)
}
}
val row : TableRow = findViewById(R.id.row1)
for (i until 0..row.childCount) {
row.getChildAt(i).setOnClickListener(onClick)
}

Badge Drawable is cutting from view and not showing proper way Android Kotlin

Hey I want to show badge drawable. I tried code from here Does BadgeDrawable not work for views within a FrameLayout such as buttons, images, textviews etc.?, BadgeDrawable does not appear on elements other than BottomNavigationView, Badge Drawable not showing. I successfully show but problem is cutting from side edge.
<FrameLayout
android:id="#+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="24dp"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingStart="6dp"
android:paddingTop="4dp"
android:paddingEnd="9dp"
android:paddingBottom="4dp"
app:layout_constraintBottom_toBottomOf="#+id/xyz"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/xyz">
<ImageView
android:id="#+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:src="#drawable/ic_settings"
tools:ignore="ContentDescription" />
</FrameLayout>
activity.kt
fun addBadgeDrawable(count: Int, target: View, parent: FrameLayout, context: Context) {
target.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val badgeDrawable = BadgeDrawable.create(context)
badgeDrawable.number = count
badgeDrawable.badgeGravity = BadgeDrawable.TOP_END
badgeDrawable.setBoundsFor(target, parent)
parent.foreground = badgeDrawable
target.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
}
private fun BadgeDrawable.setBoundsFor(#NonNull anchor: View, #NonNull parent: FrameLayout) {
val rect = Rect()
parent.getDrawingRect(rect)
this.bounds = rect
this.updateBadgeCoordinates(anchor, parent)
}
I am setting badge view this code line
addBadgeDrawable(10, binding.icon, binding.container, context)
Output
Expected Output
To avoid cutting, you need to increase paddingTop and paddingEnd in FrameLayout.
Or may be need to add additional ConstraintLayout as a parent for FrameLayout, and play with constraints and paddings.
Hope this will help to someone.

onBindViewHolder binding view inconsistently

I have a ViewHolder that is meant to appear differently depending on whether it is on the left or right side of a two-column RecyclerView with a GridLayoutManager. Note the connector lines on either side of the view:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_marginTop="12px"
android:layout_marginBottom="12px"
android:layout_gravity="center"
android:gravity="center_vertical"
android:layout_height="wrap_content"
android:id="#+id/citation_select_holder">
<ImageView
android:src="#drawable/connector_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="#+id/citation_select_connector_right"/>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="348px"
android:layout_height="104px"
android:layout_weight="1"
android:background="#drawable/button_background_white"
android:id="#+id/citation_select_citation_holder">
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="28px"
>
<TextView
tools:text="123456"
android:textAppearance="#style/citation_select_item_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/citation_select_citation_number_text"/>
<TextView
tools:text="Pay by: Nov 18th, 2019"
android:textAppearance="#style/citation_select_item_due_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/citation_select_due_date_text"/>
<TextView
tools:text="a category label"
android:textAppearance="#style/citation_select_item_category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/citation_select_category_text"/>
</LinearLayout>
<TextView
tools:text="$10.00"
android:textAppearance="#style/citation_select_item_cost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:id="#+id/citation_select_cost_text" android:layout_marginRight="28px"/>
</RelativeLayout>
<ImageView
android:src="#drawable/connector_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="#+id/citation_select_connector_left"/>
</LinearLayout>
The connector line far from the side that the view appears on is meant to disappear when onBindViewHolder is called, and the margins updated accordingly.
if (position % 2 == 0) {
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_left).visibility = View.GONE
val marginLayoutParams1 = holder.citationHolder.layoutParams as GridLayoutManager.LayoutParams
marginLayoutParams1.setMargins(0, 12, 12, 12)
holder.itemView.findViewById<LinearLayout>(R.id.citation_select_holder).layoutParams =
marginLayoutParams1
} else {
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_right).visibility = View.GONE
val marginLayoutParams2 = holder.citationHolder.layoutParams as GridLayoutManager.LayoutParams
marginLayoutParams2.setMargins(12, 12, 0, 12)
holder.itemView.findViewById<LinearLayout>(R.id.citation_select_holder).layoutParams =
marginLayoutParams2
}
Scrolling is done exclusively via on-screen buttons in increments of six. The first two pages load normally:
But the pattern begins to break down at citation #14. Keep in mind that the citation numbers correspond are the view's position within the RecyclerView:
What is happening to change the behavior?
I think I know what can help you fix this. I presume that it is reusing the old view, as a RecyclerView should and nowhere in your code is there a line to set the visibility of the connector lines back to visible.
You should add to both your GONE visibilities also the code to set the other to visible:
if (position % 2 == 0) {
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_right).visibility = View.VISIBLE
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_left).visibility = View.GONE
val marginLayoutParams1 = holder.citationHolder.layoutParams as GridLayoutManager.LayoutParams
marginLayoutParams1.setMargins(0, 12, 12, 12)
holder.itemView.findViewById<LinearLayout>(R.id.citation_select_holder).layoutParams =
marginLayoutParams1
} else {
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_left).visibility = View.VISIBLE
holder.itemView.findViewById<ImageView>(R.id.citation_select_connector_right).visibility = View.GONE
val marginLayoutParams2 = holder.citationHolder.layoutParams as GridLayoutManager.LayoutParams
marginLayoutParams2.setMargins(12, 12, 0, 12)
holder.itemView.findViewById<LinearLayout>(R.id.citation_select_holder).layoutParams =
marginLayoutParams2
}
Additional explanation
The RecyclerView will reuse some old view, right? Well since both lines are VISIBLE at the start, you assume that's their default state. But when you set the lines to GONE you never put them back to visible, and thus if RecyclerView reuses that view, it'll not add the margin there and it will just be missing the connector line. You always want to have EVERY line of code in onBindViewHolder to have a matching line that reverts it.
Vucko's answer is good, and the overall point (always update every component of your viewholder) is something you should absolutely do.
I wanted to add, however, that it appears as though you are not following the ViewHolder pattern correctly: your onBindViewHolder() method should never call findViewById(). Instead, your ViewHolder class should find each view once and then save references to them.
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val connectorRight: ImageView = itemView.findViewById(R.id.citation_select_connector_right)
val connectorLeft: ImageView = itemView.findViewById(R.id.citation_select_connector_left)
// ...
}
And then you can use these fields directly inside onBindViewHolder():
if (position % 2 == 0) {
holder.connectorRight.visibility = View.VISIBLE
holder.connectorLeft.visibility = View.GONE
// ...
} else {
holder.connectorLeft.visibility = View.VISIBLE
holder.connectorRight.visibility = View.GONE
// ...
}

Expanding view inside RecyclerView doesn't animate properly

I'm building a UI that consists of multiple CardViews inside a RecyclerView.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
android:id="#+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardCornerRadius="5dp"
app:cardElevation="1dp"
app:cardBackgroundColor="#ddffca"
android:animateLayoutChanges="true"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:padding="10dp"
>
<TextView
android:id="#+id/tvHello"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="Hello there!"
/>
<TextView
android:id="#+id/test"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:visibility="gone"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tvHello"
android:text="GENERAL KENOBI!"
/>
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>
Inside I have specified that on click even I will have my test text animated to appear. It's working great and I'm mostly happy with the results. But as soon as I add a few cards inside the RecyclerView the animation starts working a bit strange. What I mean is views that are not touched are not animating properly considering the other views have changed their size. Instead I see another view jumping to its new position without any animation.
How can I make it animate according to other views?
EDIT
I have also provided my code from onBindViewHolder. It's in Kotlin though:
override fun onBindViewHolder(
holder: OperationsViewHolder,
position: Int
) {
var card: CardView = holder.cardView
card.setOnClickListener {
if (!operations.get(position).selected!!) {
ObjectAnimator.ofFloat(card, "translationZ", 1f, 10f)
.start()
holder.test.visibility = View.VISIBLE;
operations.get(position)
.selected = true
} else {
ObjectAnimator.ofFloat(card, "translationZ", 10f, 1f)
.start()
holder.test.visibility = View.GONE;
operations.get(position)
.selected = false
}
}
}
EDIT 2 I have also tried adding android:animateLayoutChanges="true" to all elements, didn't help
Not sure if this fits the constraints of your question but you don't necessarily have to animate the expanding/collapsing manually. The RecyclerView can provide that to you out-of-the-box by using notifyItemChanged() properly in your Adapter.
override fun onBindViewHolder(
holder: OperationsViewHolder,
position: Int
) {
var card: CardView = holder.cardView
if (operations.get(position).selected!!) {
holder.test.visibility = View.VISIBLE;
} else {
holder.test.visibility = View.GONE;
}
card.setOnClickListener {
if (!operations.get(position).selected!!) {
operations.get(position)
.selected = true
} else {
operations.get(position)
.selected = false
}
notifyItemChanged(position)
}
}
The above code removes the animation logic and instead calls notifyItemChanged every time the CardView is clicked. This tells the RecyclerView to re-render the item at that particular position and gives you an animation for the re-render for free.

Android RecyclerView ItemDecorator: align Inset Divider with List Item TextView

I would like to inset a RecyclerView ItemDecorator divider to left align with a TextView (the item title) inside the list element that is constrained to be 72dp from the parent left side. Something like we see here in the Google Material Design Docs. I assume that I need to somehow reference the layout params on the titleTextView, but am not sure how to do that from my ItemDecorator code, as I seem to be able to only get the params which is the ConstraintLayout, and not the textview itself. Any help or pointers in the right direction would be greatly appreciated! The relevant ItemDecorator code, where I try to get the textView param looks something like this:
for (int i = 0; i < recyclerView.getChildCount() - 1; ++i) {
View child = recyclerView.getChildAt(i);
RecyclerView.LayoutParams params =(RecyclerView.LayoutParams) child.getLayoutParams();
//this just gives me the constraint layout, not the TextView params, how to reference the textViewParams!?
The RecyclerView looks like this:
<android.support.v7.widget.RecyclerView
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/myList"
android:name="com.example.Something"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager="LinearLayoutManager"
tools:context=".controllers.fragments.ExampleFragment"
tools:listitem="#layout/fragment_something"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
The list item xml is a constraint layout with some TextViews, and the relevant portion looks like this:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="72dp">
<ImageView
android:id="#+id/myImageView"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="parent"
tools:src="#drawable/ic_menu_manage"
android:src="#drawable/ic_menu_manage"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp" />
<TextView
android:id="#+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="72dp"
android:layout_marginTop="20dp"
android:text="MyTitle"
android:textAppearance="#style/TextAppearance.AppCompat.Subhead"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="#+id/textView5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="SampleTitle" />
...
</android.support.constraint.ConstraintLayout>
use a custom item decorator.
class CustomItemDecoration(context: Context) : RecyclerView.ItemDecoration()
{
private val mDivider: Drawable = context.resources.getDrawable(R.drawable.custom_item_divider) // this is a shape that i want to use
override fun onDrawOver(c: Canvas,
parent: RecyclerView,
state: RecyclerView.State)
{
val left = parent.paddingLeft // change to how much padding u want to add here .
val right = parent.width - parent.paddingRight
val childCount = parent.childCount
for (i in 0 until childCount)
{
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + mDivider.intrinsicHeight
mDivider.setBounds(left, top, right, bottom)
mDivider.draw(c)
}
}
}
you set it like that
recyclerView.addItemDecoration(CustomItemDecoration(context))
Since the ConstraintLayout is a ViewGroup, it is possible to access the child of the ConstraintLayout from inside of the ItemDecorator by simply doing something like:
public class MyItemDivider extends RecyclerView.ItemDecoration {
....some code here
#Override
public void onDrawOver(Canvas canvas, RecyclerView recyclerView,
RecyclerView.State state) {
super.onDrawOver(canvas, recyclerView, state);
for (int i = 0; i < recyclerView.getChildCount() - 1; ++i) {
ViewGroup item = (ViewGroup) recyclerView.getChildAt(i);
//for now the titleText is at index 1 of constraint layout
TextView titleChild = (TextView) item.getChildAt(1);
int titleTextLeftMargin = titleChild.getLeft();
//this would probably be less brittle
//but I would need to somehow change the context for this to be doable?
//View titleChild = item.findViewById(R.id.contractListProductNameTextView);
...some more code here
divider.setBounds(titleTextLeftMargin, top, right, bottom);
divider.draw(canvas);
}
}
}
Since you already know by how much you have to inset the divider from the left side (calculate it from your layout if you don't know), all you need to do is convert that dp value to px as shown here and set it as the left bound in your custom ItemDecoration.
public class InsetDividerDecoration extends RecyclerView.ItemDecoration {
private final int insetValueInDp = 72; // in your case
#Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
int dividerLeft = (int) (insetValueInDp * Resources.getSystem().getDisplayMetrics().density);
...
}
}
And use it in your recycler view -
recyclerView.addItemDecoration(new InsetDividerDecoration());

Categories

Resources