I have a RecyclerView inside a NestedScrollView that show some data downloaded asynchronously. The problem is that there is a significant lag when the items are initilized. After some tests I found out that the problem is that onCreateViewHolder is called for every item and it took some time to inflate the layout. This is my adapter:
public class EpisodeAdapter extends RecyclerView.Adapter<EpisodeAdapter.ViewHolder> {
private static final String TAG = "EpisodeAdapter";
private static final int NO_POSITION = -1;
private static final int EXPAND = 1;
private static final int COLLAPSE = 2;
private SparseArray<Episode> episodes;
private OnItemClickListener<Episode> downloadClickListener;
private OnItemClickListener<Episode> playClickListener;
private RecyclerView recyclerView;
private final EpisodeAnimator episodeAnimator;
private final Transition expandCollapse;
private int expandedPosition = NO_POSITION;
public EpisodeAdapter() {
episodes = new SparseArray<>();
episodeAnimator = new EpisodeAnimator();
expandCollapse = new AutoTransition();
}
//Called when first loading items
public void swapEpisodes(SparseArray<Episode> newEpisodes){
final int previousSize = episodes.size();
episodes = newEpisodes;
expandedPosition = NO_POSITION;
Log.e(TAG, "Swap called");
if(previousSize == 0) {
notifyItemRangeInserted(0, episodes.size());
}
else {
notifyItemRangeChanged(0, Math.max(previousSize, episodes.size()));
}
}
//Called when downloading other information, this seems to work fine without delay
public void setEpisodesDetails(final List<TmdbEpisode> episodeList){
for (TmdbEpisode episode : episodeList){
final int position = episodes.indexOfKey(episode.getNumber());
notifyItemChanged(position, episode);
}
}
#Override
public EpisodeAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.e(TAG, "Start createViewHolder");
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_episode, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
viewHolder.downloadButton.setOnClickListener(v -> {
if(downloadClickListener != null)
downloadClickListener.onItemClick(v, episodes.valueAt(viewHolder.getAdapterPosition()));
});
viewHolder.playButton.setOnClickListener(v -> {
if(playClickListener != null)
playClickListener.onItemClick(v, episodes.valueAt(viewHolder.getAdapterPosition()));
});
viewHolder.itemView.setOnClickListener(v -> {
final int position = viewHolder.getAdapterPosition();
if(position == NO_POSITION) return;
TransitionManager.beginDelayedTransition(recyclerView, expandCollapse);
episodeAnimator.setAnimateMoves(false);
//Collapse any currently expanded items
if(expandedPosition != NO_POSITION){
notifyItemChanged(expandedPosition, COLLAPSE);
}
//Expand clicked item
if(expandedPosition != position){
expandedPosition = position;
notifyItemChanged(position, EXPAND);
}
else {
expandedPosition = NO_POSITION;
}
});
Log.e(TAG, "Finish createViewHolder");
return viewHolder;
}
#Override
public void onBindViewHolder(EpisodeAdapter.ViewHolder holder, int itemPosition) {
Log.e(TAG, "Start");
holder.number.setText(String.valueOf(episodes.keyAt(itemPosition)));
holder.details.setVisibility(View.GONE);
holder.itemView.setActivated(false);
Log.e(TAG, "Finish");
}
#Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
Log.e(TAG, "Start payloads");
if(payloads.contains(EXPAND) || payloads.contains(COLLAPSE)){
setExpanded(holder, position == expandedPosition);
}
else if(!payloads.isEmpty() && payloads.get(0) instanceof TmdbEpisode){
TmdbEpisode episode = (TmdbEpisode) payloads.get(0);
holder.title.setText(episode.getName());
holder.details.setText(episode.getOverview());
}
else {
onBindViewHolder(holder, position);
}
Log.e(TAG, "Finish payloads");
}
private void setExpanded(ViewHolder holder, boolean isExpanded) {
holder.itemView.setActivated(isExpanded);
holder.details.setVisibility((isExpanded) ? View.VISIBLE : View.GONE);
}
public void setPlayClickListener(OnItemClickListener<Episode> onItemClickListener){
playClickListener = onItemClickListener;
}
public void setDownloadClickListener(OnItemClickListener<Episode> onItemClickListener){
downloadClickListener = onItemClickListener;
}
#Override
public int getItemCount() {
return episodes.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
View itemView;
TextView number;
FadeTextSwitcher title;
ImageButton downloadButton;
FloatingActionButton playButton;
TextView details;
ViewHolder(View itemView) {
super(itemView);
Log.e(TAG, "Start constructor");
this.itemView = itemView;
number = itemView.findViewById(R.id.number);
title = itemView.findViewById(R.id.title);
downloadButton = itemView.findViewById(R.id.download_button);
playButton = itemView.findViewById(R.id.play_button);
details = itemView.findViewById(R.id.details);
Log.e(TAG, "Finish constructor");
}
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
this.recyclerView.setItemAnimator(episodeAnimator);
expandCollapse.setDuration(recyclerView.getContext().getResources().getInteger(R.integer.episode_expand_collapse_duration));
expandCollapse.setInterpolator(AnimationUtils.loadInterpolator(this.recyclerView.getContext(), android.R.interpolator.fast_out_slow_in));
expandCollapse.addListener(new Transition.TransitionListener() {
#Override
public void onTransitionStart(android.transition.Transition transition) {
EpisodeAdapter.this.recyclerView.setOnTouchListener((v, event) -> true);
}
#Override
public void onTransitionEnd(android.transition.Transition transition) {
episodeAnimator.setAnimateMoves(true);
EpisodeAdapter.this.recyclerView.setOnTouchListener(null);
}
#Override
public void onTransitionCancel(android.transition.Transition transition) {}
#Override
public void onTransitionPause(android.transition.Transition transition) {}
#Override
public void onTransitionResume(android.transition.Transition transition) {}
});
}
static class EpisodeAnimator extends SlideInItemAnimator {
private boolean animateMoves = false;
EpisodeAnimator() {
super();
}
void setAnimateMoves(boolean animateMoves) {
this.animateMoves = animateMoves;
}
#Override
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
if (!animateMoves) {
dispatchMoveFinished(holder);
return false;
}
return super.animateMove(holder, fromX, fromY, toX, toY);
}
}
}
Is there a way to force reusing the same ViewHolder for every items? So onCreateViewHolder will be called once.
I've also set nestedScrollingEnabled="false" in the recyclerview.
I have a RecyclerView inside a NestedScrollView
I'm going to guess that your <RecyclerView> tag has its height defined as wrap_content. If it does, that means that you're inflating a layout resource (and creating a ViewHolder object) for every single item in your data set; potentially thousands of layout inflations and object creations.
The recycling behavior of RecyclerView only works when the height of the recyclerview is smaller than the height needed to display its children. It's normal for a recyclerview to create a small double-digit number of ViewHolder instances (usually however many items you can see on screen at once plus a few to optimize views just off-screen), but this depends on the fact that your recyclerview's size is constrained by the screen size (i.e. you're using match_parent or a fixed size).
In the case of a RecyclerView with wrap_content height inside a NestedScrollView, the user won't be able to see all of the items at a single time, but the Android framework only knows that you have a recyclerview large enough to hold every single item in your data set and so it has to create a viewholder for every single item.
You'll have to figure out a way to rework your layout hierarchy so that you can use some limited height for your RecyclerView.
Related
I have a recycler view when clicked on the item VISIBLE hide seek bar for each item.
the issue is when clicked on a single item then VISIBLE multiple hides seek bar and also when scrolling up down auto show randomly seek bar in this recycler view. help me. Thanks
See in this screenshot
Adapter class
public class Adapter_Custom extends RecyclerView.Adapter<Adapter_Custom.ViewHolder> {
private Adapter_Custom.OnItemClickListener mListener;
public interface OnItemClickListener {
void onImageClick(int position,ImageView imageView,SeekBar seekBar);
void onSeekBarProgressChange(int position,String progress);
}
public void setOnItemClickListener(Adapter_Custom.OnItemClickListener listener) {
mListener = listener;
}
Context MyContext;
List<Model_main> modelList;
public Adapter_Custom(Context myContext, List<Model_main> modelList) {
MyContext = myContext;
this.modelList = modelList;
}
#NonNull
#Override
public Adapter_Custom.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(MyContext);
View view = inflater.inflate(R.layout.rv_item_custom, null, false);
return new Adapter_Custom.ViewHolder(view, mListener);
}
#Override
public void onBindViewHolder(#NonNull final Adapter_Custom.ViewHolder holder, int position) {
final Model_main model = modelList.get(position);
holder.textView3.setText(model.getName());
holder.sound_image.setImageResource(model.getImages());
}
#Override
public int getItemCount() {
return modelList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView textView3;
ImageView sound_image;
SeekBar seekBar;
public ViewHolder(#NonNull final View itemView, final Adapter_Custom.OnItemClickListener listener) {
super(itemView);
textView3 = itemView.findViewById(R.id.textView3);
sound_image = itemView.findViewById(R.id.sound_image);
seekBar = itemView.findViewById(R.id.seekBar);
sound_image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onImageClick(position,sound_image,seekBar);
// Toast.makeText(MyContext, ""+position, Toast.LENGTH_SHORT).show();
}
}
}
});
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
if (listener != null) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onSeekBarProgressChange(position,String.valueOf(seekBar.getProgress()));
}
}
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}
}
ClickListner
adapter_custom1.setOnItemClickListener(new Adapter_Custom.OnItemClickListener() {
#Override
public void onImageClick(int position, ImageView imageView, SeekBar seekBar) {
Toast.makeText(getContext(), "" + position, Toast.LENGTH_SHORT).show();
rv.findViewHolderForAdapterPosition(position).itemView.findViewById(R.id.seekBar)
.setVisibility(isVisible()? View.VISIBLE : View.GONE);
// rv.findViewHolderForAdapterPosition(position).itemView.findViewById(R.id.seekBar).setVisibility(View.VISIBLE);
// rv.findViewHolderForAdapterPosition(position).itemView.findViewById(R.id.textView3).setVisibility(View.GONE);
if (custom_btn_div.getVisibility() == View.GONE) {
custom_btn_div.setVisibility(View.VISIBLE);
}
}
Problem: Setting view visibility or checkbox check or drawable an ImageView to one or some of a RecyclerView items (rows).
Solution: You need to negate the opposite to remaining Views; e.g. If you set visiblity of a row item view to VISIBLE, then you've to make sure to set it as GONE/INVISIBLE to the similar items of the other rows.
Reason: Because the RecyclerView, as indicates from its name, recycles views; so if it needs to hold 100 items; and the screen size can hold 10 items at a time; then with the next scroll of the RecyclerView it doesn't create those items from the scrach, but copies the data of the new items into their place holds; if the data (visibility) is not set, then it takes the old data.
#Override
public void onBindViewHolder(#NonNull final Adapter_Custom.ViewHolder holder, int position) {
final Model_main model = modelList.get(position);
holder.textView3.setText(model.getName());
holder.sound_image.setImageResource(model.getImages());
holder.seekBar.setVisibility(View.GONE);
}
I'm implementing a recycler view using room database library. I would like to list my items and that they could be edited by clicking. After re-submit the item, go back again to the list and show up the items + changes.
I'm currently using DiffUtil callback to compare the old and the new lists. I think the problem is here. My Dto item has 4 fields.
id title image arraylist
So when the user edits an item, the list should update the UI row (title, image or/and arraylist)
This is my Adapter.
private OnItemClickListener listener;
public ExercicioAdapter() {
super(DIFF_CALLBACK);
}
private static final DiffUtil.ItemCallback<Exercicio> DIFF_CALLBACK = new DiffUtil.ItemCallback<Exercicio>() {
#Override
public boolean areItemsTheSame(#NonNull Exercicio oldExercicio, #NonNull Exercicio newExercicio) {
return oldExercicio.getId() == newExercicio.getId();
}
#Override
public boolean areContentsTheSame(#NonNull Exercicio oldExercicio, #NonNull Exercicio newExercicio) {
return oldExercicio.getTitulo().equals(newExercicio.getTitulo()) &&
Float.compare(oldExercicio.getMaxPeso(), newExercicio.getMaxPeso()) == 0;
}
};
#NonNull
#Override
public MyHolderView onCreateViewHolder(#NonNull ViewGroup parent, int i) {
View view;
LayoutInflater mInflater = LayoutInflater.from(parent.getContext());
view = mInflater.inflate(R.layout.exercicios_item, parent, false);
return new MyHolderView(view);
}
#Override
public void onBindViewHolder(#NonNull MyHolderView exercicioHolder, final int i) {
Exercicio exercicio = getItem(i);
// Mostrar a informacion do obxeto.
ArrayList<Repeticion> repeticions = exercicio.getRepeticions();
float peso_max = 0f;
if (repeticions != null && repeticions.size() > 0) {
for (Repeticion rep : repeticions) {
if (rep.getPeso() > peso_max) {
peso_max = rep.getPeso();
}
}
}
exercicioHolder.nome.setText(exercicio.getTitulo());
String peso_str = exercicioHolder.itemView.getContext().getString(R.string.peso_max);
exercicioHolder.peso.setText(peso_str + " " + Float.toString(peso_max));
if (exercicio.getImaxe() != null && exercicio.getImaxe().length != 0) {
exercicioHolder.imx_exercicio.setImageBitmap(convertByteArrayToBitmap(exercicio.getImaxe()));
}
}
public Exercicio getExercicioAt(int position) {
return getItem(position);
}
public class MyHolderView extends RecyclerView.ViewHolder {
TextView nome;
TextView peso;
ImageView imx_exercicio;
CardView exercicio_cardview;
MyHolderView(#NonNull View itemView) {
super(itemView);
exercicio_cardview = itemView.findViewById(R.id.exercicio_cardview);
nome = itemView.findViewById(R.id.nome);
peso = itemView.findViewById(R.id.peso);
imx_exercicio = itemView.findViewById(R.id.imx_exercicio);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int position = getAdapterPosition();
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(getItem(position));
}
}
});
}
}
public interface OnItemClickListener {
void onItemClick(Exercicio exercicio);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
The problem: when an item is updated, changing the title for example, the list gets updated, but the eddited item disappears. If i end the activity and start it again, the item updated is there. So the problem must be the diff callback, but I'm not really able to make it work. Sorry for my English, this is not my main language, thanks all!
Most probably you are not properly using the DiffUtil functionality. You first need to calculate diff and then dispatch updates to adapter after which it will refresh your adapter with the new data. Use DiffUtil.Callback() and not DiffUtil.ItemCallback()
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
#Override
public int getOldListSize() {
// old list size, which is same as new in your case
}
#Override
public int getNewListSize() {
// new list size, which is same as old in your case
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition)
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
}
)
result.dispatchUpdatesTo(this); // this parameter is your Recycler View's adapter
You can read more about Diff Util here and here
Let me know if you still have doubts. Thanks.
I have a Recyclerview with header view as slider images and remaining view as normal recycler items.I am wondering if there is any way around to make the header view invisible depending upon some sort of condition.The recycler view consists of two separate layout files for this purpose: layout1 for header items and layout2 for normal recycler items and adapter will pick a layout and binds corresponding data at runtime.
This is my RecyclerView adapter RestaurantAdapter.java
public class RestaurantAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = RestaurantAdapter.class.getName();
private List<Restaurant> mList;
private Context mContext;
private RestaurantType mRestaurantType;
private static final int RECYCLER_HEADER=0,RECYCLER_ITEMS=1;
private LayoutInflater inflater;
private SlideItemViewHolder slideItemViewHolder;
private List<ImageSliderPOJO> mData;
public RestaurantAdapter(Context context, List<Restaurant> list, RestaurantType restaurantType) {
this.mContext = context;
this.mList = list;
this.mRestaurantType = restaurantType;
inflater=LayoutInflater.from(context);
}
public void updateAdapter(List<ImageSliderPOJO> data){
this.mData = data;
this.notifyDataSetChanged();
}
#Override
public int getItemCount() {
return mList.size();
}
#Override
public int getItemViewType(int position) {
return position == 0 ? RECYCLER_HEADER : RECYCLER_ITEMS;
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int i) {
int viewType=viewHolder.getItemViewType();
switch (viewType){
case RECYCLER_HEADER:
slideItemViewHolder = (SlideItemViewHolder) viewHolder;
slideItemViewHolder.updateHeader();
break;
case RECYCLER_ITEMS:
final RecyclerItemViewHolder holder = (RecyclerItemViewHolder) viewHolder;
final Restaurant restaurant = mList.get(i);
Picasso.with(mContext)
.load(restaurant.getVendorLogo())
.placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.into(holder.restaurentImageView);
holder.restaurentNameTextView.setText(restaurant.getName());
//Remaining code here
break;
default:
throw new RuntimeException(TAG+":Unable to bind the viewType"+viewType);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
switch (viewType){
case RECYCLER_HEADER:
return new SlideItemViewHolder(inflater.inflate(R.layout.slide_show_restaurant_fragment_list,viewGroup,false));
case RECYCLER_ITEMS:
return new RecyclerItemViewHolder(inflater.inflate(R.layout.new_restautant_list_items, viewGroup, false));
default:
throw new RuntimeException(TAG+":Invalid ViewType "+viewType);
}
}
public static class RecyclerItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private ClickListener clickListener;
//Initialization here.
public RecyclerItemViewHolder(View v) {
super(v);
ButterKnife.inject(this, v);
v.setOnClickListener(this);
}
#Override
public void onClick(View v) {
clickListener.onClick(v, getPosition(), false);
}
public interface ClickListener {
/**
* Called when the view is clicked.
*
* #param v view that is clicked
* #param position of the clicked item
* #param isLongClick true if long click, false otherwise
*/
public void onClick(View v, int position, boolean isLongClick);
}
/* Setter for listener. */
public void setClickListener(ClickListener clickListener) {
this.clickListener = clickListener;
}
}
// id = 87,170
private class SlideItemViewHolder extends RecyclerView.ViewHolder {
SliderLayout sliderLayout;
LinearLayout rootLinearLayout;
public SlideItemViewHolder(View recyclerHeader) {
super(recyclerHeader);
sliderLayout = (SliderLayout) recyclerHeader.findViewById(R.id.home_slider);
rootLinearLayout = (LinearLayout) recyclerHeader.findViewById(R.id.rootLinearLayout);
}
private void updateHeader() {
if(Util.isNetworkAvailable(mContext)){
for (int i = 0; i < mData.size(); i++) {
DefaultSliderView defaultSliderView = new DefaultSliderView(mContext);
final int finalI = i;
defaultSliderView.image(mData.get(finalI).getImageUrl())
.setOnSliderClickListener(new BaseSliderView.OnSliderClickListener() {
#Override
public void onSliderClick(BaseSliderView slider) {
Restaurant restaurantById = Restaurant.searchByRestaurantId(mData.get(finalI).getTargetVendorId());
if(restaurantById != null)
openDetailFragment(restaurantById);
}
});
sliderLayout.addSlider(defaultSliderView);
}
}
}
}
public void openDetailFragment(Restaurant restaurant) {
Intent intent = new Intent(mContext, DetailTabActivity.class);
intent.putExtra(DetailTabActivity.INTENT_RESTAURANT_DATA, restaurant);
mContext.startActivity(intent);
}
public SliderLayout getSliderLayout(){
return slideItemViewHolder.sliderLayout;
}
}
And this adapter is set and updated from this fragment RestaurantFragment.java as:
private void setAdapter() {
dismissDialog();
if (getActivity() != null)
getActivity().runOnUiThread(new Runnable(){
#Override
public void run() {
if (restaurantList != null && restaurantList.size() > 0) {
restaurantRecyclerView.setVisibility(View.VISIBLE);
mEmptyListTextView.setVisibility(View.GONE);
restaurentListAdapter = new RestaurantAdapter(getActivity(), restaurantList, mRestaurantType);
restaurantRecyclerView.setAdapter(restaurentListAdapter);
restaurentListAdapter.updateAdapter(mData);
restaurantRecyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager) {
#Override
public void onLoadMore(int current_page) {
mCurrentPage = mCurrentPage + 1;
getHttpResturantData();
}
});
}
}
});
}
Is this much of an explanation helpful or should I paste more code?
Based on your code, it's possible to remove the header based on a certain condition.
Adjust your code to cater for the following:
#Override
public int getItemViewType(int position) {
if(hasHeaeder()) { // where you add the header
return position == 0 ? RECYCLER_HEADER : RECYCLER_ITEMS;
} else { // where you don't add the header
return RECYCLER_ITEMS;
}
}
This code also needs changing (currently it's wrong since it doesn't take care of the fact that the header adds 1 to the position).
final Restaurant restaurant = mList.get(i);
Replace it with
final Restaurant restaurant = hasHeader() ? mList.get(i +1) : mList.get(i);
Where hasHeader() is the code you need to write in order to determine whether or not the recycler should contain a header.
I'm using RecyclerView for a long list and when i scroll too fast, something weird happens. There are two problems.
First one is the one in the image below. One of items (like red bordered one in the image) sticks on some random part of the screen and blocks other items. It disappears when the real version of that item becomes visible on the screen.
Another problem about scrolling this long RecyclerView too fast is sticking onClick effect. Some of items become visible like someone is pressing on them.
Are these problems about my adapter or are they common problems about RecyclerView? Here is my adapter:
public class SimpleListItemRecyclerAdapter extends RecyclerView.Adapter<SimpleListItemRecyclerAdapter.ListItemViewHolder> {
Context context;
ArrayList<RecyclerItemModel> list;
OnRecyclerViewItemClickListener onRecyclerViewItemClickListener;
private int lastPosition = -1;
private boolean isClickable;
public SimpleListItemRecyclerAdapter(ArrayList<RecyclerItemModel> list, _FragmentTemplate fragment) {
this.list = list;
this.context = fragment.getActivity();
try {
this.onRecyclerViewItemClickListener = (OnRecyclerViewItemClickListener) fragment;
isClickable = true;
} catch (ClassCastException e) {
isClickable = false;
}
setHasStableIds(true);
}
#Override
public ListItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.item_sub_main_list_single_text,
parent,
false);
return new ListItemViewHolder(itemView, onRecyclerViewItemClickListener, isClickable, list.get(0).getHeight());
}
#Override
public void onBindViewHolder(ListItemViewHolder holder, int position) {
final RecyclerItemModel recyclerItem = list.get(position);
if (recyclerItem.isBackgroundColorSpecified())
holder.itemView.setBackgroundResource(recyclerItem.getBackgroundColorResource());
else {
final int[] backgroundColors = Preferences.backgroundSelectorsGrey;
holder.itemView.setBackgroundResource(backgroundColors[position % backgroundColors.length]);
}
holder.textMain.setText(recyclerItem.getTextMain());
if (recyclerItem.isImageRightAvailable()) {
if (recyclerItem.isProgressBarAvailable())
if (recyclerItem.shouldShowDoneImage())
setDrawableFromSVG(holder.imageRight, recyclerItem.getImageRightResourceDone());
else
setDrawableFromSVG(holder.imageRight, recyclerItem.getImageRightResource());
} else
holder.imageRight.setVisibility(View.GONE);
if (recyclerItem.isTextMainBottomAvailable())
holder.textMainBottom.setText(recyclerItem.getTextMainBottom());
else
holder.textMainBottom.setVisibility(View.GONE);
if (recyclerItem.isTextRightTopAvailable())
holder.textRightTop.setText(recyclerItem.getTextRightTop());
else
holder.textRightTop.setVisibility(View.GONE);
if (recyclerItem.isTextRightBottomAvailable())
holder.textRightBottom.setText(recyclerItem.getTextRightBottom());
else
holder.textRightBottom.setVisibility(View.GONE);
if (recyclerItem.isProgressBarAvailable())
holder.progressBar.setProgress(recyclerItem.getProgress());
else
holder.progressBar.setVisibility(View.GONE);
setAnimation(holder.itemView, position);
}
#Override
public long getItemId(int position) {
return list.get(position).hashCode();
}
#Override
public int getItemCount() {
return list.size();
}
private void setAnimation(View viewToAnimate, int position) {
if (position > lastPosition) {
Animation animation = AnimationUtils.loadAnimation(context, R.anim.appear);
viewToAnimate.startAnimation(animation);
lastPosition = position;
}
}
public Drawable setDrawableFromSVG(ImageView imageView, int resource) {
SVG svg = new SVGBuilder()
.readFromResource(context.getResources(), resource)
.build();
imageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
imageView.setImageDrawable(svg.getDrawable());
return svg.getDrawable();
}
public final static class ListItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
OnRecyclerViewItemClickListener onRecyclerViewItemClickListener;
TextView textMain, textMainBottom, textRightTop, textRightBottom;
ImageView imageRight;
ProgressBar progressBar;
View itemView;
public ListItemViewHolder(View itemView, OnRecyclerViewItemClickListener onRecyclerViewItemClickListener,
boolean isClickable, int height) {
super(itemView);
this.itemView = itemView;
this.onRecyclerViewItemClickListener = onRecyclerViewItemClickListener;
textMain = (TextView) itemView.findViewById(R.id.list_item_text_main);
imageRight = (ImageView) itemView.findViewById(R.id.list_item_image_right);
textRightTop = (TextView) itemView.findViewById(R.id.list_item_text_right_top);
textRightBottom = (TextView) itemView.findViewById(R.id.list_item_text_right_bottom);
textMainBottom = (TextView) itemView.findViewById(R.id.list_item_text_main_bottom);
progressBar = (ProgressBar) itemView.findViewById(R.id.list_item_progress_bar);
switch (height) {
case RecyclerItemModel.HEIGHT_FULL:
itemView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, Preferences.squareLength));
break;
case RecyclerItemModel.HEIGHT_HALF:
itemView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, Preferences.squareLength / 2));
}
if (isClickable)
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
onRecyclerViewItemClickListener.onRecyclerViewItemClick(getPosition());
}
}
}
Here is what resolved my problem:
Animating somehow causes a problem on RecyclerView. So the solution for me was removing the following line:
setAnimation(holder.itemView, position);
I didn't try to add animation again but if you really need it, here is something that can be useful: How to animate RecyclerView items when they appear.
I am creating an app using new API 21 (Lollipop) i am setting animation slide_in_left on recyclerview to animate cardview 1 by 1 but when items appear they will come together and i want them to slide_in_left 1 by 1 and 2nd i am animating toolbar bar to hide/show its working but problem is when i scroll up and when recyclerview stops then it will hide and same case when scroll down, i want to hide/show toolbar when scroll starts anyone help me. Thanks in advance
belowis Myadapter class for recyclerview animation
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private ArrayList<PersonData> peopleDataSet;
private Context context;
View view;
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView textViewName;
TextView textViewEmail;
ImageView imageViewIcon;
public MyViewHolder(View itemView) {
super(itemView);
this.textViewName = (TextView) itemView.findViewById(R.id.textViewName);
this.textViewEmail = (TextView) itemView.findViewById(R.id.textViewEmail);
this.imageViewIcon = (ImageView) itemView.findViewById(R.id.imageView);
}
}
public MyAdapter(ArrayList<PersonData> people,Context context) {
this.peopleDataSet = people;
this.context=context;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.cards_layout, parent, false);
view.setOnClickListener(MainActivity.myOnClickListener);
setAnimation(view);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
#Override
public void onBindViewHolder(final MyViewHolder holder, final int listPosition) {
TextView textViewName = holder.textViewName;
TextView textViewEmail = holder.textViewEmail;
ImageView imageView = holder.imageViewIcon;
textViewName.setText(peopleDataSet.get(listPosition).getName());
textViewEmail.setText(peopleDataSet.get(listPosition).getEmail());
imageView.setImageResource(peopleDataSet.get(listPosition).getImage());
}
#Override
public int getItemCount() {
return peopleDataSet.size();
}
/**
* Here is the key method to apply the animation
*/
private void setAnimation(View viewToAnimate)
{
// If the bound view wasn't previously displayed on screen, it's animated
Animation animation = AnimationUtils.loadAnimation(context, android.R.anim.slide_in_left);
viewToAnimate.startAnimation(animation);
}
below is toolbar code
#SuppressLint("NewApi") #Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
toolbar.animate()
.translationY(-toolbar.getBottom())
.setInterpolator(new AccelerateInterpolator())
.start();
} else {
toolbar.animate()
.translationY(0)
.setInterpolator(new AccelerateInterpolator())
.start();
}
Unfortunately, RecyclerView does not support animation while scrolling with RecyclerView.ItemAnimator.
If you want to achieve one by one animation of RecyclerView items, you should animate each viewHolder.itemView inside onBindView method. Keeping track of last animated position is important to avoid animation when scrolling down, unless that is not your intention though.
Here is simple alpha animation I did:
public interface RecyclerViewItemOnScrollAnimator {
public void onAnimateViewHolder(RecyclerView.ViewHolder viewHolder, int position);
public void onPrepareToAnimateViewHolder(RecyclerView.ViewHolder viewHolder);
}
Here is implementation of the Animator (sort of, couldn't come up with better name)
public class RecyclerViewScrollAnimator implements RecyclerViewItemOnScrollAnimator{
private final LinearLayoutManager mLayoutManager;
private final RecyclerView mRecyclerView;
private final Interpolator mInterpolator;
int mLastAnimatedPosition = RecyclerView.NO_POSITION;
public RecyclerViewScrollAnimator(RecyclerView recyclerView) {
mLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
mRecyclerView = recyclerView;
mInterpolator = InterpolatorUtil.loadInterpolator(recyclerView.getContext());
}
#Override
public void onAnimateViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
onPrepareToAnimateViewHolder(viewHolder);
if(shouldAnimateOrPrepare(position)){
ViewCompat.animate(viewHolder.itemView)
.alpha(1)
.setInterpolator(mInterpolator)
.setDuration(300).start();
}
mLastAnimatedPosition = position;
}
private boolean shouldAnimateOrPrepare(int position) {
return mLastAnimatedPosition == RecyclerView.NO_POSITION || mLastAnimatedPosition < position;
}
#Override
public void onPrepareToAnimateViewHolder(RecyclerView.ViewHolder viewHolder) {
// do some stuff if needed before animation
if(shouldAnimateOrPrepare(position)){
ViewCompat.setAlpha(viewHolder.itemView, 0);
}
}
//clear animation if needed
private void cancelAnimationAt(int position) {
RecyclerView.ViewHolder v = mRecyclerView.findViewHolderForPosition(position);
if(!isVisible(position) || v != null){
if(v != null) {
Log.i("cancelAnimationAt", " canceling "+position);
//cancelAnimation(v.itemView);
}
}
}
private void cancelAnimation(View v) {
if(v != null) {
v.clearAnimation();
v.setAnimation(null);
ViewCompat.animate(v).cancel();
}
}
private boolean isVisible(int position){
return position >= mLayoutManager.findFirstVisibleItemPosition() && position <= mLayoutManager.findLastVisibleItemPosition();
}
}
And here is how you use it in your RecyclerView.Adapter
#Override
public void onBindViewHolder(RecyclerView.ViewHolder vh, int position) {
if(getItemViewType(position) == VIEW_TYPE_ITEM) {
bindItem(vh, position-1); // position only if no header otherwise position-1
mRecyclerViewAnimator.onAnimateViewHolder(vh, position);
}
else return; // don't bind the header
}
Now you should modify the code inside onAnimateViewHolder() to use your animation resource. That's trivial.
On your Toolbar toggling, I suggest you to use this library. It has really handy scroll callbacks for your purpose.