When I click on recycler view several times, it scrolls to bottom on random clicks.
I suspect the issue is with emulator. My AVD is Nexus 5 API 27 x86.
Question: How to eliminate this random scrolling ?
Here is minimal example: https://github.com/OleksandrBezhan/RecyclerViewAccidentalScrolling
Activity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recycler.layoutManager = LinearLayoutManager(this)
recycler.adapter = MyAdapter()
}
}
Adapter:
class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
class ViewHolder(view: View, val text: TextView) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item, parent, false)
return ViewHolder(view, view.text)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.text.text = "Hello world"
}
override fun getItemCount() = 3
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>
item.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</android.support.constraint.ConstraintLayout>
Update:
I added OnScrollListener and it shows that on accidental scrolls the scroll state goes to SCROLL_STATE_FLING and then SCROLL_STATE_IDLE.
Whereas under normal circumstances the scroll state should go to SCROLL_STATE_TOUCH_SCROLL -> SCROLL_STATE_FLING -> SCROLL_STATE_IDLE.
recycler.addOnScrollListener(object: RecyclerView.OnScrollListener(){
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
Log.d("TEST", "onScrollStateChanged: $newState")
}
})
D/TEST: onScrollStateChanged: 2
D/TEST: onScrollStateChanged: 0
// 1, 2, 0 in normal scrolling
It seems to be an emulator issue, since there is no problem on real device.
Related
I'm using Kotlin to create an event that generates a toast message on click of a recyclerview. I run into trouble making a Tost message in a recyclerview event.
I tried the following page, but couldn't solve it.
Toast message is not working in Recycler View
error code is
in kotlin & None of the following functions can be called with the arguments supplied: public open fun makeText(p0: Context!, p1: CharSequence!, p2: Int): Toast! defined in android.widget.Toast public open fun makeText(p0: Context!, p1: Int, p2: Int): Toast! defined in android.widget.Toast
PrintActivity.kt
package com.questionbank
class PrintActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vBinding = ActivityPrintBinding.inflate(layoutInflater)
setContentView(vBinding.root)
val helper = SqliteHelper(this, "myDB.sql", 1)
var recyclerViewAdapter = CustomAdapter()
recyclerViewAdapter.listData = helper.select()
vBinding.myRecyclerView.adapter = recyclerViewAdapter
vBinding.myRecyclerView.layoutManager = LinearLayoutManager(this)
vBinding.myRecyclerView.addItemDecoration(
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
)
}
class CustomAdapter : RecyclerView.Adapter<CustomAdapter.Holder>() {
var listData = ArrayList<questionType>()
inner class Holder(val vBinding: QuestionLayoutRecyclerBinding) :
RecyclerView.ViewHolder(vBinding.root) {
fun setData(id:Int?, question: String, answer: String, exp: String) {
vBinding.printId.text=id.toString()
vBinding.myLinear.setOnClickListener {
// error occur
Toast.makeText(this#PrintActivity, "test", Toast.LENGTH_SHORT).show()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val vBinding = QuestionLayoutRecyclerBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return Holder(vBinding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val question = listData[position]
holder.setData(question.id, question.question, question.answer, question.exp)
}
override fun getItemCount(): Int {
return listData.size
}
}
}
activity_print
<?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"
tools:context=".PrintActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/myRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
question_layout_recycler.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/myLinear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="#+id/printId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView" />
</LinearLayout>
Two ways to fix,
Make CustomAdapter class as inner class.
innner class CustomAdapter : RecyclerView.Adapter<CustomAdapter.Holder>() {
So toast function it will take constant from activity class.
In viewholder, get context from view. it.context will get context from linearlayout.
vBinding.myLinear.setOnClickListener {
Toast.makeText(it.context, "test", Toast.LENGTH_SHORT).show()
}
Its recommended to place adapter logic in separate file and use second solution.
So you dont need to make adapter as inner class.
My ReycyclerView is not showing data, but once i removed .setHasFixedSize(true), then only it will display. However, this causes another problem. On the first time entering the fragment, it works fine, my actionBar is displaying, but on the second time, it somehow overlaps or pushes away my ActionBar.
I guess .sethasFixedSize is necessary but if i keep it, it will not display data. What causes this?
First time entering:
Second time entering (before notifyItemInserted()):
After notifyItemInserted():
fragment.xml:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/cartItemRv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
fragment.kt:
onCreateView() {
binding.cartItemRv.layoutManager = LinearLayoutManager(context)
binding.cartItemRv.setHasFixedSize(true)
cartAdapter = CartAdapter(productNameList, productVariationList, cartItemList)
binding.cartItemRv.adapter = cartAdapter
//code for retrieving data from Firebase
cartAdapter.notifyItemInserted(productNameList.size-1)
}
Adapter:
class CartAdapter(...: RecyclerView.Adapter<CartAdapter.CartViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.cart_item, parent, false)
return CartViewHolder(itemView, mListener)
}
override fun onBindViewHolder(holder: CartViewHolder, position: Int) {
...
}
override fun getItemCount(): Int {
return productNameList.size
}
inner class CartViewHolder(itemView: View, listener: onItemClickListener) : RecyclerView.ViewHolder(itemView){
...
}
}
about sethasFixedSize , if the size of your the RecyclerView depends on the adapter's content you have to set the sethasFixedSize vlaue : false
otherwise set it true .
// for depending on adapter
mRecyclerView.setHasFixedSize(false);
// for doesn't depend on adapter
mRecyclerView.setHasFixedSize(true);
add NestedScrollView on the parent of RecyclerView
<?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="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/cartItemRv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"/>
</androidx.core.widget.NestedScrollView>
</RelativeLayout>
Then set RecyclerView
cartItemRv.setHasFixedSize(false);
I have a recycler view issue where the data is not being displayed. onCreateViewHolder and onBindViewHolder are not being called. When I call notifyDataSetChanged(), notifyChanged() is called but mObservers is empty, so it won't update my list.
public void notifyChanged() {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
here's my adapter code:
class ReleasesAdapter : RecyclerView.Adapter<ReleasesAdapter.ReleasesViewHolder>() {
private val data = mutableListOf<Album>(Album())
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReleasesViewHolder {
return ReleasesViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.song_item, parent, false)
)
}
override fun onBindViewHolder(holder: ReleasesViewHolder, position: Int) {
holder.bindView(data[position])
}
override fun getItemCount(): Int {
return data.size
}
fun setItems(items: List<Album>) {
data.addAll(items)
notifyDataSetChanged()
}
inner class ReleasesViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bindView(item: Album) {
containerView.nameTv.text = item.name
}
}
}
And here's my activity code:
#AndroidEntryPoint
class MainActivity : AppCompatActivity(), Contract.View {
#Inject
lateinit var presenter: Contract.Presenter
private val releasesAdapter = ReleasesAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
resultsRv.run {
adapter = releasesAdapter
layoutManager = LinearLayoutManager(this#MainActivity)
}
presenter.loadNewReleases()
}
override fun showReleases(data: List<Album>) {
releasesAdapter.setItems(data)
}
override fun showErrorMessage(message: String) {
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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:orientation="vertical"
tools:context=".presentation.MainActivity">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/resultsRv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="#layout/song_item" />
</androidx.appcompat.widget.LinearLayoutCompat>
song_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/nameTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="#dimen/song_item_margin"
android:textColor="#color/black" />
Please send help :(
Edit: Added Xml code
I believe you are using the scope functions wrong especially run in this case. Use run() function if you need to compute some value or want to limit the scope of multiple local variables.
So while your other code seems okay, I believe your Adapter and LayoutManager is not being assigned. I would suggest you replace the run with apply and try again.
resultsRv.apply {
adapter = releasesAdapter
layoutManager = LinearLayoutManager(this#MainActivity)
}
I am brand new in Android/kotlin development. I created my very first app with a recyclerView to display the folders and files of the phone. I put the recyclerView into a segment, created my data structure and adapter. Assembled together. It works - or seems to work. But the problem is that when I try to scroll the list, the initial state remains "ther" and the list content starts to scroll. Like it was two different layers. I have no clue where to find the problem. Never heard about such a bug like this. Please give me advice, some keywords where to dig and find the solution. Thanks!
class BrowseFileFragment : Fragment() {
private lateinit var attachedCtx : Context
override fun onAttach(context: Context) {
super.onAttach(context)
this.attachedCtx = context
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View?
{
var view : View = inflater.inflate(R.layout.fragment_browse_file, container, false)
val modelFactory = FileBrowseModelFactory()
val data = modelFactory.create()
bindModelToView(data, view)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
private fun bindModelToView(data: BrowseSettings, view: View)
{
var adapter = MyFileAdapter(data.FoldersAndFiles, this.attachedCtx)
var recyclerView = view.findViewById<RecyclerView>(R.id.fileItemView)
var linearLayoutManager = LinearLayoutManager(this.attachedCtx)
recyclerView.layoutManager = linearLayoutManager
recyclerView.adapter = adapter
var folderLabel = view.findViewById<TextView>(R.id.folderName)
folderLabel.text = data.CurrentFolder
}
}
The segment layout
<?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"
tools:context=".BrowseFileFragment">
<TextView
android:id="#+id/folderName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="#string/hello_first_fragment"
android:textAlignment="viewStart"
android:textAppearance="#style/TextAppearance.AppCompat.Display1"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/fileItemView"
android:layout_width="match_parent"
app:layout_constrainedHeight="true"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#+id/folderName"
app:layout_constraintStart_toStartOf="#+id/folderName"
app:layout_constraintTop_toBottomOf="#+id/folderName" />
</androidx.constraintlayout.widget.ConstraintLayout>
The list line layout
<?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="60dp">
<TextView
android:background="#color/cardview_shadow_start_color"
android:id="#+id/itemName"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="parent"
android:padding="5dp"
android:textSize="30dp"
android:textStyle="bold" />
</androidx.constraintlayout.widget.ConstraintLayout>
And finally my adapter:
class MyFileAdapter(private val items: List<FileItem>, private val context: Context)
: RecyclerView.Adapter<MyFileAdapter.MyViewHolder>()
{
class MyViewHolder (itemView: View) :RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
fun bindItem(f: FileItem) {
var name: TextView = itemView.findViewById(R.id.itemName) as TextView
name.text = f.Name
}
}
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater
.from(context)
.inflate(R.layout.recyclerview_filteitem_row, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
var item = items\[position\]
holder!!.bindItem( item )
}
}
It found out that as the whole list view is saved as a segment, this segment loads "automatically" and was loaded programatically also, and the two "loads" causes this thing. :( Thanks for your time guys.
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>