LayoutManager.startSmoothScroll scrolls to wrong position - android

I have a RecyclerView of goods with categories and HorizontalScrollView of good's categories above it. I am trying to implement the following behavior: when you click on category in scroll view, recycler scrolls to first good with this category.
To do it, I find first good with selected category in LiveData corresponding to recycler's content, then I find index of that good and begin to scroll recycler:
viewModel.stateLiveData.observe(viewLifecycleOwner) { state ->
val category = state.category
if (category.isNotEmpty()) {
val smoothScroller = LinearSmoothScroller(context)
val itemPositionWithCategory = viewmodel.getItemPositionWithCategory()
smoothScroller.targetPosition = itemPositionWithCategory
(binding.deliveryGoodsRecycler.layoutManager as LinearLayoutManager).startSmoothScroll(smoothScroller)
}
}
Smooth scroll works strange. By default first category is checked and list is in 0 position. If I click on for instance seventh category, when first good with this category is found correctly and list is scrolled to that good normally.
Problems appear with scrolling "backward". If I check sixth category, when seventh one is selected, the scroll works fine and scrolls to first good with sixth category. But I check fifth category, when seventh one is selected, the recycler scrolls to zero position for some reason. This behavior appears every time, when recycler is scrolled "backward" with none zero gap between categories (scroll to previous category works correct).
I made debug for backward scroll, category is not changing during scroll, index of the first good with target category is correct and is not changing during scroll. I logged index in recycler's OnScrollListener, in function onScrolled. When recycler is scrolled to previous category, this function is called much less times, than recycler is scrolled to top. In both cases index is not changing while scroll.
I have no idea, why smooth scroll works wrong in particular cases. Hope someone, who knows recycler good or had similar problems, could help me.
Update
I am adding part of my layout to make situation clear.
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/main_delivery_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<HorizontalScrollView
android:id="#+id/chips_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="#+id/chip_categories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true" />
</HorizontalScrollView>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/delivery_goods_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
Categories, which control position of recycler, are stored in chip_categories (there is no chips in layout, they are loaded from server and are added programmatically). chips_scroll_view allows to see all categories as they are not fit to screen width.
When I check category (chip) in chip_categories, selected category in state updates, this event is observed in fragment, then index of first good with selected category is found and delivery_goods_recycler is scrolled to that index. Bug appears when I check not previous category (one or more categories between them in left direction in chip_categories) and delivery_goods_recycler is scrolled to the top instead of found index.

As I understood you are scrolling a layout, you can scroll your Container(HorizontalScrollView) instead of using Layout Manager, HorizontalScrollView can contain your RecycleView inside the HorzintalScrollview.

Related

Loading items into a RecyclerView with stackFromEnd = true doesn't scroll to bottom

I have a chat app and i'm trying to ensure that when the user opens the screen the items display from the bottom with the most recent right above the input area. I've used a custom view for the input area and both the recycler and the custom view are within a ConstraintLayout.
My problem is that when I load items into the list, if the number of items is greater than the size of the recycler, it will not fully show the last item. When I make the input area visibility = Gone then the items display properly at the bottom. It's almost like the recyclers LinearLayoutManager thinks that the height of the Recycler is of the screen without the input field. I've manually printed out the size of the views and used layout inspector to ensure that the recycler is indeed drawn in the correct location (above the input and below the navigation bar).
What could be causing such an issue? I should note that whenever you click on a Linkified text in a chat bubble that the list scrolls a small amount equal to the offset that's incorrect when you open the screen. Clearly something is not measuring right here and not sure where to begin.
I should also note that if I try to add a post with smoothScroll it will go to the end of the list but then whenever a new item appears in the list from sending a message the items above the most recently added one seem to jump up a little with an unnecessary animation. It's like the last item in the list is in some special state?
if you're curious this is my scrolling function:
private fun scrollToFirstItem(dueToKeyboard: Boolean = false) {
val stackingFromEnd = (recyclerView.layoutManager as LinearLayoutManager).stackFromEnd
if (stackingFromEnd) {
val firstPosition = recyclerView.adapter?.itemCount?: 0 - 1
if (dueToKeyboard ) {
recyclerView.scrollBy(0, Integer.MAX_VALUE)
} else {
recyclerView.scrollToPosition(firstPosition)
}
recyclerView.post { recyclerView.smoothScrollToPosition(firstPosition) }
} else {
recyclerView.scrollToPosition(0)
}
}
And my xml for my fragment:
<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">
<include
android:id="#+id/searchView"
layout="#layout/compose_new_message_include"/>
<com.airbnb.epoxy.EpoxyRecyclerView
android:id="#+id/conversationEpoxyRV"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scrollbars="vertical"
app:layout_constraintTop_toBottomOf="#id/searchView"
app:layout_constraintBottom_toTopOf="#id/composeView"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
tools:listitem="#layout/conversation_item_inbound"/>
<include layout="#layout/conversation_pip_view"
android:id="#+id/selectedMediaContainer"/>
<****.ComposeView
android:id="#+id/composeView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Any help is appreciated, I'm so lost..
To me this feels like more of an issue in the layout rather than in the scrolling function. I hope that this can be resolved easily if you use a Relative Layout instead of a Linear Layout. So in case if it may be helpful, i'll add my solution below using a Relative Layout.
<RelativeLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/input_field">
</androidx.recyclerview.widget.RecyclerView>
<EditText
android:id="#+id/input_field"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
So in case if it is not clear what i have done, an EditText element is set to align to the bottom of parent which is a Relative Layout. Then a RecyclerView is added so that RecylerView will be always constraint above the EditText but not overlapped.
Everything looks fine, except the restrictions you added to recycleview design, I see that you are setting the recycleview to top of searchView app:layout_constraintTop_toBottomOf="#id/searchView" while the searchView view is not restricted as should it be.
It's better when using ConstraintLayout to restrict every view inorder avoid unexpected behaviors, because every view has a relation with other view will be effected with other view (x, y), therefore your searchView should be look like:
<include
android:id="#+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
layout="#layout/compose_new_message_include"/>
Give the size of arraylist of your data to smoothScrollToPosition
var linearLayoutManager = LinearLayoutManager(context)
recyclerView.layoutManager=linearLayoutManager
recyclerView.adapter=adapter
linearLayoutManager.setStackFromEnd(true)
adapter.notifyDataSetChanged()
recyclerView.smoothScrollToPosition(dataMsgs.size())
I had a similar problem some years back and I solved it with scrollToPositionWithOffset (support library). Of course this was before constraint was used....
I kept my newer items at the bottom, too. The code I used for scrolling after inserting an item was:
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(getItemCount() - 1, 0);
To scroll and adjust the awkward positioning after removing an item, I used:
View vTopCard = layoutManager.getChildAt(0);
// Get its adapter position
final int topCardPos = layoutManager.getPosition(vTopCard);
// Get top of card position
int topOffset = layoutManager.getDecoratedTop(vTopCard);
int cardMargin = (int) getResources().getDimension(R.dimen.card_vertical_margin);
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(topCardPos, topOffset - cardMargin);
The getDecoratedTop helps with the "bounce" and final positioning, as does factoring the vertical margin.
I hope this helps (at least part of your issue)! Like I said, this is old code (when I was learning to program Android and Java at the same time), so if I left something out, let me know and I'll reexamine the app's code in more detail (though, I'll have to find the time).
Good luck!

How to create an empty space after last element in a RecyclerView?

I am using RecyclerView from support.v7 library to display items on shopping list.
When I'm scrolling to the end of my data, I can't see the price in the right side of the last item.
I want to add some space after scrolling to the last item, so that the button don't cover it.
Here is the image of the App with RecyclerView and FAB:
I can simply add an empty object to the list for an Adapter, but in this case it will behave like the rest of rows (it will have checkbox, and price 00.00 on the right side).
Is there another way to do that?
You can add bottomPadding and disable clipping, such as:
<RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
<!-- Bottom Padding, or space below last item -->
android:paddingBottom="10dp"
<!-- Clipping diabled -->
android:clipToPadding="false" />

Avoid scrolling of first row and first column in recyclerview gridlayoutmanager

Created a two way scrollable gridlayout for a TV app
view structure from layout file
<?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:background="#021257"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/rvThumbnail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#021257"
android:orientation="vertical"/>
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
so i have used a gridlayoutmanager(orientation vertical) and spancount being 49 (24hrs *2)+1 for images displaying column. The first row is to display timeline divided into half hour slots and first column is to display channels, each channel will have its own programs running under different time slots. Now that i achieved to scroll the gridlayout both ways, now i have two more things to do.
1) When scrolled horizontally, the channels column(first column) also gets scrolled and hence gets hidden(it has to scroll vertically though, since there can be 20+ channels). Now i need to make this column static wen scrolling horizontally and rest other columns must scroll normally
2) When scrolled vertically, the timeline row (first row) also gets scrolled and hence gets hidden(it has to scroll horizontally though, since the row has to display 24 hrs). Now i need to make this row static wen scrolling vertically and rest other rows must scroll normally.
Is this possible to achieve ? I appreciate your help
The Feasible approach will be to go with three recycle view
a)Vertical recycle view for channel listing
b)Horizontal recycle view for Timing listing
c)Recycle view with grid layout manager for the data
Use scrollChange listener of grid recycle view(Recycle view used for data)
/**
* Use scroll values
*/
public void setScroll() {
mDataRecycleView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
//you can now play on scroll value of this recycle view and change the scroll value of other recycle views.
// you can get your view here and also know the value of view so based on value you can handle it here so it's very easy.
}
});
}
Try and let me know if this approach works for you and incase any query then comment below so we can solved your problem.
Try TableView project from Github.
TableView is a powerful Android library for displaying complex data
structures and rendering tabular data composed of rows, columns and
cells. TableView relies on a separate model object to hold and
represent the data it displays. This repository also contains a sample
app that is designed to show you how to create your own TableView in
your application.
Get concept from this project and modify what you want.
Hope this explanation helps you :)

Move layout up when layout above shrinks in height

Hi I have a fragment with two RecyclerViews in it, one above the other.
The first is a list of items for the user to take an action on and the second is where the items will be populated once the action is taken. So when an item is removed from the top list it is added to the bottom list.
The issue I am having is when I remove an item from the top RecyclerView all the remaining items in the top RecyclerView move up to fill in the space left by the removed item, but this leaves a gap between the top and bottom RecyclerViews.
How can I move the bottom recyclerview up to fill in the gap created once an item is removed from the top RecyclerView
Here is my layout xml
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:layout_alignParentTop="true"
android:id="#+id/pending_tasks"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v7.widget.RecyclerView
android:layout_below="#id/pending_tasks"
android:id="#+id/completed_tasks"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
I have tried calling invalidate() on the bottom RecyclerView but that has not worked. Any help would be appreciated, thanks!
It's impossible to update layout size once it's been displayed on the screen. If you modify the content of your RecyclerView you can't just simply refresh the layout to shrink it.
I would suggest using single RecyclerView and storing lower bound of the first list in some variable. Then simply update it accordingly to modifications in your first list.
Another solution which may help you:
Change Relative layout width and height dynamically

Android ListFragment, new items appear at bottom of list

I'm new to Eclipse and Android app development. Been trying to solve this issue for two days now but with no luck. Surfed the net and documentation for... many hours. :S
I have a ListFragment and a Button enclosed in RelativeLayout. The button is located below the list and the list is intially empty at app startup which is what i want. I'm using an ArrayAdapter and my items (String objects for now) are inside an ArrayList.
When I click the button, the list is updated with the new item and everything works fine except that new items show up at the bottom of the visible list, just above the button. If i click once more on my button, a new item shows up at the bottom of the list, and the first item is moved above the second item. Why is that and how do i fix it? I want the first item to show up at the top of the list, second item below that item, third one below the second one and so on.
What i do when i add an item is simply adding an item to beginning of my list of objects and then call adapter.notifyDataSetChanged().
How do i specify that new items in my empty ListFragment list should appear at the very top of the list?
Been playing around with different TRANSCRIPT_MODE but that didn't help. The solution is probably trivial... but i just can't find the solution. Help!
If you want to add items to the top of your ListView, you need to insert the items at the top of your items-list.
List items = new ArrayList();
for(Object obj : objectList) { // objectlist is a list of new items
items.add(0, obj); // INSERT AT TOP
listAdapter.notifyDataSetChanged();
}
I'll answer my own question here.
Change of
android:layout_height="wrap_content"
to
android:layout_height="match_parent"
in the fragment XML tag fixed it. What I still don't understand is why wrap_content makes list items show up at bottom of the screen first. Maybe some bug with how wrap_content works with initially empty lists when used in a ListFragment?
To make it more clear.
items.add(obj);
This will populate my initial empty list when i click the button repeatedly.
items.add(0, obj);
This will do the same, only the internal order of the items are reversed.
In BOTH cases, the first visible item in the app when it is running, shows up above the Add Button (located at the bottom of screen). I want the first item to show up just below the ActionBar. The ListFragment is inside a relative layout and is defined like this
<!-- this file is included in both single-pane and two-pane versions of the layout-->
<!-- i use merge to avoid code duplication -->
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<Button
android:id="#+id/button_add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:onClick="onClick"
android:text="#string/button_add"/>
<fragment
android:id="#+id/item_list"
android:name="com.example.test.ItemListFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#id/button_add"
tools:context=".ItemListActivity"
tools:layout="#android:layout/list_content"/>
</merge>

Categories

Resources