Get RecyclerView child Views - android

I am trying to get all the Views of RecyclerView items but it seems I can get only the first one. I am fetching the Views like this:
LinearLayoutManager manager = (LinearLayoutManager) rv.getLayoutManager();
List<View> list = new ArrayList<View>();
for(int i = 0; i < manager.getItemCount(); i ++ ) {
manager.scrollToPosition(i);
View view = manager.findViewByPosition(i);
if(view == null) {
Log.i("Null Check", "Null: " + i);
}
list.add(view);
}
The problem is that I fetch the first View in RecyclerView all the time. If I have 10 items, first item will be fetched and other 9 items will be logged as null. It's like scrollToPosition(i) is not working correctly.
This is scrollToPosition(i) javadoc:
Scroll the RecyclerView to make the position visible. RecyclerView
will scroll the minimum amount that is necessary to make the target
position visible. If you are looking for a similar behavior to
android.widget.ListView.setSelection(int) or
android.widget.ListView.setSelectionFromTop(int, int), use
scrollToPositionWithOffset(int, int). Note that scroll position change
will not be reflected until the next layout call.
I am worried because of this last sentence. What do you think how can I solve this?

Retrieve RecyclerView children through the assigned RecyclerView.Adapter not the layout manager. The layout manager assists in laying out children views (ie. measuring and layout) but you should be consulting the Adapter assigned to manage the RecyclerView's data and not its layout manager.

Related

why recycler view items are not rearranging?

I have a recycler view and there are items in it. Now I am using bubble sorting to rearrange them. But adapter.notifyItemMoved doesn't work properly in the loop (it is slow). Please look at the code below and help, I have added comments to make u understand. One more thing, I can't sort the list in beginning before adapting. So please don't put that as a solution. I know that this is a solution but that will not work in my case.
//rv_all is a recycler view
for (i in 0 until rv_all.childCount)
{
for (j in 0 until rv_all.childCount - 1)
{
/* i have added tag while binding in ViewHolder
itemView.stdate.tag = "date" */
val date1 =
sdf.parse(rv_all.getChildAt(j).findViewWithTag < TextView("date").text.toString())
val date2 =
sdf.parse(rv_all.getChildAt(j + 1).findViewWithTag < TextView("date").text.toString())
if (date1 != null && date2 != null)
{
if (date1.before(date2))
{
//alllist is a list used to adapt to recycler view
Collections.swap(alllist, j, j + 1)
alladapter.notifyItemMoved(j, j + 1)
// problem is here ,it is working fine when not in a loop but in loop it is doing nothing
// i have debugged and used breakpoints and what i saw that alllist is swapping elements but adapter child are not rearranging
}
}
}
}
you are iterating through childrens of RecyclerView (why twice? you arent using i anywhere, this is unefficient)
for (i in 0 until rv_all.childCount) {
for (j in 0 until rv_all.childCount - 1) {
but these are only Views - first child/View is at position 0, second 1 etc. When you scroll a bit down and your RecyclerView first visible item is e.g. 10th in alllist, then still first visible View is at position 0, like always
thus these lines makes no sense:
Collections.swap(alllist,j,j+1)
alladapter.notifyItemMoved(j,j+1)
they always swapping and notifying items at the beggining of array, starting 0, but your RecyclerView can be scrolled a bit down to e.g. 10th item - then above lines are swapping items in alllist, but notifyItemMoved does nothing as RecyclerView doesn't have to redraw first items, they are "scrolled out"
so in short: position of View drawn in RecyclerView != position in data array
you can add "real_position" tag in adapter to every child, then you can still iterate through visible childs/Views, obtain Views with findViewByTag, but swap and notifyItemMoved for positions in data array ("real_position" obtained from tag), not visible childs positions in parent RecyclerView
var realPosition : Integer = rv_all.getChildAt(j).tag as Integer // set in adapter
Collections.swap(alllist, realPosition, realPosition+1)
alladapter.notifyItemMoved(realPosition, realPosition+1)
You can try using notifyDataSetChanged():
class MovieAdapter(...) : RecyclerView.Adapter<MovieViewHolder>() {
var data: List<Movies> = list
set(value) {
field = value
notifyDataSetChanged()
}
...
For a more effient way of refreshing use DiffUtil.ItemCallback<...>(). See the Docs

RecyclerView scrolls to bottom when data is loaded

I have some weird issue with RecyclerView since I changed my app to load the data from Room database.
I have a screen (a Fragment) which displays a Profile for a user. Basically it's a GridLayout with the first item (row) displaying some info about the user and then for each row I'm displaying three images.
The code for the RecyclerView is :
private void initializeRecyclerView() {
if (listAdapter == null) {
listAdapter = new PhotosListAdapter(getActivity(), userProfileAccountId, false);
}
rvPhotosList.setNestedScrollingEnabled(true);
rvPhotosList.getItemAnimator().setChangeDuration(0);
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), ProfileFragment.GRID_COLUMNS,
LinearLayoutManager.VERTICAL, false);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
// 3 spans for Header, 1 for photos
return listAdapter.isHeader(position) ? 3 : 1;
}
});
rvPhotosList.setLayoutManager(layoutManager);
int spacingInPixels = 1dp;
rvPhotosList.addItemDecoration(new PaddingItemDecoration(GRID_COLUMNS, spacingInPixels, true));
rvPhotosList.setAdapter(listAdapter);
}
and this is how I load data :
compositeDisposable.add(
repository.getAccount(accountId)
.doOnNext(account -> {
if (account != null && view != null) {
view.showCover(account.getCover());
view.showAccount(account);
}
})
.flatMap(s -> repository.getUserPosts(accountId))
.subscribe(posts -> {
if (view != null) {
view.showPosts(posts);
}
}, throwable -> {}));
Both calls return a Flowable, either a Flowable<Account> or a Flowable<Post> where Account and Post are Room's Entity classes.
The two methods showAccount() and showPosts() pass the previously mentioned entity classes to the adapter.
The problem is this : the moment the data are loaded it scrolls to the bottom of the screen (which is also the bottom of the RecyclerView).
I'm not sure why this happens. Is it because of Room's Flowable type?Cause previously when I was fetching the data from network and passing them to the adapter I didn't have this problem.
Edit : I'm using version 25.3.1 for RecyclerView
Edit 2 : This is how I'm updating my adapter class with Account and Post data :
public void addPhotos(List<Post> posts) {
dataset.addAll(posts);
notifyDataSetChanged();
}
public void addProfileItem(Account account) {
this.account = account;
notifyItemInserted(0);
}
Did you try this? Setting descendantFocusability property of parent RecyclerView to blocksDescendants.
What this will do is, it will no more focus on your dynamically loaded child views inside recycler view, as a result, the automatic scroll will not take place.
android:descendantFocusability="blocksDescendants"
Note: The property descendantFocusability can be used with other views as well like FrameLayout, RelativeLayout, etc. So if you set this property to your parent/root layout, it will no more scroll to bottom.
If you have some initial items, that should be at the bottom after new items loaded, then you need to give them new ids, if you don't want to recycler scrolls to the bottom.
It happens because the recycler remember that this items was visible to the user before update and after update he restore his state so that initial items at the bottom will be displayed
In my case this line was culprit,
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, SPAN_COUNT, GridLayoutManager.VERTICAL, true);
Specifically , last parameter which is setting reverseLayout attribute to true. Make it false and RecyclerView doesn't scroll to bottom of screen.

Sync al horizontal RecyclerView inside vertical list view

Hi I have vertical list (Custom ArrayAdapter) where each element has a horizontal RecyclerView, to give me a table like format.
What I want to do is, if one row is scrolled horizontally, rest rows too should be. I tried putting them just inside horizontal scroll view and various tricks available, but got no luck.
If possible, I would also like this synchronization keeping first element fixed.
Make an instance of RecyclerView.OnScrollListener. This instance will be common to all your horizontal recycler views. When onScrolled( ) gets called, scroll all the views except the one on which this got called.
Adding more to Little Child's answer, it was trickier than expected, I had to create a custom listener that was in my main Activity that was called by onScroll. This is because onScroll was in a getView method and did not get access to other views in listview, but my mainactivty could access them.
In my listadapter, I added
public interface RecycleListener {
public void onScroll(int scroll_x);
}
Then in getView
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
listener.onScroll(recyclerView.computeHorizontalScrollOffset());
}
});
and finally in MainActivity
dayAdapter.setRecycleListener(new DayAdapter.RecycleListener() {
int busy = 0;
#Override
public void onScroll(int scroll_x)
{
if(busy == 1) return;
busy = 1;
for(int i = 0;i<days.size();i++)
{
View view = lv.getChildAt(i);
if(view == null) continue;
RecyclerView r = (RecyclerView) view.findViewById(R.id.timetable_row_recycler);
LinearLayoutManager layoutManager = ( LinearLayoutManager) r.getLayoutManager();
layoutManager.scrollToPositionWithOffset(0,0-scroll_x);
}
busy = 0;
}
});
Note I found out that you can easily get scroll position by using int scroll_x = recyclerView.computeHorizontalScrollOffset() however there is no direct method to appy it back. The method layoutManager.scrollToPosition(i) scrolls to ith element and not the scroll position, so the only correct possible way is
layoutManager.scrollToPositionWithOffset(0,0-scroll_x);
or (in case of custom layoutmanager)
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(0,0-scroll_x);
We basically apply negative offset to the 0th position, so instead of scrolling to offset before 0th, it scrolls to our desired scroll_x.
UPDATE
this worked but caused a lot of lag, so later I ended up switching to android.widget.TableLayout, and inserted rows (android.widget.TableRow) like I was trying with recyclerview.

update adapter time gridview scroll automatically on top

I am integrating endless grid view. end less is working perfect. but on scroll while update my adapter data at that time grid view goes to top. and i need that it should be maintain it's current position where it is.
For the feel adapter in grid view i used below code.
myDataAdapter = new ProductGridAdapter(activity, results, null);
gridView.setAdapter(myDataAdapter);
gridView.invalidate();
myDataAdapter.notifyDataSetChanged();
Can any one tell me that how can i resolve this issue?
Have a variable that captures the top element of your grid view that is being displayed. Then whenever you invalidate the view, use setSelection() function. http://developer.android.com/reference/android/widget/ListView.html#setSelection%28int%29
I got the way. in first time i add adapter in list view and after that i just update the adapter.
if (myDataAdapter == null)
{
myDataAdapter = new ProductGridAdapter(activity, results, null);
gridView.setAdapter(myDataAdapter);
}
else
{
myDataAdapter.updateData(results); //update adapter's data
myDataAdapter.notifyDataSetChanged();
}

Synchronize two ListView positions when you scroll from both list views Android

I have two ListViews. Is there any way to synchronize the position of ListViews when I scroll both the Lists
Use the following method to get the scroll position of your first listView-
private void saveListScrollPosition()
{
// save index and top position
index = _listview1.getFirstVisiblePosition();
View view = _listview1.getChildAt(0);
top = (view == null) ? 0 : view.getTop();
}
And scroll the second listView to that position with-
// restore
_listview2.setSelectionFromTop(index, top);
You could use this in your second list view: smoothScrollToPosition(position)
And in your first ListView you could use a OnScrollListener and check the first visible item with getFirstVisiblePosition.
Best wishes,
Tim

Categories

Resources