I am using a RecyclerView with GridLayoutManager. Users can toggle the span count between 2 and 4, which would result in an animation that runs the inbuilt translate animation for each cell to it's new position. Code I have been using thus far is:
TransitionManager.beginDelayedTransition(moviesGridRecycler);
gridLayoutManager.setSpanCount(NEW_SPAN_COUNT);
adapter.notifyDataSetChanged();
This has been working fine for me, but now I need to have a different layout for each span count. To support this, I have 2 view types in the RecyclerView and since the view type is changed when moving to a new span count, RecyclerView is unable to see that it's the "same" content, and the default translate animation is not run.
If I enable layout transitions, I get view entry animations and not view position change animations. Cell views for both span count are having a width and height as match_parent and wrap_content.
As an alternative, I tried dropping the new view type all together, and just having one view type. I have a FrameLayout, which holds views for both span counts. In onBindViewHolder(), I simply toggle visibility of child views. This too results in no animations.
I followed this thread and this one, but could not resolve my issue.
Full Code:
class ItemFragment : Fragment() {
private var spanCount = 2
lateinit var recyclerView: RecyclerView
lateinit var button: Button
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_item_list, container, false)
recyclerView = view.findViewById(R.id.listView)
recyclerView.apply {
adapter = MyItemRecyclerViewAdapter(DummyContent.ITEMS, spanCount)
layoutManager = GridLayoutManager(context, spanCount)
addItemDecoration(RecyclerGridDecoration(context))
}
button = view.findViewById(R.id.toggle)
button.setOnClickListener { onToggle() }
return view
}
fun onToggle() {
spanCount = if (spanCount == 2) 4 else 2
TransitionManager.beginDelayedTransition(recyclerView)
(recyclerView.layoutManager as GridLayoutManager).spanCount = spanCount
(recyclerView.adapter!! as MyItemRecyclerViewAdapter).apply {
span = spanCount
notifyDataSetChanged()
}
}
}
class MyItemRecyclerViewAdapter(
private val mValues: List<DummyItem>,
var span: Int)
: RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
if (viewType == 2) {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.items_two, parent, false)
return TwoViewHolder(view)
} else {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.items_four, parent, false)
return FourViewHolder(view)
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = mValues[position]
if (holder is TwoViewHolder) {
holder.textTwo.text = item.content
} else if (holder is FourViewHolder) {
holder.textFour.text = item.content
}
}
override fun getItemCount(): Int = mValues.size
abstract inner class ViewHolder(mView: View) : RecyclerView.ViewHolder(mView)
inner class TwoViewHolder(mView: View) : ViewHolder(mView) {
val image: ImageView = mView.imageTwo
val textTwo: TextView = mView.textTwo
}
inner class FourViewHolder(mView: View) : ViewHolder(mView) {
val textFour: TextView = mView.textFour
}
override fun getItemViewType(position: Int): Int {
return span
}
}
I have a solution for your problem change your toggle function to this:
fun onToggle() {
spanCount = if (spanCount == 2) 4 else 2
(recyclerView.adapter!! as MyItemRecyclerViewAdapter).apply {
span = spanCount
notifyDataSetChanged()
}
recyclerView.post(object:Runnable {
public override fun run() {
TransitionManager.beginDelayedTransition(recyclerView)
(recyclerView.layoutManager as GridLayoutManager).spanCount = spanCount
}
})
}
In the above code we are first changing the viewType of recyclerView and then we are changing spanCount of GridLayoutManager on RecyclerView UI Handler Queue, so to achieve the two UI operations serially.
Related
I am trying to show header as one row in Paging3 library using GridLayoutManager. For the footer as one row, I made following implementation :
val header = LoadStateAdapter { showAdapter.retry() }
binding.list.apply {
val layoutManager = layoutManager as GridLayoutManager
layoutManager.spanSizeLookup = object : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (showAdapter.getItemViewType(position) == ShowAdapter.LOADING_ITEM)
1 else layoutManager.spanCount
}
}
adapter = showAdapter.withLoadStateHeaderAndFooter(
header = header,
footer = LoadStateAdapter { showAdapter.retry() }
)
}
And in the Adapter, I have following implementation :
override fun getItemViewType(position: Int): Int {
return if (position == itemCount) SHOW_ITEM else LOADING_ITEM
}
How about header? How can I display it in one row?
Full source code can be found here : https://github.com/alirezaeiii/Paging3-Sample
override HeaderAdapter getStateViewType() method
Create a ConcatAdapter use config
fun withHeaderAndFooter(
header: LoadStateAdapter<*>,
footer: LoadStateAdapter<*>
): ConcatAdapter {
addLoadStateListener { loadStates ->
header.loadState = loadStates.prepend
footer.loadState = loadStates.append
}
return ConcatAdapter(ConcatAdapter.Config.Builder().setIsolateViewTypes(false).build(), header, this, footer)
}
set layoutManager and override spanSizeLookup
val contactAdapter = adapter.withHeaderAndFooter(HeaderAdapter(), FooterAdapter())
layoutManager = GridLayoutManager(context, 2).apply {
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (contactAdapter?.getItemViewType(position) in
arrayOf(ADAPTER_FOOTER_TYPE,ADAPTER_HEADER_TYPE)
) spanCount else 1
}
}
}
I use a recyclerview in a viewpager an when I want to scroll to a position using smothScrollToPosition I see no change in the current page but when I slide I see that the change has been applied to the next or the previous page. I have define a function in the sliderAdapter call scrollTo in this method I call smothScrollToPosition.The method scrollTo is call when the user click on a button in the MainActivity
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val pager = findViewById<ViewPager>(R.id.view_pager)
val adapter = SlideAdapter(this)
pager.adapter = adapter
findViewById<Button>(R.id.button).setOnClickListener {
adapter.scrollTo(30)
}
}
}
SlideAdapter
class SlideAdapter(val context: Context) : PagerAdapter() {
lateinit var recyclerView: RecyclerView
lateinit var viewManager : LinearLayoutManager
fun scrollTo(position: Int) {
viewManager.scrollToPositionWithOffset(position, 0)
}
override fun isViewFromObject(view: View, `object`: Any): Boolean = view == `object` as RelativeLayout
override fun getCount() = 5
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val view = LayoutInflater.from(context)
.inflate(R.layout.slide, container, false) as RelativeLayout
val list = mutableListOf<String>()
for (i in 0..50)
list.add("Element $i")
val viewAdapter = Adapter(context, list)
viewManager = LinearLayoutManager(context)
recyclerView = view.findViewById(R.id.recyclerView)
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = viewAdapter
}
container.addView(view)
return view
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as RelativeLayout)
}
}
IMAGE PAGE 1 I CLICK ON THE BUTTON AND I SEE NO CHANGE
IMAGE PAGE 2 I SLIDE AND SEE THAT THE CHANGE HAS BEEN APPLIED ON THIS PAGE.
I need help please ^_^
From JavaDOC of method scrollToPositionWithOffset:
Note that scroll position change will not be reflected until the next layout call.
If you are just trying to make a position visible, use {#link #scrollToPosition(int)}.
May be here is the problem?
I have a vertical RecyclerView with LinearLayoutManager with different item types and one ViewHolder. It is a messaging screen.
I want show/hide message date on click on the cell.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layout = R.layout.xxx
val viewHolder = ViewHolder(LayoutInflater.from(parent.context).inflate(layout, parent, false))
viewHolder.itemView.layout?.setOnClickListener {
setMessageDateVisibility(viewHolder)
}
return viewHolder
}
So i added the method to reload view with notifyItemChanged(pos)
private fun setMessageDateVisibility(holder: ViewHolder) {
val pos = holder.adapterPosition
if (pos.toLong() == THREAD_DEFAULT_POSITION)
return
val message = mDataset[pos]
message.isDateVisible = !message.isDateVisible
notifyItemChanged(pos)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val message = mDataset[position]
itemView.message_date.text = message.date
itemView.message_date.visibility = if (message.isDateVisible)
View.VISIBLE
else
View.GONE
}
But the problem a the end, is that because of this line notifyItemChanged(pos), the override onBindViewHolder method called for all not visible cells and so my screen is lagging with 150/200 cells
Can you help me to find a solution ?
I'm new to Android development (and Kotlin).
I'm trying to implement a RecyclerView (which works fine) and when I click on a specific row it opens a new activity (Intent).
However, whenever I've press/click on one of the rows, I'm only able to get the value "-1" returned.
I've tried a number of different approaches (you should see the number of tabs in my browser).
This seems like it should be a fairly straightforward occurrence for something as common as a RecyclerView, but for whatever reason I'm unable to get it working.
Here is my RecyclerView Adapter file:
class PNHLePlayerAdapter (val players : ArrayList<PNHLePlayer>, val context: Context) : RecyclerView.Adapter<ViewHolder>() {
var onItemClick: ((Int)->Unit) = {}
// Gets the number of items in the list
override fun getItemCount(): Int {
return players.size
}
// Inflates the item views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(context).inflate(
R.layout.pnhle_list_item,
parent,
false
)
val viewHolder = ViewHolder(itemView)
itemView.setOnClickListener {
onItemClick(viewHolder.adapterPosition)
}
return ViewHolder(itemView)
}
// Binds each item in the ArrayList to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvPlayerName?.text = players[position].Name
holder.tvPlayerRank?.text = position.toString()
holder.tvPNHLe?.text = players[position].PNHLe.toString()
holder.tvTeam?.text = players[position].Team
holder.ivLeague?.setImageResource(leagueImageID)
}
}
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val linLayout = view.hor1LinearLayout
val ivTeam = view.teamImageView
val tvPlayerName = view.playerNameTextView
val tvPlayerRank = view.rankNumTextView
val tvPNHLe = view.pnhleTextView
val tvTeam = view.teamTextView
val ivLeague = view.leagueImageView
}
As you can see, there is a class property "onItemClick" which uses a lambda as the click callback.
I setOnClickListener in the onCreateViewHolder method after the view is inflated.
Next, in my Activity I add the list to my Adapter and set the call back.
However, every time I 'Toast' the position it is displayed as '-1'.
val adapter = PNHLePlayerAdapter(list, this)
adapter.onItemClick = { position ->
Toast.makeText(this, position.toString(),Toast.LENGTH_SHORT).show()
var intent = Intent(this, PlayerCardActivity::class.java)
//startActivity(intent)
}
rv_player_list.adapter = adapter
Perhaps I'm not thinking about this properly, but shouldn't the position represent the row number of the item out of the RecyclerView???
Ideally, I need to use the position so that I can obtain the correct item from the 'list' (ArrayList) so that I can pass information to my next Activity using the Intent
I found the issue.
Change this line in onCreateViewHolder:
return ViewHolder(itemView)
to this one:
return viewHolder
I would reorganize the adapter like this:
class PNHLePlayerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<Adapter.ViewHolder>() {
interface AdapterListener {
fun onItemSelected(position: Int?)
}
var players: List<Player> = listOf()
set(value) {
field = value
this.notifyDataSetChanged()
}
var listener: AdapterListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_car_selector, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int {
return brands.size
}
inner class ViewHolder(view: View): androidx.recyclerview.widget.RecyclerView.ViewHolder(view) {
private var position: Int? = null
private val baseView: LinearLayout? = view.findViewById(R.id.baseView) as LinearLayout?
...
init {
baseView?.setOnClickListener {
listener?.onManufacturerSelected(position)
}
}
fun bind(position: Int) {
this.position = position
...
}
}
}
And from your activity/fragment set the listener as adapter.listener = this, and implement the onItemSelected(position: Int?)
override fun onItemSelected(position: Int?) {
...
}
I would like to make a RecyclerView having differents elements per line depending on a boolean in the adapter.
In my app, you have some banknote and coins. I have to put all in a RecyclerView. First of all I want to show the banknote (2 per 2) and after the coins (4 per 4)
like this :
This is how I want the result look like :
This is my Adapter
class MoneyAdapter(private val money_list: ArrayList<Money>) : RecyclerView.Adapter<MoneyAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MoneyAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.item_money, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: MoneyAdapter.ViewHolder, position: Int) {
money_map.put(money_list[position].value, 0)
holder.bindItems(money_list[position])
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(money: Money) {
// if (money.isCoin) I would like to put it on a 4 element per line
}
}
}
override fun getItemCount(): Int {
return money_list.size
}
}
This is where I declar emy RecyclerView :
recycler_money.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
recycler_money.adapter = MoneyAdapter(getMoneyList(PrefsModel.currency)
I was searching a lot of examples but it's always with differents col/row using StaggeredGridLayoutManager and I want only change the number of elements by line (2 or 4) directly on the Adapter.
I found the solution thanks to #Gautam :
val l = GridLayoutManager(context, 4)
l.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
if (getMoneyList(PrefsModel.currency)[position].type == MONEY_PIECE)
return 1
else
return 2
}
}
recycler_money.layoutManager = l