I have a form which contains 2 fields and under those, few generated buttons in a RecyclerView. The RecyclerView has a GridLayout of 2 columns. My cells
I just noticed a bug which appears when I close the native keyboard (it has opened after I filled my two fields)
If I take the example of 3 buttons, I would have in my RecyclerView one row with 2 cells and a second row with one.
After closing my keyboard, the RecyclerView gets wrapped into one row of 2 cells and to access the second row, I have to scroll inside the RecyclerView.
I tried with a LinearLayout, same bug.
I tried few fixes found on StackOverFlow :
setting the keyboard to adjustPan in the Manifest
setting height to match_parent to my cells and layouts
None of those worked.
Here are some screens of the bugs:
Before opening the keyboard :
After closing the keyboard :
Any idea on how to fix this ?
Best regards
UPDATE 1 :
As asked, here are some code samples to reproduce the problem :
Adapter Setup :
binding.actionButtonsContainer.setLayoutColumnsCount(actionsList.size)
val adapter = IncidentActionButtonsListAdapter(actionsList, this)
binding.actionButtonsContainer.setAdapter(adapter)
binding.actionButtonsContainer.visibility = View.VISIBLE
My Adapter :
import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class IncidentActionButtonsListAdapter(
private val actionsList: List<IncidentAction>,
private val incidentActionButtonViewClickListener: IncidentActionButtonViewClickListener? = null
): RecyclerView.Adapter<IncidentActionButtonsListAdapter.IncidentActionButtonsListViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IncidentActionButtonsListViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_incident_action_button_item, parent, false)
return IncidentActionButtonsListViewHolder(view)
}
override fun onBindViewHolder(holder: IncidentActionButtonsListViewHolder, position: Int) {
actionsList[position].let { action ->
holder.tvIncidentActionTitle.text = holder.tvIncidentActionTitle.context.getString(action.text)
holder.itemView.setOnClickListener {
val incidentActionResult = IncidentActionResult(
code = action.id
)
incidentActionButtonViewClickListener?.onIncidentActionButtonViewClicked(incidentActionResult)
}
}
}
inner class IncidentActionButtonsListViewHolder(view: View): RecyclerView.ViewHolder(view) {
val container: LinearLayout = view.findViewById(R.id.container)
val tvIncidentActionTitle: TextView = view.findViewById(R.id.tvIncidentActionTitle)
}
override fun getItemCount(): Int = actionsList.size
}
interface IncidentActionButtonViewClickListener {
fun onIncidentActionButtonViewClicked(incidentActionResult: IncidentActionResult)
}
class IncidentActionButtonsItemDecorator (private val padding: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
)
{
super.getItemOffsets(outRect, view, parent, state)
outRect.top = padding
outRect.bottom = padding
outRect.left = padding
outRect.right = padding
}
}
The RecyclerView code :
class IncidentActionButtonView(
context: Context,
attrs: AttributeSet
): LinearLayout(context, attrs) {
var binding: LayoutIncidentActionListBinding = LayoutIncidentActionListBinding.inflate(LayoutInflater.from(context), this, true)
init {
val spacing = (context.resources.displayMetrics.density * 4).toInt() // converting dp to pixels
binding.list.addItemDecoration(IncidentActionButtonsItemDecorator(spacing)) // setting space between items in RecyclerView
}
fun setLayoutColumnsCount(numberOfActions: Int) {
var numberOfColumns = numberOfActions
val orientation = resources.configuration.orientation
// if phone is in landscape orientation we can accept up to 3 cols
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
if (numberOfColumns > 3) {
numberOfColumns = 3
}
} else { // else in portrait we can accept up to 2 cols
if (numberOfColumns > 2) {
numberOfColumns = 2
}
}
val layoutManager = GridLayoutManager(context, numberOfColumns)
binding.list.layoutManager = layoutManager
}
fun setAdapter(adapter: IncidentActionButtonsListAdapter) {
binding.list.adapter = adapter
binding.list.visibility = View.VISIBLE
}
}
The RecyclerView Layout :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/actionButtonsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/incident_list_background"
app:layout_constraintTop_toBottomOf="#id/tvTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
The cells layout :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/container"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="#drawable/incident_action_button_background"
android:orientation="horizontal"
android:paddingLeft="#dimen/padding_small"
android:paddingRight="#dimen/padding_small"
android:paddingTop="#dimen/padding_extra_small"
android:paddingBottom="#dimen/padding_extra_small">
<TextView
style="#style/Theme.PortailAchat.Title1"
android:id="#+id/tvIncidentActionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#color/white"
android:text="#string/incident_await_new_delivery"
android:textAllCaps="true"
android:textAlignment="center"
android:layout_gravity="center"/>
</LinearLayout>
add this line of code inside the activity tag in manifest file:
android:windowSoftInputMode="adjustPan"
Related
I need to create this kind of grid recycler view:
Where first item is biggest than others. Grid might have only 6 items and sizes might be this one:
The first one will have width = 320 and height = 220.
Others will have width = 100 and height = 150.
My recyclerView item XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="#dimen/home_items_space">
<ImageView
android:id="#+id/lookImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
My recyclerView XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="#dimen/looks_grid_height"
android:paddingHorizontal="12dp">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/looksGrid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="6" />
</androidx.constraintlayout.widget.ConstraintLayout>
My Adapter code:
class LooksAdapter :
ListAdapter<HomeLook, LooksAdapter.LooksViewHolder>(DiffCallback()) {
companion object {
private val FIRST_ITEM_INDEX = 0
}
var listener: ((HomeLook) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LooksViewHolder {
return LooksViewHolder(
ItemHomeLooksBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
override fun onBindViewHolder(holder: LooksViewHolder, position: Int) {
holder.bind(getItem(position), position)
}
class LooksViewHolder(private val binding: ItemHomeLooksBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(look: HomeLook, index: Int) {
if (index == FIRST_ITEM_INDEX) {
binding.root.updateLayoutParams {
width = binding.root.width / 2
height = 310
}
} else {
binding.root.updateLayoutParams {
width = 100
height = 150
}
}
binding.lookImage.load(look.lookImage) {
fallback(R.drawable.ic_camera_placeholder)
error(R.drawable.ic_camera_placeholder)
}
}
}
My ViewHolder code:
class LooksHomeViewHolder(
val binding: LooksGridViewBinding
) : RecyclerView.ViewHolder(binding.root) {
companion object {
fun from(parent: ViewGroup): LooksHomeViewHolder {
return LooksHomeViewHolder(
LooksGridViewBinding.inflate(LayoutInflater.from(parent.context), parent, false),
)
}
}
private val adapter = LooksAdapter().apply {
stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
}
init {
binding.looksGrid.adapter = adapter
binding.looksGrid.itemAnimator = DefaultItemAnimator().apply {
supportsChangeAnimations = false
}
}
fun bind(
lookBookEntry: HomeEntity.LookBookEntry,
listener: (HomeLook) -> Unit,
addPhotoListener: () -> Unit
) {
adapter.submitList(lookBookEntry.looks)
adapter.listener = listener
binding.addLookButton.setOnClickListener { addPhotoListener.invoke() }
binding.noLooksPlaceholder.isVisible = lookBookEntry.looks.isEmpty()
}
}
I tried to use StaggerManager and change imageView and root size. But it isn't help.
Basically you need to use StaggeredLayoutManager to achieve this output . You can check below mentioned links
example :-
https://www.geeksforgeeks.org/recyclerview-as-staggered-grid-in-android-with-example/
documentation :-
https://developer.android.com/reference/androidx/recyclerview/widget/StaggeredGridLayoutManager
What I want to do:
My idea is simple:
I have RecyclerView in RelativeLayout
When data loaded I set first item's text to TextView for pinned message
On scroll I take first visible item and set this item's text to header (TextView)
So I have:
RelativeLayout + RecyclerView + TextView for pinned message + TextView for header
I update header on scroll, it looks like sticky header for list.
It is my layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/pin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ebebeb"
android:gravity="center"
android:padding="24dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/pin"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#id/list"
android:layout_alignEnd="#id/list"
android:background="#33ff0000"
android:gravity="center"
android:padding="24dp"/>
</RelativeLayout>
And it is my Activity:
class CustomAdapter() :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
var data: List<UUID> = listOf()
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView
init {
textView = view.findViewById(R.id.text)
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.list_item, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.textView.text = data[position].toString()
}
override fun getItemCount() = data.size
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = CustomAdapter()
val header = findViewById<TextView>(R.id.header)
findViewById<RecyclerView>(R.id.list).apply {
this.adapter = adapter
layoutManager = LinearLayoutManager(this#MainActivity, LinearLayoutManager.VERTICAL, false)
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val position = recyclerView.topChildPosition() ?: return
if (position >= 0) {
header.text = adapter.data[position].toString().substring(0, 10)
Log.w("MainActivity", header.text.toString())
} else {
header.text = null
}
}
})
}
/**
* SIMULATE DATA LOADING
*/
Handler(Looper.getMainLooper()).postDelayed({
adapter.data = List(100) {
UUID.randomUUID()
}
findViewById<TextView>(R.id.pin).text = adapter.data[0].toString()
adapter.notifyDataSetChanged()
}, 1000L)
}
private fun RecyclerView.topChildPosition(): Int? {
layoutManager.let { layoutManager ->
if (layoutManager != null && layoutManager is LinearLayoutManager) {
return if (!layoutManager.reverseLayout) layoutManager.findFirstVisibleItemPosition()
else layoutManager.findLastVisibleItemPosition()
} else {
val topChild: View = getChildAt(0) ?: return null
return getChildAdapterPosition(topChild)
}
}
}
}
And what I have:
RelativeLayout measured views, header has text = null, so header's width = padding only
Data loaded (see "SIMULATE DATA LOADING" in the activity code), I call notifyDataSetChanged and I set text to header. Header (TextView) calls requestLayout() on set text, but RelativeLayout doesn't measured new width (actually, RelativeLayout calls header's onMeasure with widthMode == MeasureSpec.EXACTLY and pass old width)
When I scroll RecyclerView header remeasured successfully.
I can reproduce it on Android 26-33
What I can do with this.
I can change layout:
from this:
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#id/list"
android:layout_alignEnd="#id/list"
android:background="#33ff0000"
android:gravity="center"
android:padding="24dp"/>
to this:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignTop="#id/list"
android:layout_alignEnd="#id/list">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="#33ff0000"
android:gravity="center"
android:padding="24dp"/>
</FrameLayout>
But actually I can't, because really my TextView in custom view extends from TextView and it is part of library. I don't know how it will be used in layouts.
I can call requestLayout() after data loading like this:
adapter.notifyDataSetChanged()
header.doOnNextLayout { header.post { header.requestLayout() } }
But it is ugly.
So, finally my questions:
Do you know how to fix it without layout changing?
Do you know bug or some documented RelativeLayout behavior
Thanks for any help!
I get a problem with my horizontal RecyclerView. I need to create RecyclerView with different width of each item. I use wrap_content for this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
android:id="#+id/recyclerItemLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardPreventCornerOverlap="false"
app:cardCornerRadius="#dimen/_10sdp"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#android:color/darker_gray"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp">
<TextView
android:id="#+id/recyclerItemText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:textSize="#dimen/_17sdp"
android:text="test"
android:textColor="#color/colorKeyboardRecyclerViewItemText"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_marginTop="7dp"
android:layout_marginBottom="7dp"/>
</androidx.cardview.widget.CardView>
But when I scroll recyclerview and get to the first element I got this:
I think it is because the adapter redraws items every time. Here is code of my adapter:
class KeyboardAdapter(private val fontsNames: FontsData, private val fontButtonCallback: (Int) -> Unit): RecyclerView.Adapter<KeyboardAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.keyboard_recyclerview_item, parent, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return fontsNames.fontsArrayEn.count()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemText.text = fontsNames.fontsArrayEn[position].name
val selectedColor = holder.itemLayout.context.resources.getColor(R.color.colorKeyboardRecyclerViewSelectedItem)
val backgroundColor = holder.itemLayout.context.resources.getColor(android.R.color.transparent)
holder.itemLayout.setOnClickListener{
fontButtonCallback(position)
changeIsSelectedState(position)
fontsNames.fontsArrayEn[position].isSelected = true
notifyDataSetChanged()
}
/* holder.itemLayout.post{ val itemHeight = holder.itemLayout.height.toFloat()
val itemRadius = itemHeight /2.5
holder.itemLayout.radius = Utils().intToDp(holder.itemLayout.context, itemRadius.toFloat())} */
if (fontsNames.fontsArrayEn[position].isSelected)
holder.itemLayout.setCardBackgroundColor(selectedColor)
else
holder.itemLayout.setCardBackgroundColor(backgroundColor)
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val itemLayout = itemView.findViewById<CardView>(R.id.recyclerItemLayout)!!
val itemText = itemView.findViewById<TextView>(R.id.recyclerItemText)!!
}
private fun changeIsSelectedState(position: Int){
for (i in 0 until fontsNames.fontsArrayEn.count()){
fontsNames.fontsArrayEn[i].isSelected = i == position
}
}
}
And i have one more question. How I can set cardCornerRadius dynamically depends of item height ?
Thanks in advance!
The TextView inside the card is the problem.
The text view is match_parent
android:layout_width="match_parent"
android:layout_height="match_parent"
But the parent is wrap_content.
Change the TextView to wrap_content and then every word is gonna be the size of the TextView and the CardView will have the size of the child.
I want to add a headerview that hides when user scrolls to down and shows again when user scrolls to up.
Example: https://imgur.com/a/tTq70B0
As you can see in the link "You are writing as..." pharase is showing only when user scrolls to top. Is there something like that in Android sdk?
How can i achive the same thing?
Obtaining the scroll event is just the first step to achieving this. Animations is required to achieve the effect. I recreated a simple version of the gif example you posted.
Layout for the main activity, activity_main.xml
<?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="match_parent"
android:orientation="vertical"
android:animateLayoutChanges="true"> <!-- Note the last line-->
<TextView
android:id="#+id/textview_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:text="Hello Stack Overflow!"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerview_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Below is the code for the main activity where we populate the RecyclerView and use the addOnScrollListener to provide animations to the TextView. Do note the commented out lines these will provide a default fade-out or fade-in animation due to the noted line in the xml layout above. The method slideAnimation() is an example of creating a custom animation. This link proved useful for creating the animations.
class MainActivity : AppCompatActivity() {
private lateinit var viewAdapter: RecyclerView.Adapter<*>
private lateinit var viewManager: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Some data for the RecyclerView
val data: List<String> = (1..100).toList().map { it.toString() }
viewManager = LinearLayoutManager(this)
viewAdapter = TextAdapter(data)
findViewById<RecyclerView>(R.id.recyclerview_main).apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = viewAdapter
addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val pastVisibleItems = viewManager.findFirstCompletelyVisibleItemPosition()
if (pastVisibleItems == 0) {
slideAnimation(0f, 1f, View.VISIBLE)
//textview_hello.visibility = View.VISIBLE
} else if (textview_hello.visibility != View.GONE) {
slideAnimation(-150f, 0f, View.GONE)
//textview_hello.visibility = View.GONE
}
}
}
)
}
... // SlideAnimation function
}
}
The slideAnimation function
private fun slideAnimation(translationY: Float, alpha: Float, viewVisibility: Int) {
textview_hello.visibility = View.VISIBLE
textview_hello.clearAnimation()
textview_hello
.animate()
.translationY(translationY)
.alpha(alpha)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
textview_hello.clearAnimation()
textview_hello.visibility = viewVisibility
}
})
.duration = 500
}
The adapter for the RecycleView:
class TextAdapter(private val textList: List<String>) :
RecyclerView.Adapter<TextAdapter.TextViewHolder>() {
class TextViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): TextViewHolder {
val textView = LayoutInflater.from(parent.context)
.inflate(R.layout.item_text_view, parent, false) as TextView
return TextViewHolder(textView)
}
override fun onBindViewHolder(holder: TextViewHolder, position: Int) {
holder.textView.text = textList[position]
}
override fun getItemCount() = textList.size
}
Item to display in the RecyclerView, item_text_view.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_margin="8dp"
android:padding="16dp"
android:textAlignment="center"
android:background="#android:color/darker_gray">
</TextView>
The app contains two buttons, one for ListView and other for GridView, so I created to ItemViews and one adapter. I have created method, to switch between ListView and GridView. Every thing is working fine, except the GridView, which seems like it is jammed. List View is scrolling vertically but only upto 6 items, the list contains 9. I want the GridView to scroll in same fashion as List View scrolls until end. I have spent 2 simultaneous hours for searching solution, but none has worked. I have tried, ScrollView, NestedScrollView, but still unable to solve.
Grid View = RecyclerView with GridLayoutManager
Here is screenshot:
This one works fine upto 6 item and hide 3 out of 9
This one don't scroll (only GridView)
Here is Code:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity">
<FrameLayout
android:id="#+id/frameLayout_imageContainer"
android:layout_width="match_parent"
android:layout_height="180dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="#+id/iv_house"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
app:srcCompat="#drawable/house" />
</FrameLayout>
<LinearLayout
android:id="#+id/linearLayout_btnContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:gravity="end"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="#id/frameLayout_imageContainer">
<ImageButton
android:id="#+id/btnListView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/btn_gradient_purple"
android:padding="8dp"
android:tint="#color/colorWhite"
app:srcCompat="#drawable/ic_list" />
<ImageButton
android:id="#+id/btnGridView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:background="#drawable/btn_gradient_purple"
android:padding="8dp"
android:tint="#color/colorWhite"
app:srcCompat="#drawable/ic_grid" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_room"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="#+id/linearLayout_btnContainer">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private lateinit var mRoomList: ArrayList<Room>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val roomTypeBedroom = RoomType("Bedroom", R.drawable.ic_bedroom)
val roomTypeKitchen = RoomType("Kitchen", R.drawable.ic_kitchen)
val roomTypeBathroom = RoomType("Bathroom", R.drawable.ic_bathroom)
val roomTypeLiving = RoomType("Living", R.drawable.ic_living)
val roomTypeDining = RoomType("Dining", R.drawable.ic_dinning)
val roomTypeEmpty = RoomType("Empty", R.drawable.ic_empty)
mRoomList = arrayListOf(
Room("Bedroom 1", R.drawable.bedroom_1, 1, roomTypeBedroom),
Room("Bedroom 2", R.drawable.bedroom_2, 2, roomTypeBedroom),
Room("Bedroom 3", R.drawable.bedroom_3, 3, roomTypeBedroom),
Room("Kitchen", R.drawable.kitchen, 4, roomTypeKitchen),
Room("Bathroom", R.drawable.bathroom, 5, roomTypeBathroom),
Room("Living Room", R.drawable.living, 6, roomTypeLiving),
Room("Dining Room", R.drawable.sitting_area, 7, roomTypeDining),
Room("Empty Room 1", R.drawable.empty_1, 8, roomTypeEmpty),
Room("Empty Room 2", R.drawable.empty_2, 9, roomTypeEmpty)
)
showRoomsAs(ViewType.GRID)
btnGridView.setOnClickListener {
showRoomsAs(ViewType.GRID)
}
btnListView.setOnClickListener {
showRoomsAs(ViewType.LIST)
}
}
private fun showRoomsAs(viewType: ViewType) {
val roomAdapter = RoomAdapter(
this,
mRoomList,
viewType,
frameLayout_imageContainer
)
if (viewType == ViewType.GRID) {
rv_room.layoutManager = GridLayoutManager(this, 3)
btnGridView.visibility = View.GONE
btnListView.visibility = View.VISIBLE
} else {
rv_room.layoutManager = LinearLayoutManager(this)
btnGridView.visibility = View.VISIBLE
btnListView.visibility = View.GONE
}
rv_room.adapter = roomAdapter
}
}
RoomAdapter.k
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class RoomAdapter(
private val context: Context,
private val rooms: ArrayList<Room>,
private val type: ViewType,
private val imageContainer: FrameLayout)
: RecyclerView.Adapter<RoomAdapter.RoomViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RoomViewHolder {
val inflater = LayoutInflater.from(parent.context)
var view: View? = null
if (type == ViewType.GRID)
view = inflater.inflate(R.layout.rv_item_grid, parent, false)
else if (type == ViewType.LIST)
view = inflater.inflate(R.layout.rv_item_list, parent, false)
return RoomViewHolder(view!!)
}
override fun getItemCount(): Int {
return rooms.size
}
override fun onBindViewHolder(holder: RoomViewHolder, position: Int) {
val room = rooms[position]
holder.icon.setBackgroundResource(room.type.icon)
holder.name.text = room.name
holder.type.text = room.type.type
holder.status.text = room.isRoomOnOrOff()
}
inner class RoomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
override fun onClick(p0: View?) {
val room = rooms[adapterPosition]
if (room.associatedImageView == null) {
val imageView = ImageView(context)
imageView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
imageView.setImageResource(room.image)
room.associatedImageView = imageView
imageContainer.addView(imageView)
}
if (room.isOn) {
room.associatedImageView?.visibility = View.GONE
room.isOn = false
} else {
room.associatedImageView?.visibility = View.VISIBLE
room.isOn = true
}
notifyDataSetChanged()
}
val icon: ImageView
val name: TextView
val type: TextView
val status: TextView
init {
itemView.setOnClickListener(this)
icon = itemView.findViewById(R.id.rv_item_icon) as ImageView
name = itemView.findViewById(R.id.rv_room_name) as TextView
type = itemView.findViewById(R.id.rv_room_type) as TextView
status = itemView.findViewById(R.id.rv_status) as TextView
}
}
}
any kind of quick help will be appreciated
ConstraintLayout can be tricky sometimes. Your recyclerView might be sitting inches away from app's view port. You can try the below attributes on your recyclerView
Option 1 : use of large padding
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_room"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="250dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom ="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent">
</androidx.recyclerview.widget.RecyclerView>
Option 2: Use of app:layout_constraintBottom_toBottomOf
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_room"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="250dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom ="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#+id/linearLayout_btnContainer">
</androidx.recyclerview.widget.RecyclerView>
Adding app:layout_constraintBottom_toBottomOf="parent" in second option forces the bottom of the recyclerView stick to the bottom of constraintLayout and not overflow