How to use recyclerview smothScrollToPosition in a viewpager - android

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?

Related

Segmented Tab Layout with RecyclerView Data

I want to make a custom view with two Recyclerview data. First one is header titles, second one is content details. When I click on a title, I want different XML files to be shown.
After some research, I saw that I could do it using TabLayout and ViewPager, however, I don't want to use an extra fragment for each tab, instead I want to inflate an XML file. Because I want to use this view on other screens as a custom view.
How can I do that? What should I use as a solution? Thank you.
You can use ViewPager2 :
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/topicViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
you can assign second recycler adapter to ViewPager2 for example:-
class DummyAdapter(var list:ArrayList<String>) :
RecyclerView.Adapter<DummyAdapter.ViewHolder>() {
lateinit var context: Context
var isFullScreen = false
var isDownload: Boolean = false
var doubleClick = false
inner class ViewHolder(val binding: DummyItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(
position: Int,
context: Context,
adapter: DummyAdapter,
) {
with(binding) {
// add data
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder {
context = parent.context
val inflater = LayoutInflater.from(context)
val binding = DummyItemBinding.inflate(inflater, parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) =
holder.bind(position, context, this, list[position])
override fun getItemCount(): Int {
return list.size
}
}
binding.viewPager.adapter = dummyAdapter
then on the click of TabLayout you can update the adapter of second recyclerView.

How do I implement onClickListener to a recyclerView to open a new fragment when clicked

I am trying to implement onClickListener with recyclerView to open a new fragement, I understand that they are a lot this on stackoverflow already but as a beginner I find them somewhat complex.
Like I said I want to open a fragment when I click on recyclerView, and I have some questions;
Mainly, how do I do this??
how do I controll the Views (i.e textViews, imageViews, etc) of the screens I'm navigating to??
Here's the fragment that has the recyclerView
class NewScreenFragment : Fragment() {
private var _binding: FragmentNewScreenBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
// for the recyclerView
private lateinit var newArrayList: ArrayList<DataClass>
// variables to the recyclerView views
lateinit var imageId: Array<Int>
lateinit var title: Array<String>
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentNewScreenBinding.inflate(inflater, container, false)
val root: View = binding.root
imageId = arrayOf(
R.drawable.ways_start_cpa_marketing,
R.drawable.ways_start_gigging,
R.drawable.ways_teach_english_online,
R.drawable.ways_test_websites,
R.drawable.ways_tutor_on_your_own_time,
R.drawable.ways_use_your_voice,
R.drawable.ways_write_a_list
)
title = arrayOf(
getString(R.string.Become_A_Dog_Walker),
getString(R.string.Become_A_Proofreader),
getString(R.string.Become_A_Virtual_Assistant),
getString(R.string.Make_Things_to_Sell),
getString(R.string.Give_your_opinion),
getString(R.string.Play_Games),
getString(R.string.Start_A_Blog),
)
// this creates a vertical layout Manager
binding.recyclerview.layoutManager = LinearLayoutManager(context)
/** I change LinearLayoutManager(this)
* to LinearLayoutManager(context)
*
* remember this in case of any issue
**/
binding.recyclerview.setHasFixedSize(true)
newArrayList = arrayListOf<DataClass>()
getUserData()
return root
}
private fun getUserData() {
for (i in imageId.indices){
val dataClass = DataClass(imageId[i],title[i])
newArrayList.add(dataClass)
}
// ArrayList of class ItemsViewModel
// val data = ArrayList<DataClass>()
// This loop will create Views containing
// the image with the count of view
// This will pass the ArrayList to our Adapter
// val adapter = RecyclerAdapter(data)
// Setting the Adapter with the recyclerview
binding.recyclerview.adapter = RecyclerAdapter(newArrayList)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Here's the Adapter.kt
class RecyclerAdapter(private val mList: ArrayList<DataClass>) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
// create new views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// inflates the card_view_design view
// that is used to hold list item
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.new_screen_recyclerview_look, parent, false)
return ViewHolder(view)
}
// binds the list items to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val dataClass = mList[position]
// sets the image to the imageview from our itemHolder class
holder.imageView.setImageResource(dataClass.cardImage)
// sets the text to the textview from our itemHolder class
holder.textView.text = dataClass.cardTitle
}
// return the number of the items in the list
override fun getItemCount(): Int {
return mList.size
}
// Holds the views for adding it to image and text
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.card_image)
val textView: TextView = itemView.findViewById(R.id.card_title)
// implement the onClickListener event here
init {
itemView.setOnClickListener {
}
}
}
}
Not needed though, but for more clarify.. Here's my Data class;
data class DataClass(var cardImage: Int, var cardTitle: String) {
}
I really appreciate your help, thanks to you in advance..
Create a new parameter on your Adapter constructor
Change
class RecyclerAdapter(private val mList: ArrayList<DataClass>)
to
class RecyclerAdapter(private val mList: ArrayList<DataClass>, private val onItemClicked: (DataClass) -> Unit)
Set the clickListener for you item inside onBindViewHolder. If you wanna make the whole item clickable, set:
holder.root.setOnClickListener{
onItemClicked(dataClass)
}
On your fragment, when creating the adapter, pass the new parameter
binding.recyclerview.adapter = RecyclerAdapter(newArrayList){ dataClass ->
//Here, 'dataClass' will be the clicked item on recyclerView. Here you can do any logic that's supposed to happen after the click, like opening a new fragment passing 'dataClass' as parameter.
}
To open a new frag passing dataClass as parameter, check "newInstance" pattern.
Understanding the Fragment.newInstance method

RecyclerView kotlin don't remember selected items, after add next items

Hello i have problem with my shopping List. I have RoomDatabase and recycler view to adding and reading my list. I create action when user click on button row change background, but after i add new items then don't see selected items. Where is a problem?
My Adapter:
lass ListAdapter() : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
private var itemList = emptyList<Items>()
lateinit var viewModel: ItemsViewModel
inner class MyViewHolder(itemview: View) : RecyclerView.ViewHolder(itemview) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.items_row, parent, false)
)
}
override fun getItemCount(): Int {
return itemList.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = itemList[position]
holder.itemView.name_rec.text = currentItem.itemsName
holder.itemView.amount_rec.text = currentItem.amountItems.toString()
holder.itemView.rowLayout.setOnClickListener {
val action = ListFragmentDirections.actionListFragmentToUpdateFragment(currentItem)
holder.itemView.findNavController().navigate(action)
}
holder.itemView.btnDeleteee.setOnClickListener {
val action = ListFragmentDirections.actionListFragmentToDeleteFragment(currentItem)
holder.itemView.findNavController().navigate(action)
}
holder.itemView.btncheckbox.setOnClickListener {
holder.itemView.rowLayout.setBackgroundColor(Color.parseColor("#FFBA5F"))
}
}
fun setData(items: List<Items>) {
this.itemList = items
notifyDataSetChanged()
}
My ListFragment:
class ListFragment : Fragment() {
lateinit var itemsViewModel: ItemsViewModel
private val args by navArgs<UpdateFragmentArgs>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_list, container, false)
//menu
setHasOptionsMenu(true)
//RecyclerView
val recyclerView = view.recycler_view_list
val adapter = ListAdapter()
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
//ViewWModel
itemsViewModel = ViewModelProvider(this).get(ItemsViewModel::class.java)
itemsViewModel.readAllData.observe(viewLifecycleOwner, Observer { items ->
adapter.setData(items)
})
view.floatingActionButton2.setOnClickListener {
findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
Where is problem with save all actions who do user in list?
Thanks for help
You have to store the selected item and in onBindViewHolder you have to check that the item for this position is previously selected item or not. If this is the selected item then change the background or make if default.
Actually the problem is as you are changing the background in onClickListener so when you are updating the itemList with setData(items: List<Items>) method RecyclerView reload all the visible items and your previously selected item loses its background

Save/Restore Fragment state with RecyclerView in Android

I have created an Android app, which connects to GitHub API and shows a list of repositories of certain user. List is shown by recyclerView. I'm trying to save and restore a list, when I'm switching fragments. I have tried to save a list to a variable and add this to recyclerView. But it doesn't work.
My Fragment:
class MainFragment : Fragment() {
lateinit var recyclerView: RecyclerView
var responseSave:List<GitHubPOJO> = ArrayList()
var posts: MutableList<GitHubPOJO> = ArrayList()
lateinit var btn:Button
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_main, container, false)
btn = view.findViewById(R.id.button)
btn.setOnClickListener {
posts = ArrayList()
val name:String = view!!.findViewById<EditText>(R.id.editText).text.toString()
recyclerView = view!!.findViewById(R.id.posts_recycle_view)
val layoutManager = LinearLayoutManager(this.activity!!)
recyclerView.layoutManager = layoutManager
val adapter = PostsAdapter(posts)
recyclerView.adapter = adapter
//HIDE KEYBOARD
val inputMethodManager = this.activity!!.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
if(activity!!.currentFocus !=null) {
inputMethodManager.hideSoftInputFromWindow(this.activity!!.currentFocus!!.windowToken, 0)
}
val service = Retrofit.Builder()
.baseUrl("https://api.github.com/") // CHANGE API
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(GitHubService::class.java)
service.retrieveRepositories(name)
.enqueue(object : Callback<List<GitHubPOJO>> {
override fun onResponse(call: Call<List<GitHubPOJO>>, response: Response<List<GitHubPOJO>>) {
responseSave = response.body()!!
posts.addAll(responseSave)
response.body()?.forEach { println ("TAG_: $it")}
recyclerView.adapter?.notifyDataSetChanged()
}
override fun onFailure(call: Call<List<GitHubPOJO>>, t: Throwable) {
//Toast.makeText(this#MainFragment, "Error occurred while networking", Toast.LENGTH_SHORT).show()
}
})
recyclerView.addOnItemTouchListener(
ClickListener(this.activity!!, recyclerView, object : ClickListener.OnItemClickListener {
override fun onLongItemClick(view: View?, position: Int) {
}
override fun onItemClick(view: View, position: Int) {
val url = posts!![position].htmlUrl
println("URL = $url")
view.findNavController().navigate(MainFragmentDirections.actionMainFragmentToWebFragment(url))
}
})
)
}
return view
}
And there is my code onResume:
override fun onResume() {
super.onResume()
val adapter = PostsAdapter()
adapter.updateAdapterList(responseSave.toMutableList())
println("RESUME")
println(responseSave)
}
When I print responseSave I see that my list is there. But it doesn't appears in RecyclerView.
Fragments are swithced by standart navigation library.
Activity code:
class MainActivity : AppCompatActivity(){
private lateinit var navController: NavController
private lateinit var mNavView:NavigationView
private lateinit var mDrawerLayout:DrawerLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mDrawerLayout = findViewById(R.id.drawer_layout)
mNavView = findViewById(R.id.nav_view)
navController = this.findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController,mDrawerLayout)
NavigationUI.setupWithNavController(mNavView, navController)
}
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.myNavHostFragment)
return NavigationUI.navigateUp(mDrawerLayout,navController)
}}
My RecyclerView Adapter code:
class PostsAdapter(private var posts: MutableList<GitHubPOJO>? = ArrayList()) : RecyclerView.Adapter<PostsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.post_item, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val post = posts!![position]
holder.post.text = post.name
holder.site.text = post.fullName // Change what you wanna see
}
fun updateAdapterList(newList: MutableList<GitHubPOJO>) {
posts!!.clear()
posts!!.addAll(newList)
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return posts?.size ?: 0
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var post: TextView = itemView.findViewById<View>(R.id.postitem_post) as TextView
var site: TextView = itemView.findViewById<View>(R.id.postitem_site) as TextView
}}
Add a method in your adapter thats something like;
fun updateAdapterList(newList: MutableList<GitHubPOJO>) {
oldList.clear()
oldList.addAll(newList)
notifyDataSetChanged()
}
Then call this in your onResume() and pass in your updated list
The problem is on your onResume method. You are not adding an Adapter and a LayoutManager to the RecylerView so it can't display the data. Try moving the RecylerView initalization code from the onClickListener to your onResume method:
recyclerView = view!!.findViewById(R.id.posts_recycle_view)
val layoutManager = LinearLayoutManager(this.activity!!)
recyclerView.layoutManager = layoutManager
val adapter = PostsAdapter(posts)
recyclerView.adapter = adapter
Although having this initialization code in your onCreateView method would be better since it's the first lifecycle method called when a fragment returns to the front view from the backstack.

RecyclerView.Adapter and GridLayoutManager with spanCount great than 4 continuously recreates ViewHolders

I am working with GridLayoutManager and I have encountered unexpected RecyclerView's behaviour. If spanCount is greater than 4, the RecyclerView continuously recreates ViewHolders on scrolling.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = GridLayoutManager(this, 7)
recyclerView.adapter = Adapter()
}
private class Adapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
//continuously invokes while scrolling:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view:View = TextView(parent.context).apply {
text = "Hello!"
}
return object : RecyclerView.ViewHolder(view) {}
}
override fun getItemCount(): Int = 3500
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {}
}
How to fix it and force RecyclerView.Adapter to reuse ViewHolders?
I reproduced your issue with this code (copy-pasteable, no resource files required):
class RecActivity : AppCompatActivity() {
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
recyclerView = RecyclerView(this)
setContentView(recyclerView)
recyclerView.layoutManager = GridLayoutManager(this, 7)
recyclerView.adapter = Adapter()
}
inner class Adapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var vhCount = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val txtView = AppCompatTextView(this#RecActivity)
txtView.tag = vhCount++.toString()
txtView.gravity = Gravity.CENTER
title = vhCount.toString() // display # of created VHs in title
return object : RecyclerView.ViewHolder(txtView){}
}
override fun getItemCount() = 3500
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = "$position (${holder.itemView.tag})"
}
}
}
The problem is there's not enough views to fill out entire rows in the RecycledViewPool. By default there's only 5 items per ViewType, so having wide rows of 7 force creation of more ViewHolders while scrolling. To fix this issue, simply increase size of your RecycledViewPool like so (in onCreate):
recyclerView.layoutManager = GridLayoutManager(this, 7)
recyclerView.adapter = Adapter()
// add line below: 0 is default itemViewType, 14 is two rows of items which should be enough
recyclerView.recycledViewPool.setMaxRecycledViews(0, 14)

Categories

Resources