I have an app which has endless recyclerView (when certain offset to the end of list is reached, I call interface method from recyclerView adapter).
RecyclerView Adapter
public SearchResultAdapter(ArrayList<String> suggestions) {
this.suggestions = suggestions;
}
public SearchResultAdapter(ArrayList<Word> results, RecyclerView recyclerView) {
words = results;
this.recyclerView = recyclerView;
final LinearLayoutManager mLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) //check for scroll down
{
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if ((visibleItemCount + pastVisiblesItems + 14) >= totalItemCount) {
if (mOnLoadMoreListener != null)
mOnLoadMoreListener.onLoadMore();
loading = false;
}
}
}
}
});
}
public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
this.mOnLoadMoreListener = mOnLoadMoreListener;
}
public void setOnSearchResultClickListener(OnSearchResultClickListener mClickListener) {
this.mClickListener = mClickListener;
}
public void setLoading() {
loading = true;
}
@Override
public int getItemViewType(int position) {
if(words !=null)
return words.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
else
return VIEW_TYPE_SUGGESTION;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
if (suggestions != null) {
View view = inflater.inflate(R.layout.activity_suggestion_item, parent, false);
return new SuggestionsViewHolder(view);
}
if (words != null) {
// Inflate the custom layout
if (viewType == VIEW_TYPE_ITEM) {
View view = inflater.inflate(R.layout.activity_main_search_result_item, parent, false);
return new WordViewHolder(view);
}
//inflate loading
else if (viewType == VIEW_TYPE_LOADING) {
View view = inflater.inflate(R.layout.activity_main_search_result_loading, parent, false);
return new LoadingViewHolder(view);
}
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
Word word=null;
if(words !=null)
word = words.get(position);
if (viewHolder instanceof LoadingViewHolder) {
LoadingViewHolder holder = (LoadingViewHolder) viewHolder;
holder.progressBar.setIndeterminate(true);
} else if (viewHolder instanceof WordViewHolder) {
WordViewHolder holder = (WordViewHolder) viewHolder;
holder.wordTextView.setText(word.getTitle());
holder.sourceTextView.setText(word.getSource().getTitle());
} else if (viewHolder instanceof SuggestionsViewHolder){
SuggestionsViewHolder holder = (SuggestionsViewHolder) viewHolder;
holder.wordTextView.setText(suggestions.get(position));
}
//wordTextView.loadDataWithBaseURL(null,result.toString(), "text/html", "utf-8", null);
}
@Override
public int getItemCount() {
return words == null ? (suggestions ==null?0:suggestions.size()) : words.size();
}
public static class LoadingViewHolder extends RecyclerView.ViewHolder {
public ProgressBar progressBar;
public LoadingViewHolder(View itemView) {
super(itemView);
progressBar = (ProgressBar) itemView.findViewById(R.id.search_item_progressbar);
}
}
public class WordViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
// public TextView titleTextView;
private CustomTextView wordTextView;
private CustomTextView sourceTextView;
private CardView searchResultItem;
public WordViewHolder(View itemView) {
super(itemView);
searchResultItem = (CardView) itemView.findViewById(R.id.search_result_item_cardview);
wordTextView = (CustomTextView) itemView.findViewById(R.id.search_result_item_body);
sourceTextView = (CustomTextView) itemView.findViewById(R.id.search_result_source);
searchResultItem.setOnClickListener(this);
}
@Override
public void onClick(View view) {
mClickListener.onSearchResultClick(getAdapterPosition());
}
}
public class SuggestionsViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
// public TextView titleTextView;
private CustomTextView wordTextView;
private CardView suggestionItem;
public SuggestionsViewHolder(View itemView) {
super(itemView);
suggestionItem = (CardView) itemView.findViewById(R.id.suggestion_item_cardview);
wordTextView = (CustomTextView) itemView.findViewById(R.id.activity_suggestion_title);
wordTextView.setTypeface(MainActivity.custom_font);
suggestionItem.setOnClickListener(this);
}
@Override
public void onClick(View view) {
mClickListener.onSuggestionClick(suggestions.get(getAdapterPosition()));
}
}
@Override
public int getItemViewType(int position) {
if(words !=null)
return words.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
else
return VIEW_TYPE_SUGGESTION;
}
OnLoadMore
@Override
public void onLoadMore() {
if (request.getPage() < totalPageAmount) {
request.nextPage();
LoadMoreTask task = new LoadMoreTask(request, words, adapter);
task.execute();
}
}
LoadMoreTask
public LoadMoreTask(Request request, ArrayList<Word> words, SearchResultAdapter adapter){
this.request=request;
this.words=words;
this.adapter=adapter;
this.connection = new Connection(request.getUrl());
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if(words.get(words.size()-1)!=null){
words.add(null);
index = words.size() - 1;
adapter.notifyItemInserted(index);
}
}
@Override
protected Void doInBackground(Void... params) {
connection.connect();
String result = connection.getStringFromServer();
Respond respond = new Respond(result);
if(!request.isDetailed())
words.addAll(respond.handleSimple());
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
connection.disconnect();
if(index!=0 && words.get(index)==null){
words.remove(index);
adapter.notifyItemRemoved(index+1);
}
adapter.notifyDataSetChanged();
adapter.setLoading();
}
As it can be seen, when onLoadMore
triggered, I connect to the webserver get more data and attach it to the previous arraylist
. Adding null object in preExecute
is done in order to change viewHolder
type (I have little loading progressBar
in the end of list, and it is deleted after the asynctask
is finished).
This code works pretty well, except that notifyDataSetChanged
freezes UI a little bit. Yes it is small enough and seen only if you scroll too fast, but anyway it is annoying. How I can optimize this code to remove freezes?
I solved the problem using notifyItemRangeInserted
function, it seems that this function needs less resources than notifyDatasetChanged
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
connection.disconnect();
adapter.resetLoading();
if(index!=0 && words.get(index)==null){
words.remove(index);
adapter.notifyItemRemoved(index);
}
adapter.notifyItemRangeInserted(index,30);
}
So using this code everything works perfectly without any even nano freezes.