I have an app with a RecyclerView
and an customized Adapter
which gets that from a parse backend.
My adapter contains a few TextView
s, a Button
, a small ImageView
. Some of my TextView
s require a function (calculating upvote count and stuff like this) in my Adapter
's onBindViewHolder()
to get their data.
I've noticed that my scrolling is really slow and laggy because of the functions that are called in onBindViewHolder()
.
Where should I set my data and call my functions in order to get a smooth scrolling ?
EDIT 1:
Here's my Adapter Code:
public class PostAdapter extends RecyclerView.Adapter<PostAdapter.ViewHolder> {
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView postName;
public ImageView postCatIcon;
public TextView postDays;
public TextView postDaysLabel;
public TextView postHours;
public TextView postHoursLabel;
public TextView postMinutes;
public TextView postMinutesLabel;
public TextView postDistance;
public TextView postLikes;
public TextView postAuthor;
public LikeButton likepost;
private Post post;
public ViewHolder(View itemView) {
super(itemView);
postName = (TextView) itemView.findViewById(R.id.post_name);
postCatIcon = (ImageView) itemView.findViewById(R.id.post_cat_icon);
postDays = (TextView) itemView.findViewById(R.id.post_days);
postDaysLabel = (TextView) itemView.findViewById(R.id.post_days_label);
postHours = (TextView) itemView.findViewById(R.id.post_hours);
postHoursLabel = (TextView) itemView.findViewById(R.id.post_hours_label);
postMinutes = (TextView) itemView.findViewById(R.id.post_minutes);
postMinutesLabel = (TextView) itemView.findViewById(R.id.post_minutes_label);
postDistance = (TextView) itemView.findViewById(R.id.post_distance);
likePost = (LikeButton) itemView.findViewById(R.id.like_the_post);
postAuthor = (TextView) itemView.findViewById(R.id.post_author);
postLikes = (TextView) itemView.findViewById(R.id.post_likes);
}
public void setData(Post post){
this.post = post;
updateTimeRemaining(System.currentTimeMillis());
}
public void updateTimeRemaining(long currentTime) {
long diff = post.getDate().getTime() - currentTime;
int minutes = (int) ((diff / (1000*60)) % 60);
int hours = (int) ((diff / (1000*60*60)) % 24);
int days = (int) (diff / (1000*60*60*24));
String strgMinutes = "00";
String strgHours = "00";
String strgDays = "00";
if(minutes < 10)
strgMinutes = "0" + String.valueOf(minutes);
else
strgMinutes = String.valueOf(minutes);
if(hours < 10)
strgHours = "0" + String.valueOf(hours);
else
strgHours = String.valueOf(hours);
if(days < 10){
strgDays = "0" + String.valueOf(days);
}else
strgDays = String.valueOf(days);
postDays.setText(strgDays);
postHours.setText(strgHours);
postMinutes.setText(strgMinutes);
}
}
public static class ProgressViewHolder extends PostAdapter.ViewHolder {
public ProgressBar progressBar;
public ProgressViewHolder(View v) {
super(v);
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
}
}
private List<Post> mPosts;
private Context mContext;
private ListFragment mFragment;
private final int VIEW_ITEM = 1;
private final int VIEW_PROG = 0;
ArrayList<PostAdapter.ViewHolder> viewHoldersList;
private Handler handler = new Handler();
private Runnable updateRemainingTimeRunnable = new Runnable() {
@Override
public void run() {
long currentTime = System.currentTimeMillis();
synchronized (viewHoldersList) {
for (PostAdapter.ViewHolder holder : viewHoldersList) {
holder.updateTimeRemaining(currentTime);
}
}
handler.postDelayed(this, 60000);
}
};
// The minimum amount of items to have below your current scroll position before loading more.
private int visibleThreshold = 2;
private int lastVisibleItem, totalItemCount;
private boolean loading;
private OnLoadMoreListener onLoadMoreListener;
public PostAdapter(List<Post> posts, Context context, ListFragment fragment, RecyclerView recyclerView) {
mContext = context;
mPosts = posts;
mFragment = fragment;
if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
totalItemCount = linearLayoutManager.getItemCount();
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
if (!loading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
// End has been reached
// Do something
if (onLoadMoreListener != null) {
onLoadMoreListener.onLoadMore();
}
loading = true;
}
}
});
}
viewHoldersList = new ArrayList<>();
startUpdateTimer();
}
public PostAdapter(List<Post> posts, Context context, RecyclerView recyclerView) {
mContext = context;
mPosts = posts;
if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
totalItemCount = linearLayoutManager.getItemCount();
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
if (!loading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
// End has been reached
// Do something
if (onLoadMoreListener != null) {
onLoadMoreListener.onLoadMore();
}
loading = true;
}
}
});
}
viewHoldersList = new ArrayList<>();
startUpdateTimer();
}
private void startUpdateTimer() {
handler.postDelayed(updateRemainingTimeRunnable,60000);
}
public PostAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View postView;
ViewHolder viewHolder;
if (viewType == VIEW_ITEM) {
postView = inflater.inflate(R.layout.list_item, parent, false);
viewHolder = new ViewHolder(postView);
} else {
postView = inflater.inflate(R.layout.loading_item, parent, false);
viewHolder = new ProgressViewHolder(postView);
}
return viewHolder;
}
public void onBindViewHolder(final PostAdapter.ViewHolder viewHolder, final int position) {
if (viewHolder instanceof ViewHolder) {
final Post post = mPosts.get(position);
if(post != null){
synchronized (viewHoldersList) {
viewHolder.setData(post);
viewHoldersList.add(viewHolder);
}
Category cat = post.getCategory();
TextView nameTextView = viewHolder.postName;
TextView authorTextView = viewHolder.postAuthor;
final TextView likesTextView = viewHolder.postLikes;
ImageView CatIconView = viewHolder.postCatIcon;
final LikeButton likeButtonView = viewHolder.likePost;
TextView postDistance = viewHolder.postDistance;
ParseUser postAuthor = null;
if(mFragment != null) {
if (mFragment.getIfFilteredByDistance()) {
postDistance.setVisibility(View.VISIBLE);
Location postLocation = new Location("Post location");
if(post.getLocation() != null){
postLocation.setLongitude(post.getLocation().getLongitude());
postLocation.setLatitude(post.getLocation().getLatitude());
GPSTracker gpsTracker = new GPSTracker(mContext);
if (gpsTracker.canGetLocation()) {
Location myLocation = new Location("My location");
myLocation.setLatitude(gpsTracker.getLatitude());
myLocation.setLongitude(gpsTracker.getLongitude());
postDistance.setText((Math.round(myLocation.distanceTo(postLocation)) / 1000) + " km");
} else
gpsTracker.showSettingsAlert();
}
} else
postDistance.setVisibility(View.INVISIBLE);
}
try{
postAuthor = post.getAuthor().fetchIfNeeded();
}catch (ParseException e){
Log.e("Parse:", e.getMessage());
}
saveActivitiesToLocalDatastore(post);
final int likeCount = ((PostApplication)mContext).getPostLikeCount(post);
likesTextView.setText(String.valueOf(likeCount));
likeButtonView.setLiked(((PostApplication)mContext).getIfLiked(post));
viewHolder.itemView.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
Intent intent = new Intent(mContext, DetailActivity.class);
intent.putExtra("PostID",post.getObjectId());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
});
likeButtonView.setOnLikeListener(new OnLikeListener() {
@Override
public void liked(LikeButton likeButton) {
if(((PostApplication)mContext).likePost(post))
likesTextView.setText(String.valueOf(Integer.parseInt(likesTextView.getText().toString()) + 1));
}
@Override
public void unLiked(LikeButton likeButton) {
if(((PostApplication)mContext).unlikePost(post))
likesTextView.setText(String.valueOf(Integer.parseInt(likesTextView.getText().toString()) -1));
}
});
nameTextView.setText(post.getTitle());
authorTextView.setText(post.getAuthor().getUsername());
CatIconView.setImageDrawable(ContextCompat.getDrawable(mContext,mContext.getResources().getIdentifier(cat.getIconName()+"_18dp","drawable",mContext.getPackageName())));
}
} else {
((ProgressViewHolder) viewHolder).progressBar.setIndeterminate(true);
}
}
public int getItemCount() {
return mPosts.size();
}
private void saveActivitiesToLocalDatastore(final Post post){
// Saves Likes to Parse local datastore
}
@Override
public int getItemViewType(int position) {
return mposts.get(position) != null ? VIEW_ITEM : VIEW_PROG;
}
public void setLoaded() {
loading = false;
}
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
public interface OnLoadMoreListener {
void onLoadMore();
}
EDIT 2:
I followed this in order to fix my countdown's flickering:
Recyclerview with multiple countdown timers causes flickering
EDIT 3:
I tried running the app without the additionals methods like saveActivitiesInBackgroud, and the listeners and it works really smooth