I am creating a simple view where on the top I have some elements and below a recyclerView. When I scroll it down, would like to scroll the whole screen, not the only recycler.
I have achieved it with NestedScrollView, however, now the problem appears. Items in the list will be pretty heavy and in this configuration, all the items are bind at the same time(call of onBindViewHolder).
any ideas how to make them recycle and solve this problem?
Here is my xml file
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/darker_gray"
android:orientation="vertical"
tools:context="com.gkuziel.testkotlin.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="#drawable/ic_available_stores_default" />
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="test text" />
<android.support.v7.widget.RecyclerView
android:id="#+id/list_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:isScrollContainer="false"
android:nestedScrollingEnabled="false">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
Update:
The found a sweet solution: you add a complex header as ItemDecoration, its great cause your adapter can stay untouched, you just add sth like this:
recyclerView.addItemDecoration(dividerItemDecoration);
the only drawback of this solution is i couldn't make this header clickable (in my case it contains another recyclerView), however I know some people achieved it as well.
For this moment I decided implement heterogeneous recyclerview, with 1 instance of header type and the rest of simple row types.
What is important, the header type is fully binded once in HeaderViewHolder constructor and onBindViewHolder looks like this:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is HeaderViewHolder) {
//do nothing
Log.d("ProductAdapter", "Binding: Header")
} else if (holder is ItemViewHolder) {
Log.d("ProductAdapter", "Binding: " + position.toString())
val searchItem = items!![position - 1]
//here the proper binding is going on
}
}
You can try setting the recyclerview layout manager's method canScrollVertical to false and it won't respond to any touch inner scroll events.
override below method and return false.
boolean canScrollVertically()
here it is how to set.
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
// Lookup the recyclerview in activity layout
RecyclerView listTest = (RecyclerView) findViewById(R.id.list_test);
// Attach the adapter to the recyclerview to populate items
listTest.setAdapter(adapter);
// Set layout manager to position the items
listTest.setLayoutManager(new LinearLayoutManager(this){
#Override
public boolean canScrollVertically(){
return false;
}
});
// That's all!
}
Related
I have scrollview inside Recycler view. I am trying to achieve swipeable behaviour like inshorts but the card should scroll to the bottom so user can see all the contents. Once scrollview bottom has been reached then swipe should work to move to next article.
I have implement swipeable behaviour using stacklayoutmanager. The problem is I am not able to scroll to the bottom.
Below is implementation
Recycler view inside main activity
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
MainActivity code, added PagerSnapHelper for fling effect.
adapter = new RecyclerViewAdapter(setData(),this);
layoutManager = new CustomLayoutManager(StackLayoutManager.ScrollOrientation.BOTTOM_TO_TOP,1, this);
layoutManager.setPagerFlingVelocity(600);
recyclerView.setLayoutManager(layoutManager);
LinearSnapHelper snapHelper = new CustomSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
recyclerView.setAdapter(adapter);
CustomLayoutManager class which extends the library for swipeable behaviour
class CustomLayoutManager(orientation: ScrollOrientation, count:Int, context: Context) : StackLayoutManager(orientation, count, context){
private var isScrollEnabled = true
fun setScrollEnabled(flag: Boolean) {
isScrollEnabled = flag
}
override fun canScrollVertically(): Boolean {
//Similarly you can customize "canScrollHorizontally()" for managing horizontal scroll
return isScrollEnabled && super.canScrollVertically()
}
}
item view inflated in adapter class
<ScrollView
android:id="#+id/scroll"
android:scrollbars="none"
android:nestedScrollingEnabled="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:text="very very long text will add here"
android:textSize="16sp" />
</ScrollView>
Here in the below image, when trying to scroll to the bottom to read the complete content but recyclerview swipe behaviour becomes active and next item becomes visible.
Update
I have replaced scrollview with nestedscrolledview and found out that
scrollVerticallyBy() method present in RecyclerView.LayoutManager() does not invoke, only scrollHorizontallyBy() is getting called n times.
I have tried so far but not working
ScrollView inside RecyclerView won't scroll
https://stackoverflow.com/a/41139693/7368819
https://stackoverflow.com/a/34060065/7368819
https://stackoverflow.com/a/10334353/7368819
This question already has answers here:
How to update RecyclerView Adapter Data
(16 answers)
Closed 3 years ago.
I'm developing a application that the RecyclerView load new data when the user hit the bottom of the scroll. However, the list is already completed and have it's fixed size, I just want to put the content in the Recycler, but when I insert the content in the Recycler, the scroll returns to the top of the list. And then I need to scroll down all over again until I reach the new data that was inserted in to the bottom of the list. All that I want is to maintain the scroll position when the new data is loaded.
I've tried use descendantFocusability = "blocksDescendants", but this didn't work.
This is my XML code where the RecyclerView can be found
<?xmlversion="1.0"encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:headerLayout="#layout/top_navigation_bar"
tools:context=".MainActivity"
android:descendantFocusability="blocksDescendants">
<RelativeLayout
android:id="#+id/productsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="50dp"
android:orientation="vertical"
android:background="#EBE7E7"
>
<TextView
android:id="#+id/productsLayoutTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:lineSpacingExtra="0sp"
android:text="#string/produtos_em_destaque"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="normal"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/productsLayoutTitle"
/>
<ProgressBar
android:id="#+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_alignBottom="#+id/recyclerView"
android:scaleX="1"
android:scaleY="1"
android:progressTint="#1C36FF"
/>
</RelativeLayout>
</RelativeLayout>
The code that is used to add data to the RecyclersView is
fun showData(products:Product,productsListSize:Int){
if(!isStarted){
recyclerView.apply{
layoutManager=LinearLayoutManager(this#MainActivity)
adapter=ViewHolderAdapter(products,productsListSize)
(adapter as ViewHolderAdapter).notifyItemRangeInserted(0,productsListSize)
pageNumber++
}
progressBar.visibility=View.GONE
isStarted=true
}else{
recyclerView.apply{
adapter=ViewHolderAdapter(products,pageNumber*limitPerPage)
(adapter as ViewHolderAdapter).notifyItemRangeInserted(((pageNumber-1)*limitPerPage),pageNumber*limitPerPage)
pageNumber++
}
progressBar.visibility=View.GONE
}
}
My ViewHolderAdapter
packagecom.example.kotlinbasics
importandroid.graphics.Color
importandroid.view.LayoutInflater
importandroid.view.View
importandroid.view.ViewGroup
importandroid.widget.ImageView
importandroid.widget.RatingBar
importandroid.widget.TextView
importandroidx.recyclerview.widget.RecyclerView
importcom.squareup.picasso.Picasso
importkotlinx.android.synthetic.main.recyclerview_layout.view.*
class ViewHolderAdapter(private val products:Product,private val productsListSize:Int):RecyclerView.Adapter<ViewHolderAdapter.ViewHolder>(){
override fun onCreateViewHolder(parent:ViewGroup,viewType:Int):ViewHolder{
val view=LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_layout,parent,false)
return ViewHolder(view)
}
override fun getItemCount()=productsListSize
override fun onBindViewHolder(holder:ViewHolder,position:Int){
holder.productName.text=products.produtos[position].nome
Picasso.get().load(products.produtos[position].img).into(holder.productImage)
}
class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val productName:TextView=itemView.ProductName
val productImage:ImageView=itemView.ProductImage
}
}
All application works really fine, the only problem is with that scroll behavior.
I expected that the scroll position didn't goes to the top of the RecyclersView list when I add new data in him.
As you are creating a new adapter each time, the RecyclerView will be always going back to the start position. You have to update the current adapter, and not to create another adapter with the entire list.
You should have a method on your adapter to manage the item list, such as
fun updateList(product: Product) {
myList.add(product.list)
}
And in your else branch you will need to update the list
recyclerView.apply{
(adapter as? ViewHolderAdapter)?.updateList(products)
}
just use code bellow, and input requared position (in your case - last position)
recyclerView.scrollToPosition()
Create one instance of your recycler view adapter , the pass the orignal list to it . whenever you want to add the any data , add it to the list , and then notify the adapter.
In your case , Just add the product list and notify it
private list<product> list = newArrayList<>();
adapter= new ViewHolderAdapter(list);
OnClick of button when you want to add data , just insert it into the list
list.addAll(productlist);
And now notify it to the adapter
adapter.notifyDataSetChange();
In your case
fun showData(products:Product,productsListSize:Int){
if(!isStarted){
list.addAll(product)
adapter.notifyDataSetChange();
pageNumber++
}
progressBar.visibility=View.GONE
isStarted=true
}
progressBar.visibility=View.GONE
}
}
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.
Below is my BindableItem Class which also act as an adapter while using Groupie
class FilterByAthleteTypeItem(var athleteResponse: AthleteModel, var onFilterAthleteItemClick: OnFilterAthleteItemClick) : BindableItem<FilterItemLayoutBinding>() {
override fun bind(viewBinding: FilterItemLayoutBinding, position: Int) {
ViewHolder(viewBinding, position)
viewBinding.executePendingBindings()
viewBinding.notifyChange()
}
override fun getLayout(): Int {
return R.layout.filter_item_layout
}
inner class ViewHolder(var binding: FilterItemLayoutBinding, position: Int) : RecyclerView.ViewHolder(binding.root), View.OnClickListener {
override fun onClick(p0: View?) {
athleteResponse.isChecked = binding.playlistSwitch.isChecked
onFilterAthleteItemClick.onFilterAthleteClicked(athleteResponse)
notifyChanged()
}
init {
val athleteModel = athleteResponse
binding.totalItems.text = athleteModel.areelCount.toString()
binding.playlistSwitch.isChecked = athleteModel.isChecked
binding.sportName.text = athleteModel.athleteType
binding.playlistSwitch.setOnClickListener(this)
when {
athleteModel.athleteType == "highschool" -> binding.playerLevelImage.setBackgroundColor(
ContextCompat.getColor(binding.root.context, R.color.black))
athleteModel.athleteType == "college" -> binding.playerLevelImage.setBackgroundColor(
ContextCompat.getColor(binding.root.context, R.color
.college))
athleteModel.athleteType == "pro" -> binding.playerLevelImage.setBackgroundColor(
ContextCompat.getColor(binding.root.context, R.color.pro))
athleteModel.athleteType == "enthusiast" -> binding.playerLevelImage.setBackgroundColor(
ContextCompat.getColor(binding.root.context,
R.color.enthusiast))
athleteModel.athleteType == "military" -> binding.playerLevelImage.setBackgroundColor(
ContextCompat.getColor(binding.root.context,
R.color.text_color_9b))
else -> binding.playerLevelImage.setBackgroundColor(
ContextCompat.getColor(binding.root.context,
R.color.white))
}
}
}
}
interface OnFilterAthleteItemClick {
fun onFilterAthleteClicked(athleteModel: AthleteModel)
}
Here is how I used it in MyActivity
Section section = new Section();
section.setHeader(headerItemGroupie);
if (!Utils.isNull(athleteModelList))
for (int i = 0; i < athleteModelList.size(); i++) {
AthleteModel athleteModel = athleteModelList.get(i);
athleteModel.setPosition(i);
athleteModelList.remove(i);
athleteModelList.add(i, athleteModel);
section.add(new FilterByAthleteTypeItem(athleteModelList.get(i), this));
}
groupAdapter.add(section);
Below is my layout Item file
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:orientation="horizontal">
<com.areel.android.customview.CustomTextView
android:id="#+id/totalItems"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="28dp"
android:layout_weight="1.7"
android:paddingBottom="18dp"
android:paddingTop="18dp"
android:textColor="#color/text_color_9b"
android:textSize="12sp"
app:fontPath="#string/font_avenir_heavy"
app:letterSpacing="0.154"
tools:text="14,932"/>
<com.areel.android.customview.CustomTextView
android:id="#+id/sportName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="end"
android:textAllCaps="true"
android:textColor="#color/black"
android:textSize="12sp"
app:fontPath="#string/font_avenir_heavy"
app:letterSpacing="0.3"
tools:text="NAME OF SPORT"/>
<FrameLayout
android:id="#+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.8">
<ImageView
android:id="#+id/playerLevelImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="21dp"
android:scaleType="fitXY"/>
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/playlistSwitch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:button="#drawable/switch_on_off"/>
</FrameLayout>
</LinearLayout>
and here is my recyclerView in layout
<android.support.v7.widget.RecyclerView
android:id="#+id/filter_list_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/showFriendLayout"
android:clipChildren="false"
android:clipToPadding="false">
</android.support.v7.widget.RecyclerView>
Now when I scroll the recyclerView the background color and Image start shuffling means respective positions of background color doesn't remain appropriate while scrolling
the major problem is the last item has an image as a background, and when I scroll that Image overlaps other backgrounds and shuffle happened
But there is now way to change that image to color so I need more robust solution!!
I am adding the screen shots here
First one, What I have done
And I check items and Scrolls the recyclerview then that last image shuffles its position like below image
The FIfth Image also shuffles on Down side
Author of the library here. You have several problems in your posted code.
For starters, you shouldn't be calling executePendingBindings or notifyChange. You aren't using the feature of data binding where you bind a model object, so both of those are unnecessary.
Second, the whole point of Groupie (especially with data binding) is that you shouldn't have to create your own ViewHolder. In fact, yours isn't doing anything. You can move all the code from your ViewHolder class into FilterByAthleteTypeItem.bind().
Last, your problem with images loading in the wrong spots, or duplicated, is a very common issue in RecyclerViews. You didn't post your image loading code, but I'm guessing it's asynchronously loading images -- meaning they load into a certain view, regardless of whether it's been recycled -- and/or failing to clear the old image from reused viewholders. I recommend you avoid dealing with this problem entirely by using an image loading library like Picasso or Glide.
Hope that helps!
I have a recyclerview inside SwipeRefreshLayout. and at the end of recyclerview, I'd like to add a button, but I'm unable to do so. Here the code :
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/refresh"
android:layout_width="wrap_content"
android:layout_below="#+id/toolbar"
android:paddingBottom="50dp"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"/>
<Button
android:layout_width="match_parent"
android:layout_height="50dp"
android:id="#+id/load_more"
android:layout_alignParentBottom="true"
android:textColor="#color/md_black_1000"
android:text="TEST"/>
</android.support.v4.widget.SwipeRefreshLayout>
The button does not appear at the end of the recyclerview.
SwipeRefreshLayout could have only one child.
Add RecyclerView and Button to vertical LinearLayout and put it into SwipeRefreshLayout.
<android.support.v4.widget.SwipeRefreshLayout>
<LinearLayout>
<android.support.v7.widget.RecyclerView>
<Button>
</LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>
To add Button as last item in RecyclerView you need edit adapter:
1) increase number of rows
#Override
public int getItemCount() {
return pictureArrayList.size() + 1;
}
2) show Button when list ends
#Override
public void onBindViewHolder(final ExampleHolder holder, final int position) {
final Picture picture = pictureArrayList.get(position);
if (position <= pictureArrayList.size()) {
holder.title.setVisibility(View.VISIBLE);
holder.button.setVisibility(View.GONE);
holder.title.setText(picture.getName());
holder.imageView.setImageResource(picture.getImage());
} else {
holder.title.setVisibility(View.GONE);
holder.button.setVisibility(View.VISIBLE);
}
}
You need to create a RecyclerView with different view items depending on what you want to do may be an option to use a view button at the bottom of the list, I have a blog post where I wrote a blog about the RecyclerView(Spanish).
http://erikcaffrey.github.io/2015/10/05/recyclerview/
You can see the code!
https://github.com/erikcaffrey/RecyclerView-Examples