I want to implement search functionality for my RecyclerView. On text changed i want to change the data that are displayed with this widget. Maybe this question has been asked before or is simple, but I don't know how the change the data that is to be shown...
My RecyclerView is defined as follows:
// 1. get a reference to recyclerView
mRecyclerView = (RecyclerView)findViewById(R.id.recyclerView);
// 2. set layoutManger
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
// 3. create an adapter
mAdapter = new ItemsAdapter(itemsData);
// 4. set adapter
mRecyclerView.setAdapter(mAdapter);
And the data that I am showing is something like:
ItemData itemsData[] = { new ItemData("Mary Richards"),
new ItemData("Tom Brown"),
new ItemData("Lucy London")
};
So when when I want to give the adapter another set of data, another array (with one item for example), what should I do?
If you have stable ids in your adapter, you can get pretty good results (animations) if you create a new array containing the filtered items and call
recyclerView.swapAdapter(newAdapter, false);
Using swapAdapter hints RecyclerView that it can re-use view holders. (vs in setAdapter, it has to recycle all views and re-create because it does not know that the new adapter has the same ViewHolder set with the old adapter).
A better approach would be finding which items are removed and calling notifyItemRemoved(index). Don't forget to actually remove the item. This will let RecyclerView run predictive animations. Assuming you have an Adapter that internally uses an ArrayList, implementation would look like this:
// adapter code
final List<ItemData> mItems = new ArrayList(); //contains your items
public void filterOut(String filter) {
final int size = mItems.size();
for(int i = size - 1; i>= 0; i--) {
if (mItems.get(i).test(filter) == false) {
mItems.remove(i);
notifyItemRemoved(i);
}
}
}
It would perform even better if you can batch notifyItemRemoved calls and use notifyItemRangeRemoved instead. It would look sth like: (not tested)
public void filterOut(String filter) {
final int size = mItems.size();
int batchCount = 0; // continuous # of items that are being removed
for(int i = size - 1; i>= 0; i--) {
if (mItems.get(i).test(filter) == false) {
mItems.remove(i);
batchCount ++;
} else if (batchCount != 0) { // dispatch batch
notifyItemRangeRemoved(i + 1, batchCount);
batchCount = 0;
}
}
// notify for remaining
if (batchCount != 0) { // dispatch remaining
notifyItemRangeRemoved(0, batchCount);
}
}
You need to extend this code to add items that were previously filtered out but now should be visible (e.g. user deletes the filter query) but I think this one should give the basic idea.
Keep in mind that, each notify item call affects the ones after it (which is why I'm traversing the list from end to avoid it). Traversing from end also helps ArrayList's remove method performance (less items to shift).
For example, if you were traversing the list from the beginning and remove the first two items.
You should either call
notifyItemRangeRemoved(0, 2); // 2 items starting from index 0
or if you dispatch them one by one
notifyItemRemoved(0);
notifyItemRemoved(0);//because after the previous one is removed, this item is at position 0
This is my answer - thanks to Ivan Skoric from his site: http://blog.lovelyhq.com/creating-lists-with-recyclerview-in-android/
I created an extra method inside my adapter class:
public void updateList(List<Data> data) {
mData = data;
notifyDataSetChanged();
}
Then each time your data changes, you just call this method passing in your new data and your view should change to reflect it.
Just re-initialize your adapter:
mAdapter = new ItemsAdapter(newItemsData);
or if you only need to remove add a few specific items rather than a whole list:
mAdapter.notifyItemInserted(position);
or
mAdapter.notifyItemRemoved(position);
If you want to change the complete Adapter in the recycler view. you can just simply set by recycler.setAdapter(myAdapter);
It will automatically remove the old adapter from recycler view and replace it with your new adapter.
As ygit answered, swapAdapter is interesting when you have to change the whole content.
But, in my FlexibleAdapter, you can update the items with updateDataSet. You can even configure the adapter to call notifyDataSetChanged or having synchronization animations (enabled by default). That, because notifyDataSetChanged kills all the animations, but it's good to have for big lists.
Please have a look at the description, demoApp and Wiki pages: https://github.com/davideas/FlexibleAdapter
Related
Below is my implementation for recycler view.
The recycler view is refreshing but the view is lagging alot making it not smooth to scroll.
If I dont refresh data the recyclerView is smooth.
The problem is with refreshing only..!!
I need to refresh data completely every 1 sec in RecyclerView maintaining same scroll position.
xml
<android.support.v7.widget.RecyclerView
android:id="#+id/recyView_disp_pgms"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#null" />
Activity :
RecyclerView recyView_disp_pgms = (RecyclerView) findViewById(R.id.recyView_disp_pgms);
DisplayProgramsAdapterRecycler pgmAdapter = new DisplayProgramsAdapterRecycler(programsList);
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
recyView_disp_pgms.setLayoutManager(layoutManager);
recyView_disp_pgms.setAdapter(pgmAdapter);
handler = new Handler();
runnable = new Runnable() {
#Override
public void run() {
System.out.println("AllPgm Runflag : " + runflag);
programList = // Get List from database
if (programsList != null && programsList.size() > 0) {
if (pgmAdapter != null && pgmAdapter.getItemCount() > 0) {
pgmAdapter.resetData(programsList);
}
}
handler.postDelayed(this, 1000);
}
};
handler.post(runnable);
Adapter :
public void resetData(ArrayList<ProgramDetails_Table> programsList) {
this.programDetailsList = programsList;
this.notifyDataSetChanged();
}
Problem - Lagging scroll of recyclerView only while refreshing data for every 1 sec
Tried following other posts; but still no luck.
Please help..!!
if you want to refresh your list find your changes and then just notify that position because notifyDataSetChanged resets all of your data.
you should use lightweight notifies.something like this:
notifyItemChanged(yourPosition);
notifyItemInserted(yourPosition);
notifyItemRemoved(yourPosition);
or if you get more than one change in a range you can use:
notifyItemRangeChanged(yourPosition);//and etc
use DiffUtil. Don't use notifyDataSetChanged.
According to this https://developer.android.com/reference/android/support/v7/util/DiffUtil.html,
DiffUtil is a utility class that can calculate the difference between
two lists and output a list of update operations that converts the
first list into the second one.
It can be used to calculate updates for a RecyclerView Adapter.
Unlike the notifyDataSetChanged method, it does not reload the whole list so it has a better performance.
And you don't have to manually find the differences between the new and the old list like the other notifyData methods from RecyclerView.Adapter.
Delete your line this.programDetailsList = programsList; and add the following. This is a simple hack, a way that makes you understand Async Callbacks.
/* I have done adding one element and then deleting the same element as the last element of programsList */
/* adding and deleting triggers notifyDataChange() callback */
//add an element in index 0 as the last element into the programList
programsList.add(programsList.size() - 1, programsList.get(0));
//remove the same last element from ProgramList
programsList.add(programsList.size() - 1);
//now it works
this.notifyDataSetChanged(programsList.size() - 1);
Make sure you are calling notifyDataSetChanged() inside the resetData function.
Also, try this instead of assigning directly to programList
programList.clear();
programList.addAll(arrayListOfDataFromServer);
I have a requirement, where I should download the ad item while scrolling and update the list. Since calling notifyDatasetChnaged(), resets everything, I'm calling notifyItemInserted(position). But, calling this duplicated the items in the list. I found that there are no repeated items in the list. But after calling notifyItemInserted, it duplicates the item. I'm not getting how to resolve this issue. This what I'm doing:
mNewsList.add(mPreviousAdPosition, newsItem);
mAdapter.notifyItemInserted(mPreviousAdPosition);
If I call, it works properly, there are no repeated items. But I don't want my list items to recreate. What can be the issue ?
I had the same problem for exactly the same use case, the solution is:
Implement this method in your Adapter :
#Override
public long getItemId(int position) {
//Return the stable ID for the item at position
return items.get(position).getId();
}
Call this method in the Constructor of your Adapter :
//Indicates whether each item in the data set can be represented with a unique identifier
setHasStableIds(true);
You can add the object at the end of the array with each object having a position along with it where it needs to be shown in the recycler view. Sort this array on the basis of position before calling notifyItemInserted(position). In this way only required data will be drawn.I have recenlty followed this approach and works very well with dynamic sections added in between in recycler view.
You should add the item at the end of the list.
mNewsList.add(newsItem);
and then notify like this.
mAdapter.notifyItemInserted(mNewsList.size()-1);
Create a temporary list and add items as mentioned below:
List<YourModel> mTmpList = new ArrayList<YourMdel>();
//add items (from 0 -> mPreviousAdPosition) to mTmpList;
for(int i=0; i<mPreviousAdPosition; i++) {
mTmpList.add(mNewsList.get(i));
}
//add item at mPreviousAdPosition
mTmpList.add(newsItem);
//add remaining items and set i<=mNewsList.size() because we ha
for(int i=mPreviousAdPosition; i<=mNewsList.size(); i++) {
mTmpList.add(mNewsList.get(i - 1)); //because we have added item at mPreviousAdPosition;
}
mNewsList = mTmpList;
mAdapter.notifyDataSetChanged();
You code should be written like this:
public class RecyclerViewAdapter extends RecyclerView.Adapter{
...
public void addData(int position, Item newsItem) {
mNewsList.add(position, newsItem);
notifyItemInserted(position);
}
...
}
and then you need to call the fun addData
I have implemented my RecyclerView and even added an onscrolllistener to support infinity scrolling and now I'm stuck with a, hopefully, easy problem: How can I add the newly loaded data to the existing dataset?
My current approach: I create a new array with the length of the existing dataset + the length of the newly loaded data. I System.arraycopy my existing dataset and add the new content with a for-loop.
This works but the list is always reset (scrolls back to the top) and I assume my way to add additional content is overly complicated/wrong, though the tutorials I have looked at seem to pass over this "detail".
Update: I'm currently calling "scrollToPosition" on the UI-Thead after the data has been loaded, but I doubt this is the correct way of doing this or am I wrong?
You shouldn't be adding stuff to your dataset, you will sooner or later run out of memory. What you can do is return a big number (I used Short.MAX_VALUE) item in getItemCount inside your adapter and in the method that requests a view for postion you should do position % list.size();
It is not a truly endless RecyclerView this way, but good enough. I will paste some code tomorrow, I don't have it here now :/
I think you have to add items inside your adapter. Let`s say
class Adapter extends Recycler.Adapter<Recycler.ViewHolder>{
List<YourCustomObject> list;
public Adapter(){
list = new ArrayList<>();
}
public void addItem(YourCustomObject item){
list.add(item);
notifyItemDateSetChanged(); //This method for adapter to notice that list size have been changed
}
// Here your views
}
There is implementation of Your fragment or Activity where you retrieve data from internet.Let` say
class MainActivity extends AppCompactActivity{
Adapter adapter = new Adapter();
List<YourCustomObjects> objects;
public void onCreateView(){
//////// Something yours
}
public void onLoadMore(){
///// Your operation to retrieve data and init it to your list objects
for(YourCustomObject object : objects){
adapter.addItem(object);
}
}
}
I'm using a ListView with a custom ArrayAdapter.
The List is an infinite scroll of tweets.
Updates to the list are inserted from the top.
I want to obtain an effect as the Twitter application. I'm not talking about the "scroll to update", but to maintain the position after the update.
I've just implemented some code that works in that way. Here it is:
// get the position of the first visible tweet.
// pausedCounter traces the number of tweets in the waiting line
final int idx = listView.getFirstVisiblePosition() + pausedCounter;
View first = listView.getChildAt(0);
int position = 0;
if (first != null)
position = first.getTop();
// here I update the listView with the new elements
for (Tweet[] tweets1 : pausedTweets)
super.updateTweets(tweets1);
final int finalPosition = position;
// this code maintain the position
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelectionFromTop(idx, finalPosition);
}
});
The problem of this code is that for an instant the listView goes to the first element of the list, then kick in the setSelectionFromTop and it goes to the correct position.
This sort of "flickering" is annoying, and I want to remove it.
I found out only this solution:
// add the new elements to the current ArrayAdapter
for (Tweet[] tweets1 : pausedTweets)
super.updateTweets(tweets1);
// create a NEW ArrayAdapter using the data of the current used ArrayAdapter
// (this is a custom constructor, creates an ArrayAdapter using the data from the passed)
TweetArrayAdapter newTweetArrayAdapter =
new TweetArrayAdapter(context, R.layout.tweet_linearlayout, (TweetArrayAdapter)listView.getAdapter());
// change the ArrayAdapter of the listView with the NEW ArrayAdapter
listView.setAdapter(newTweetArrayAdapter);
// set the position. Remember to add as offset the number of new elements inserted
listView.setSelectionFromTop(idx, position);
In this way I have no "flickering" at all!
I'm trying to implement paging in a custom ListAdapter. Right now I'm just making the request for the next page when the last item in the ListView becomes visible, by checking in getView() if position is >= the size of ListAdapter.getCount().
It works fine, but I'm wondering if there's a better way (or a different way) that will only make the request once the last item in the list is actually visible to the user. Anyone know of a way?
I'm doing it almost the same way:
public static final int SCROLLING_OFFSET = 5;
// ...
private final ArrayList<T> items = new ArrayList<T>();
// ...
if (SCROLLING_OFFSET == items.size() - position) {
if (hasNextPage()) {
addNextPage();
}
}
private boolean hasNextPage() {
// basically calculates whether the last 2 pages contained the same # of items
}
private void addNextPage() {
// show spinner
// fetch next page in a background thread
// add to items
notifyDataSetChanged();
}
I think there is a better way to do it. Implementing the OnScrollListener interface. Take a look at this: Endless Scrolling ListView
Try removing the check altogether. In my experience, getView() is only called when the entry is about to come on screen.