Enabled hardware acceleration causes flickering when scrolling a RecyclerView with multiple images - android

I have Android application with Navigation drawer with some checkboxes and main content of the application in scrollable RecyclerView with multiple images and some buttons at the bottom of the activity.
When I disable hardware acceleration with:
<application android:hardwareAccelerated="false">
in my AndroidManifest.xml file, my RecyclerView is scrolling smoothly, but animations of checkboxes and buttons are displayed only partially or not at all.
Hardware acceleration disabled, smooth scrolling, animations are not displaying correctly
When I enable hardware acceleration with:
<application android:hardwareAccelerated="true">
my RecyclerView flickers while scrolling the same number of images, but animations of checkboxes and buttons are displayed correctly.
Hardware acceleration enabled, flickering while scrolling, animations are displayed correctly
Flickering is much more noticeable in real life.
When I reduced the number of images displayed, flickering does not appear with hardware acceleration enabled, but when hardware acceleration is turned off, flickering does not appear regardless of the number of images.
Is there any way to display animations correctly with hardware acceleration disabled or to scroll smoothly with hardware acceleration enabled?
Edit
Adapter
public class Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<DataHolder> dataList;
public Adapter(List<DataHolder> data) {
this.dataList=data;
}
public static class ViewHolderImg extends RecyclerView.ViewHolder {
private TextView titleView;
private ImageView imageView;
private TextView commentNumberView;
private TextView voteNumberView;
Context context = itemView.getContext();
public ViewHolderImg(View view) {
super(view);
titleView= view.findViewById(R.id.title_img);
imageView= view.findViewById(R.id.image);
commentNumberView= view.findViewById(R.id.comment_img);
voteNumberView= view.findViewById(R.id.vote_img);
}
public TextView getTitleView() {
return titleView;
}
public ImageView getImageView() {
return imageView;
}
public TextView getCommentNumberView() {
return commentNumberView
}
public TextView getVoteNumberView() {
return voteNumberView;
}
}
public static class ViewHolderVideo extends RecyclerView.ViewHolder {
private TextView titleView;
private VideoView videoView;
private TextView commentNumberView;
private TextView voteNumberView;
Context context = itemView.getContext();
public ViewHolderVideo(View view) {
super(view);
titleView= view.findViewById(R.id.title_video);
videoView = view.findViewById(R.id.video);
commentNumberView= view.findViewById(R.id.comment_video);
voteNumberView= view.findViewById(R.id.vote_video);
}
public TextView getTitleView() {
return titleView;
}
public VideoView getVideoView() {
return videoView;
}
public TextView getCommentNumberView() {
return commentNumberView;
}
public TextView getVoteNumberView() {
return voteNumberView;
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if(viewType==0) {
View imageView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_img, parent, false);
view = imageView;
return new ViewHolderImg(view);
} else {
View videoView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_video, parent, false);
view = videoView;
return new ViewHolderVideo(view);
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
DataHolder dataHolder = dataList.get(position);
if (holder.getItemViewType()==0) {
((ViewHolderImg)holder).getTitleView().setText(dataHolder.getTitle());
GlideApp.with(((ViewHolderImg)holder).context)
.load(dataHolder.getUrl())
.into(((ViewHolderImg)holder).getImageView());
((ViewHolderImg)holder).getCommentNumberView().setText(dataHolder.getCommentNumber());
((ViewHolderImg)holder).getVoteNumberView().setText(dataHolder.getVoteNumber());
} else {
((ViewHolderVideo)holder).getTitleView().setText(dataHolder.getTitle());
Uri uri = Uri.parse(dataHolder.getUrl());
MediaController controller = new MediaController(((ViewHolderVideo) holder).context);
((ViewHolderVideo)holder).getVideoView().setVideoURI(uri);
controller.setMediaPlayer(((ViewHolderVideo) holder).getVideoView());
((ViewHolderVideo)holder).getVideoView().setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mp.setLooping(true);
((ViewHolderVideo)holder).getVideoView().setMediaController(controller);
controller.setAnchorView(((ViewHolderVideo)holder).getVideoView());
((ViewHolderVideo)holder).getVideoView().setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
}
});
((ViewHolderVideo)holder).getCommentNumberView().setText(dataHolder.getCommentNumber());
((ViewHolderVideo)holder).getVoteNumberView().setText(dataHolder.getVoteNumber());
}
}
#Override
public int getItemCount() {
return dataList.size();
}
#Override
public int getItemViewType(int position) {
int type;
type = ((dataList.get(position).isImage())?0:1);
return type;
}
}

Related

Setup span in StaggeredGridLayoutManager

I need to create a view like the following (this is an iOS screenshot since I've already created it).
Each blue box is part of a linear layout of a RecyclerView, while each green item is part of a staggered layout.
I'm able to create that structure. The problem is that I cannot make each gray box half of the yellow box. Using the StaggeredGridLayoutManager I'm not able to control the span (size) of the boxes that appear for the inner recycler view. Only a GridLayoutManager has the chance to set a span size lookup.
The desiderata is to calculate the boxes based on the with of the container. For example: if the screen is 99, the yellow box should be 66 while the gray 33.
Do you have any suggestions on how to achieve this?
Here a snippet of the code I'm using:
public class BoxViewAdapter extends RecyclerView.Adapter<BoxViewAdapter.BoxViewHolder> {
private List<Box> mValues;
private Context mContext;
protected BoxViewAdapter.ItemListener mListener;
public BoxViewAdapter(Context context, List<Box> values, BoxViewAdapter.ItemListener itemListener) {
mValues = values;
mContext = context;
mListener = itemListener;
}
#Override
public BoxViewAdapter.BoxViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.box_view_item, parent, false);
return new BoxViewAdapter.BoxViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull BoxViewAdapter.BoxViewHolder holder, int position) {
holder.bind(mValues.get(position));
}
#Override
public int getItemCount() {
return mValues.size();
}
public interface ItemListener {
void onItemClick();
}
public class BoxViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public Box item;
private RecyclerView recyclerView;
public BoxViewHolder(View v) {
super(v);
v.setOnClickListener(this);
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.HORIZONTAL);
recyclerView = v.findViewById(R.id.cardRecyclerView);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
}
public void bind(Box item) {
this.item = item;
recyclerView.setAdapter(new CardViewAdapter(itemView.getContext(), item.cards, null));
}
#Override
public void onClick(View view) {
if (mListener != null) {
mListener.onItemClick();
}
}
}
}
In the meantime, I've resolved this way.
In onBindViewHolder of CardViewAdapter I simply set LayoutParams for itemView. Since the item view is the rendered inside a RecyclerView, StaggeredGridLayoutManager knows how to deal with it.
#Override
public void onBindViewHolder(#NonNull CardViewHolder viewHolder, int position) {
if (mUseLayoutParameters) {
int singleScreenWidth = DisplayUtils.getScreenWidth() / 3;
Card card = mValues.get(position);
if (card.isDoubleSized) {
singleScreenWidth = singleScreenWidth * 2;
}
StaggeredGridLayoutManager.LayoutParams layoutParams = new StaggeredGridLayoutManager.LayoutParams(singleScreenWidth, ViewGroup.LayoutParams.MATCH_PARENT);
viewHolder.itemView.setLayoutParams(layoutParams);
}
viewHolder.bind(mValues.get(position));
}

RecyclerView onClick is not working at all

I'm trying to set an onclick method in my RecyclerView. It worked on another project but not here. I searched on the web but can't figure it out. Not even the toast is popping up. Can someone, please, explain to me where I am making a mistake?
Here's my code:
private Context mContext;
private MenuImages[] mMenuImages;
public MenuAdapter(Context context, MenuImages[] menuImages){
mContext = context;
mMenuImages = menuImages;
}
#Override
public MenuViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.menu_list_item, parent, false);
MenuViewHolder viewHolder = new MenuViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(MenuViewHolder holder, int position) {
holder.bindMenu(mMenuImages[position]);
}
#Override
public int getItemCount() {
return mMenuImages.length;
}
public class MenuViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener{
public ImageView mImageView;
public TextView mTextView;
public MenuViewHolder(View itemView) {
super(itemView);
mImageView = (ImageView) itemView.findViewById(R.id.itemImageView);
mTextView = (TextView) itemView.findViewById(R.id.textView2);
itemView.setOnClickListener(this);
}
public void bindMenu(MenuImages menuImage){
mImageView.setImageBitmap(menuImage.getImageMenu());
mTextView.setText(menuImage.getTitleImageMenu());
}
#Override
public void onClick(View v) {
Toast.makeText(v.getContext(), getLayoutPosition(), Toast.LENGTH_LONG).show();
}
}
in my case I was using RecyclerView in InterceptorCardView of other class wich was preventing onClickListener of recyclerview,
when I changed it with CardView and it worked perfectly
Note: I removed clickable = "false" ,focusable = "false".
it may be late but in your layout xml file you should add attribute clickable = "false" and also focusable = "false" to the children view ,textview and imageview in your case

recyclerview listview crash when scrolling

I'm using RecyclerView listview to show some list values.In this list I have two ImageView and one Textview. When I quickly scroll the list, app gets crashed. It's happened on some devices.Some devices it's worked but not smoothly scroll.
This is my adapter
public class MYAdapter extends RecyclerView.Adapter<MYAdapter.ItemViewHolder> {
private ArrayList<Data_Model> mItems;
private ClickListener clicklistener = null;
private Context context;
public RVAdapter(Context context,ArrayList<Data_Model> arrayList) {
this.context = context;
this.mItems = arrayList;
}
public class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView mTextView;
private ImageView imageview;
private ImageView mType;
public ItemViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
mTextView = (TextView) itemView.findViewById(R.id.list_item);
imageview = (ImageView) itemView.findViewById(R.id.image);
mType = (ImageView) itemView.findViewById(R.id.mtype);
}
#Override
public void onClick(View v) {
if (clicklistener != null) {
clicklistener.itemClicked(v, getAdapterPosition());
}
}
}
public void setClickListener(ClickListener listener) {
this.clicklistener = listener;
}
#Override
public void onBindViewHolder(ItemViewHolder itemViewHolder, int i) {
itemViewHolder.mTextView.setText(mItems.get(i).getTitle());
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeFile(mItems.get(i).getImage(),bmOptions);
itemViewHolder.imageview.setImageBitmap(bitmap);
{
Bitmap bitmaps = BitmapFactory.decodeResource(context.getResources(),R.drawable.music);
itemViewHolder.mType.setImageBitmap(bitmaps);
}
#Override
public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_row, viewGroup, false);
ItemViewHolder itemViewHolder = new ItemViewHolder(view);
return itemViewHolder;
}
#Override
public int getItemCount() {
return mItems.size();
}
}
this is my list
Error Message
make sure to recyler image when view onViewRecycled
#Override
public void onViewRecycled(ItemViewHolder holder) {
holder.imageView.setImageBitmap(null);
}
your recyclerview most likely still not smooth enough when scrolled, since you load image synchronously in main thread.
but I think it can prevent crash at some point. so, I suggest you to use image loader library like Picasso or Glide to make sure image is loaded asynchronously and make scrolling in your recyclerview way more smoother.
#Override
public void onBindViewHolder(ItemViewHolder itemViewHolder, int i) {
itemViewHolder.mTextView.setText(mItems.get(i).getTitle());
Picasso.with(itemViewHolder.imageview.getContext()).load(mItems.get(i).getImage()).into(itemViewHolder.imageView);
itemViewHolder.imageview.setImageBitmap(bitmap);
}

Selected image in recyclerview is get unselected upon scroll

In my recyclerview has one imageview and textview. I'm changing image of ImageView onn onClickListener. Now the problem is if i click on image of position 3 and scroll down... image of position 8 is also changed and again if i scroll up...image of position 2 is changed.
public class PortraitListviewAdapter extends RecyclerView.Adapter<PortraitListviewAdapter.ViewHolder> {
Context context;
static List<PortraitParentListAdapterBean> list;
static List<String> selectedPosition ;
public PortraitListviewAdapter(Context context, List<PortraitParentListAdapterBean> list) {
this.context = context;
this.list = list;
selectedPosition = new ArrayList<>();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.listview_parent_portrait, parent, false);
return new ViewHolder(itemView);
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
PortraitParentListAdapterBean portBean = list.get(position);
Log.i("pos",position+"");
holder.parentHeading.setText(portBean.getHeading());
if (selectedPosition.contains(list.get(position).getHeading())){
holder.parentImage.setImageResource(R.drawable.sad);
}
}
#Override
public int getItemCount() {
return list.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
protected TextView parentHeading;
protected ImageView parentImage;
public ViewHolder(View itemView) {
super(itemView);
parentHeading = (TextView)itemView.findViewById(R.id.parent_heading);
parentImage = (ImageView)itemView.findViewById(R.id.imageView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
selectedPosition.add(list.get(getAdapterPosition()).getHeading());
parentImage.setImageResource(R.drawable.sad);
}
});
}
}
}
the above code is my implementation for recyclerview adapter. please help to understand the concept.
SOLUTION
change
if (selectedPosition.contains(list.get(position).getHeading())){
holder.parentImage.setImageResource(R.drawable.sad);
}
with
if (selectedPosition.contains(list.get(position).getHeading())){
holder.parentImage.setImageResource(R.drawable.sad);
} else {
holder.parentImage.setImageResource(R.drawable.your_default_drawable);
}
EXPLANATION
When you scroll your RecyclerView the system doesn't recreate always your ViewHolder but reuse one you previously scrolled that is no longer visible, so you need to reset your standard values in order to avoid showing wrong values.

something weird happens while scrolling RecyclerView too fast

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.

Categories

Resources