I'm struggling a little bit with RecyclerView.Adapter. The problem is that RecyclerView sometimes mixes up views, but not with the underlying data but with what is displayed. And it happens only on something around 5% of items on the screen. Sometimes TextViews got their text completely from outter space, for example it suppose to display '1' but it displays '50' but that number doesn't exist anywhere in the application because text is autoincremented while I'm creating items and I've got like 2 of them, also when I'm checking what items are in the database on which position everything is fine, what's more bizzare, when I debugged onBindViewHolder, the holder got all the right data, and set it to the views, but the screen got something different, so it seems like the errors are generated somewhere after onBindViewHolder. Just to be clear, I'm implementing all the methods getItemId, hasStableIds, getItem properly and they indeed return correct items for given positions in onBindViewHolder.
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
Item item = mProvider.getItem(position);
int stickerSize = (int) (screenWidth * Const.STICKER_GRID_PROP);
Picasso.with(mContext)
.load(Const.backgroundNoteIDs[item.backgroundId])
.into(holder.background);
Picasso.with(mContext)
.load(Const.stickerIDs[item.stickerId])
.resize(stickerSize, stickerSize)
.centerInside()
.into(holder.sticker);
int paddingHorizontal = (int) (screenWidth * Const.GRID_TEXT_PADDING_HORIZONTAL_PROP);
int paddingVertical = (int) (screenWidth * Const.GRID_TEXT_PADDING_VERTICAL_PROP);
switch(item.type) {
case Const.NOTE:
holder.noteText.setText(item.note.text);
holder.checklistItemsLayout.removeAllViews();
holder.checklistLines.setImageBitmap(null);
holder.noteText.setTextSize(screenWidth * Const.GRID_TEXT_SIZE_PROP);
holder.noteText.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
break;
case Const.CHECKLIST:
holder.noteText.setText(null);
holder.checklistItemsLayout.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
Iterator<ChecklistItem> it = item.checklist.checklistItems.iterator();
int i = 0;
while(it.hasNext() && i < 4)
{
ChecklistItem checklistItem = it.next();
final TextView textView = new TextView(mContext);
textView.setEllipsize(TextUtils.TruncateAt.END);
textView.setText(checklistItem.text);
int drawable;
if(checklistItem.checkStatement) {
drawable = R.drawable.checked;
} else {
drawable = R.drawable.unchecked;
}
Picasso.with(mContext)
.load(drawable)
.resize((int) (stickerSize * 0.25f), (int) (stickerSize * 0.25f))
.centerInside()
.into(new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Drawable d = new BitmapDrawable(mResources, bitmap);
textView.setCompoundDrawablesWithIntrinsicBounds(d, null, null, null);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
Picasso.with(mContext)
.load(R.drawable.karteczka_linie)
.resize( (int) (screenWidth * Const.MENU_BIG_BUTTON_PROP), (int) (screenWidth * Const.MENU_BIG_BUTTON_PROP))
.centerInside()
.into(holder.checklistLines);
textView.setTextSize(screenWidth * Const.GRID_TEXT_SIZE_PROP);
holder.checklistItemsLayout.addView(textView);
i++;
}
break;
default:
throw new IllegalArgumentException("onBindViewHolder got item without any type");
}
}
public Item getItem(int index) {
return mItems.get(index);
}
public static class ItemViewHolder extends RecyclerView.ViewHolder implements
ItemTouchHelperViewHolder {
public TextView noteText;
public ImageView background;
public ImageView checklistLines;
public ImageView sticker;
public LinearLayout checklistItemsLayout;
public ItemViewHolder(View itemView) {
super(itemView);
noteText = (TextView) itemView.findViewById(R.id.note_text);
background = (ImageView) itemView.findViewById(R.id.note_background);
checklistLines = (ImageView) itemView.findViewById(R.id.checklist_lines);
sticker = (ImageView) itemView.findViewById(R.id.note_sticker);
checklistItemsLayout = (LinearLayout) itemView.findViewById(R.id.checklist_items);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "onClick " + getItemId());
}
});
}
#Override
public void onItemSelected() {
itemView.setBackgroundColor(Color.LTGRAY);
}
#Override
public void onItemClear() {
itemView.setBackgroundColor(0);
}
}
Okey, so here's the solution, I forgot to remove all views from the checklistItemsLayout in the ViewHolder, which inflates one of two types of items I display, so everytime specific ViewHolder was reused, TextViews were added properly but they were stacked on old ones so they didn't had space to show up, but this happened only when ViewHolder was reused on the same type of the item.
You need to remember to reload every element from the ViewHolder in onBindViewHolder, otherwise You might have some leftovers from other items previously used in this ViewHolder like I did
Here's the fixed code:
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
...
switch(item.type) {
case Const.NOTE:
holder.noteText.setText(item.note.text);
holder.checklistItemsLayout.removeAllViews();
holder.checklistLines.setImageBitmap(null);
holder.noteText.setTextSize(screenWidth * Const.GRID_TEXT_SIZE_PROP);
holder.noteText.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
break;
case Const.CHECKLIST:
//This line was missing
holder.checklistItemsLayout.removeAllViews();
holder.noteText.setText(null);
holder.checklistItemsLayout.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
Iterator<ChecklistItem> it = item.checklist.checklistItems.iterator();
int i = 0;
while(it.hasNext() && i < 4)
{
...
}
}
}
Related
My title could not be very descriptive because of a character limit. My goal is that when I click an element INSIDE the recyclerView it prints out the RecyclerView position(basically index).
Using OnClick() with XML and regular OnClick methods just crash my app.
public class torrentAdapter extends RecyclerView.Adapter<torrentAdapter.MyViewHolder> {
private List<torrent> seznamTorrentov;
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView upSpeed, downSpeed, progress, name;
public ImageView delete, stanje;
public MyViewHolder(View view) {
super(view);
name = (TextView) view.findViewById(R.id.name);
upSpeed = (TextView) view.findViewById(R.id.upSpeed);
downSpeed = (TextView) view.findViewById(R.id.downSpeed);
progress = (TextView) view.findViewById(R.id.progress);
delete = (ImageView) view.findViewById(R.id.zbrisi);
stanje = (ImageView) view.findViewById(R.id.stanje);
}
}
public torrentAdapter(List<torrent> seznamTorrentov) {
this.seznamTorrentov = seznamTorrentov;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.torrenti_vrsta, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
torrent torrent = seznamTorrentov.get(position);
holder.name.setText(torrent.getName());
holder.delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
System.out.println("POZICIJA " + position);
}
});
holder.progress.setText(String.valueOf(torrent.getProgress() + "%"));
//TUKAJ ZAOKROŽUJEMO VREDNOSTI
//TRENUTNO NI PODPORE ZA GIGABITNE PRENOSE
//S SPREMENLJIVKAMA sizeUname in Dname spreminjamo, ali napišemo na koncu hitrosti downloada MB/S ali KB/s
int sizeD = 1024;
int sizeU = 1024;
String sizeUname = "KB/s";
String sizeDname = "KB/s";
if (torrent.getDownSpeed() > 1000000) {
sizeD = 1024 * 1024;
sizeDname = "MB/s";
}
if (torrent.getUpSpeed() > 1000000) {
sizeU = 1024 * 1024;
sizeUname = "MB/s";
}
holder.downSpeed.setText(String.valueOf(Math.round(torrent.getDownSpeed()) / sizeD) + sizeDname);
holder.upSpeed.setText(String.valueOf(Math.round(torrent.getUpSpeed()) / sizeU) + sizeUname);
//TODO: ZRIHTAJ,DA SE POJAVIJO SLIKE IN PROGRESS BAR
if (torrent.getDownSpeed() == 0 && torrent.getUpSpeed() == 0) {
holder.stanje.setImageResource(R.drawable.check);
}
if (torrent.getStanje().contains("Stopped") || torrent.getStanje().contains("Paused")) {
holder.stanje.setImageResource((R.drawable.stop));
}
if (torrent.getStanje().contains("Downloading")) {
holder.stanje.setImageResource(R.drawable.down);
}
if (torrent.getStanje().contains("Seeding")) {
holder.stanje.setImageResource(R.drawable.up);
}
holder.delete.setImageResource(R.drawable.delete);
}
#Override
public int getItemCount() {
return seznamTorrentov.size();
}
}
This is my RecyclerViewAdapter. Delete is the drawable I would like to OnClick listen. With my current implementation I get responses no matter where I click on the RecyclerView. This is an issue because I am supposed to have two different "buttons" that have their own functions when clicked.
If it's relevant, this is the error I get when I use OnClick with XML:
java.lang.IllegalStateException: Could not find method deleteTorrent(View) in a parent or ancestor Context for android:onClick attribute defined on view class android.support.v7.widget.AppCompatImageView with id 'zbrisi'
I think you have mentioned an property in your adapter layout xml tag with this id
zbrisi like this
android:onclick="deleteTorrent"
remove this as it will interfere with you current onClickListener method
if onclick is defined in the xml then it has to be implemented in the java
ie.
android:onclick="delete"
java must have
public void delete(){//codes here}
but i recommend not doing this as there is a better way which is to implement onClickListener on the view itself
If i understood you correctly, code above returns you wrong position? What is the position? 0? 1? Or each time that's different? Try to change "public void onBindViewHolder(MyViewHolder holder, final int position)" to "public void onBindViewHolder(MyViewHolder holder, int position)".
I have a recyclerview adapter that is based on inflating two different card layouts. This adapater is dynamically updated with new cards and therefore each of the cards in the adapter needs to be updated as well. For each of the cards, there are a progressbar showing random generated data.
However, the problem Im facing is when I remove one of the cards in my list, the reference for the deleted card is not removed (the onCreateViewHolder is not called and therefore does not update the views), instead the cards left are just writing over the layout of the "deleted" card. The text on the card is correct and it removes the correct item from the list, but the progress bar is still having the old reference for the old card, which makes it adding values that corresponds to the deleted card.
To illustrate the problem, I'm adding random data to each of the cards in the progressbar depending on the movement name. So regardless of the card, the movement card named "Open Hand" should always add random data between 0-20, "Close hand" 20-60" and "Pronation" 60-100".
Here is everything working as supposed after adding three cards:
After inserted three cards
But when I delete the two first cards "Open hand" and "Close hand", the "Pronation" card left is showing the data in progress bar that actually corresponds to the "Open hand" data range. So why isn't the referencing correct?
I've tried calling both onDataSetChange(); and notifyItemRemoved(pos);
After deleted two cards
My adapter code:
public class ParameterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<Movement> movements;
public ParameterAdapter() {
this.movements=movementList;
}
class AddCard extends RecyclerView.ViewHolder {
public AddCard(View inflate) {
super(inflate);
}
}
class Card extends RecyclerView.ViewHolder {
TextView movement;
TextView channel;
TextView remove;
ProgressBar strength;
TextView graph;
LinearLayout expandedView;
LineChart lineChart;
Movement mov;
public Card(View itemView, Movement m) {
super(itemView);
movement = (TextView) itemView.findViewById(R.id.movementText);
channel = (TextView) itemView.findViewById(R.id.channelNameText);
remove = (TextView) itemView.findViewById(R.id.removeCard);
strength = (ProgressBar) itemView.findViewById(R.id.progressBarStrength);
graph = (TextView) itemView.findViewById(R.id.graphTextButton);
expandedView = (LinearLayout) itemView.findViewById(R.id.detailsNr2);
lineChart = (LineChart) itemView.findViewById(R.id.linechartCard);
mov=m;
channel.setText(m.getChannel());
//Setup real time graph
setupLineChart(mov, lineChart, mov.getId());
// Start adding random data to the graph
startAddingRandomData();
}
public void startAddingRandomData() {
new Thread(
new Runnable() {
public void run() {
while (true) {
int generatedData;
// Depending on what type of movement, add slightly different generated random data
// (to be sure that the views are correctly updated when removed for instance)
if(mov.getSettingsType().equals(SettingsDbHelper.MOVEMENTS_STRING[0])){
// Add random generated data between the span 0-20
generatedData=generateRandomData(0,20);
} else if (mov.getSettingsType().equals(SettingsDbHelper.MOVEMENTS_STRING[1])) {
// Add random generated data between the span 20-60
generatedData=generateRandomData(20, 60);
} else {
// Add random generated data between the span 60-100
generatedData=generateRandomData(60, 100);
}
//Set the progress bar
strength.setProgress(generatedData);
// Add the random generated data to the graph
addEntry(mov, lineChart, generatedData);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
public int generateRandomData(int n1, int n2){
Random r = new Random();
return r.nextInt(n2 - n1) + n1;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View vSetting=null;
View vCard=null;
// If its the first position, then inflate the "add movement" card.
if(viewType==0) {
vSetting = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_parameter_cardview, parent, false);
return new AddCard(vSetting);
}else if(viewType>0) {
// Otherwise, if it's not the first position, then inflate a "movement card" and create the corresponding movement item
vCard = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview_movement, parent, false);
Movement m = movementList.get(viewType - 1);
return new Card(vCard, m);
}else {
// For some reason, this occurs
return null;
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder,final int position) {
// "Add movement" card
if(position==0){
((AddCard)holder).itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(getActivity(), AddMovement.class);
startActivityForResult(intent, ADD_MOVEMENT);
}
});
// "Movement" item card
} else if(position>0) {
final boolean isExpanded = position == mExpandedPosition;
((Card) holder).expandedView.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
((Card) holder).graph.setActivated(isExpanded);
((Card) holder).graph.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mExpandedPosition = isExpanded ? -1 : position;
notifyDataSetChanged();
}
});
// Get the type of movement
Movement m = movementList.get(position - 1);
// This lets the real-time graph view be created before actual adding any data.
m.setChartCreated(1);
//Update the textviews inside the card
((Card)holder).movement.setText(m.getMovement());
((Card)holder).channel.setText(m.getChannel());
((Card)holder).remove.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Movement m = movementList.get(position - 1);
//Remove the item from the list
remove(m);
// Get ID and settings type in order to update the entry
String id=m.getId();
String settingsType = m.getSettingsType();
//Update the entry
settingsdb.updateSetting(settingsType,id,"false");
}
});
}
}
// Remove item from the list
public void remove(Movement data) {
int position = movementList.indexOf(data);
movementList.remove(data);
notifyDataSetChanged();
// (+ 1 one to compensate for the first card)
notifyItemRemoved(position + 1);
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
#Override
public int getItemViewType(int position) {
//Return the position
return position;
}
#Override
public int getItemCount() {
// (+1 to compensate for the first card)
return movementList.size()+1;
}
}
I am having Recycler View. It's like a grid view. A total of 9 images in grid layout. If I click a image in any one of the above, that image have to change to an another image. If I click another image. Last one want to reset. Then the clicked image alone will change to highlighted image.
Here is my code...
holder.mLayout.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View view) {
//for (int i = 0; i < data_collection.size(); i++) {
holder.mLayout.setVisibility(View.VISIBLE);
holder.mHighLighted.setVisibility(View.GONE);
if (position == i) {
}
//}
holder.mLayout.setVisibility(View.GONE);
holder.mHighLighted.setVisibility(View.VISIBLE);
mHighLight.onHighLight(position, view);
}
});
Remove what you dont need.
#Override
public void onBindViewHolder(final SimpleViewHolder holder, final int position) {
holder.textView.setText(elements.get(position).getName());
holder.textView.setTypeface(typeface1);
CircularImageView circularImageView = (CircularImageView) holder.linearLayout.findViewById(R.id.personazhe_layout_grid_item_image);
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// circularImageView.setBackground(elements.get(position).getPhoto());
// }
circularImageView.setImageDrawable(elements.get(position).getProfileImage());
//Picasso.with(context).load(elements.get(position).getProfileImage()).into(circularImageView);
holder.linearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(pos != position){
c.setImageDrawable(elements.get(position).getProfileImage());
t.setText(elements.get(position).getName());
seekBar.setProgress(0);
pos = position;
}
//image = elements.get(position).getProfileImage();
// textviews
// trajneri = elements.get(position).getTrajneri();
// mosha = elements.get(position).getMosha();
// vendbanimi = elements.get(position).getVendbanimi();
// vendlindja = elements.get(position).getVendlindja();
// arsimi = elements.get(position).getArsimi();
// name = elements.get(position).getName();
// surname = elements.get(position).getSurname();
// pos = elements.get(position).number();
// posi = position;
// button.performClick();
}
});
}
The ViewHolder pattern is something that Android pushed developers to use for a long time, and then (rightfully) forced on them with RecyclerViews. The idea, opposed to a simple ListView, is that you reuse as much of the view as possible when scrolling to reduce inflation and resource identification. The ViewHolder should be managed as something that is changed/not created within the RecyclerView.
Because of that, storing information in a ViewHolder that must be persistent will not work. For that, there are a plethora of other options. Let's go with an inner class that will manage holding onto the currently selected view position and its relative images.
Let's say we have a custom ViewHolder like below:
public class ImageViewHolder extends RecyclerView.ViewHolder{
private ImageView iv;
public ImageViewHolder(View v){
iv = (ImageView) v.findViewById(R.id.iv);
}
public ImageVie getImageView(){
return iv;
}
}
And utilizing that view holder is an adapter DemoAdapter, we can modify it to look something like this:
public class DemoAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
public interface SelectionListener{
void onImageSelected(Bitmap bmp);
}
private static class SelectionHolder{
protected int position;
protected Bitmap originalBmp, newBmp;
public SelectionHolder(int position, Bitmap originalBmp,
Bitmap newBmp){
this.position = position;
this.originalBmp = originalBmp;
this.newBmp = newBmp
}
}
private SelectionHolder selectionHolder;
private SelectionListener selectionListener;
/*
Pre-existing Adapter functionality
*/
public void setSelectionListener(SelectionListener listener){
selectionListener = listener;
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
/*
Pre-existing onBindViewHolder code
*/
ImageView iv = holder.getImageView();
if(selectionHolder != null && selectionHolder.position == position)
iv.setImageBitmap(selectionHolder.newBmp);
else{
//set the image however you are doing it now
}
iv.setOnClickListener(
new new View.OnClickListener() {
#Override
public void onClick(View v) {
ImageView iv = (ImageView) v;
// Get the IV's current bmp
Bitmap originalBmp = getBitmapFromImageView(iv);
// Get the currently selected image's "new" image
// if it is null, set it to the original bmp
// this will initialize our "highlighting"
Bitmap newBmp = selectionHolder == null || selectionHolder.newBmp == null?
originalBmp: selectoinHolder.newBmp;
// set the selection holder
selectionHolder = new SelectionHolder(position, originalBmp, newBmp);
// notify our listener
if(selectionListener != null)
selectionListener.onImageSelected(bmp);
// refresh the adapter
DemoAdapter.this.notifyDataSetChanged();
}
});
}
private Bitmap getBitmapFromImageView(ImageView iv){
return ((BitmapDrawable)(iv.getDrawable()).getBitmap()
}
}
Then if we have an activity that needs the selected image, perhaps to display it in an ImageView it hosts
recyclerAdapter = new DemoAdapter(...);
recyclerAdapter.setSelectionListener(new SelectionListener(){
#Override
public void onImageSelected(Bitmap bmp){
// set the bmp to your image view or whatever you want
}
}
I have problems with RecyclerView when I try to loop adding more child views to the parent view. When I scroll, it appears blank for a second. Is from Data binding or the view rendering?
Here is my code:
public class TournamentFixtureAdapter extends LoadMoreRecyclerViewAdapter<FixtureGroup> {
private OnFixtureClickListener onFixtureClickListener = null;
public TournamentFixtureAdapter(List<FixtureGroup> data) {
super(data);
}
#Override
protected RecyclerView.ViewHolder onCreateContentItemViewHolder(ViewGroup parent, int contentViewType) {
return new TournamentFixtureHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_tournament_fixture, parent, false));
}
#Override
protected void onBindContentItemViewHolder(RecyclerView.ViewHolder holder, int position) {
super.onBindContentItemViewHolder(holder, position);
FixtureGroup fixtureGroup = data.get(position);
((TournamentFixtureHolder) holder).onFixtureClickListener = onFixtureClickListener;
((TournamentFixtureHolder) holder).parentPos = position;
((TournamentFixtureHolder) holder).binding.setFixtureGroup(fixtureGroup);
((TournamentFixtureHolder) holder).addFixtures(fixtureGroup.getFixtures());
}
public void setOnFixtureClickListener(OnFixtureClickListener onFixtureClickListener) {
this.onFixtureClickListener = onFixtureClickListener;
}
static class TournamentFixtureHolder extends FixtureHolder {
ListItemTournamentFixtureBinding binding = null;
public TournamentFixtureHolder(View itemView) {
super(itemView);
binding = DataBindingUtil.bind(itemView);
}
}
}
public class FixtureHolder extends BaseAdapter.BaseHolder {
LinearLayout layoutMain = null;
OnFixtureClickListener onFixtureClickListener = null;
int parentPos;
public FixtureHolder(View itemView) {
super(itemView);
layoutMain = (LinearLayout) itemView.findViewById(R.id.layout_main);
setIsRecyclable(layoutMain.getChildCount() > 0);
}
public void addFixtures(final ArrayList<Fixture> fixtures) {
for (final Fixture fixture : fixtures) {
LinearLayout parent = (LinearLayout) LayoutInflater.from(itemView.getContext()).inflate(R.layout.view_fixture, null);
Utils.getDefaultClubLogo((NetworkImageViewPlus) parent.findViewById(R.id.netview_home_img)).setImageUrl(fixture.getHome().getImg(), AppController.getInstance().getImageLoader());
Utils.getDefaultClubLogo((NetworkImageViewPlus) parent.findViewById(R.id.netview_away_img)).setImageUrl(fixture.getAway().getImg(), AppController.getInstance().getImageLoader());
ViewFixtureBinding binding = DataBindingUtil.bind(parent);
layoutMain.addView(parent);
binding.setFixture(fixture);
parent.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onFixtureClickListener.onFixtureClick(parentPos, findFixturePosById(fixtures, fixture.getId()));
}
});
}
}
private int findFixturePosById(ArrayList<Fixture> fixtures, int id) {
for (int i = 0; i < fixtures.size(); i++) {
if (fixtures.get(i).getId() == id) {
return i;
}
}
return 0;
}
}
If the problem was more on the data side (adapter), then it would probably be showing up on the normal layout, not just during scrolling. For instance, if you were loading images from a slow server, the initial display would be slow. Since it's only happening when you scroll, that points more to a problem with the layout manager.
For every new view, you have to get it from the adapter, and add it to the layout. If you allow maximum dx in horizontal/vertical scrolling, and have recycled views outside of the screen display cached, it's likely that things will appear blank prior the layout manager getting the new views from the adapter and laying them out.
So there are two factors - horizontal and/or vertical dx is too large, too soon, and the number of recycled (or scrapped) views is too small. So the solution is to either slow down scrolling, or to increase the number of views you are adding off-screen.
It's possible to use expandable list items with new RecyclerView? Like ExpandableListView?
This is simple to do with the stock LayoutManagers, it all depends on how you manage your adapter.
When you want to expand a section you just add new items to your adapter after the header. Remember to call notifyItemRangeInserted when you do this. To collapse a section you simply remove the relevant items, and call notifyItemRangeRemoved(). For any data changes that are appropriately notified, the recycler view will animate the views. When adding items, an area to be filled with the new items is made, with the new items fading in. Removal is the opposite. All you need to do besides the adapter stuff is to style your views to convey the logical structure to the user.
Update: Ryan Brooks has now written an article on how to do this.
Get the sample code implementation from here
Set ValueAnimator inside onClick of ViewHolder
#Override
public void onClick(final View view) {
if (mOriginalHeight == 0) {
mOriginalHeight = view.getHeight();
}
ValueAnimator valueAnimator;
if (!mIsViewExpanded) {
mIsViewExpanded = true;
valueAnimator = ValueAnimator.ofInt(mOriginalHeight, mOriginalHeight + (int) (mOriginalHeight * 1.5));
} else {
mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(mOriginalHeight + (int) (mOriginalHeight * 1.5), mOriginalHeight);
}
valueAnimator.setDuration(300);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
view.getLayoutParams().height = value.intValue();
view.requestLayout();
}
});
valueAnimator.start();
}
Here is the final code
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView mFriendName;
private int mOriginalHeight = 0;
private boolean mIsViewExpanded = false;
public ViewHolder(RelativeLayout v) {
super(v);
mFriendName = (TextView) v.findViewById(R.id.friendName);
v.setOnClickListener(this);
}
#Override
public void onClick(final View view) {
if (mOriginalHeight == 0) {
mOriginalHeight = view.getHeight();
}
ValueAnimator valueAnimator;
if (!mIsViewExpanded) {
mIsViewExpanded = true;
valueAnimator = ValueAnimator.ofInt(mOriginalHeight, mOriginalHeight + (int) (mOriginalHeight * 1.5));
} else {
mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(mOriginalHeight + (int) (mOriginalHeight * 1.5), mOriginalHeight);
}
valueAnimator.setDuration(300);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
view.getLayoutParams().height = value.intValue();
view.requestLayout();
}
});
valueAnimator.start();
}
}
https://github.com/gabrielemariotti/cardslib
This library has an implementation of an expandable list with a recyclerview (refer to the demo app under "CardViewNative" --> "List, Grid, and RecyclerView" --> "Expandable cards"). It also has a lot of other cool combinations of cards/lists.
Someone complained about that the above mentioned solution is not usable with a listview as expandable content. But there's a simple solution: create a listview and fill this listview manually with your rows.
Solution for the lazy ones: there's a simple solution if you don't want to change your code to much. Just manually use your adapter to create views and add them to the LinearLayout.
Here's the example:
if (mIsExpanded)
{
// llExpandable... is the expandable nested LinearLayout
llExpandable.removeAllViews();
final ArrayAdapter<?> adapter = ... // create your adapter as if you would use it for a ListView
for (int i = 0; i < adapter.getCount(); i++)
{
View item = adapter.getView(i, null, null);
// if you want the item to be selectable as if it would be in a default ListView, then you can add following code as well:
item.setBackgroundResource(Functions.getThemeReference(context, android.R.attr.selectableItemBackground));
item.setTag(i);
item.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// item would be retrieved with:
// adapter.getItem((Integer)v.getTag())
}
});
llExpandable.addView(item);
}
ExpandUtils.expand(llExpandable, null, 500);
}
else
{
ExpandUtils.collapse(llExpandable, null, 500);
}
helper functions: getThemeReference
public static int getThemeReference(Context context, int attribute)
{
TypedValue typeValue = new TypedValue();
context.getTheme().resolveAttribute(attribute, typeValue, false);
if (typeValue.type == TypedValue.TYPE_REFERENCE)
{
int ref = typeValue.data;
return ref;
}
else
{
return -1;
}
}
helper class: ExpandUtils
Kavin Varnan postet already how to animate a layout...
But if you want to use my class, feel free to do so, I posted a gist: https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a
You can use ExpandableLayout that like a smooth expand/collapse animation CheckBox, so you can use it as CheckBox in ListView and RecyclerView.
https://github.com/KyoSherlock/ExpandableLayout
This is the sample code for what is mentioned by #TonicArtos to add and remove Items and to animate it while doing, this is taken from RecyclerView Animations and GitHub sample
1) Add Listener inside your onCreateViewHolder() to register for onClick
2) Create your custom OnClickListener inside your Adapter
private View.OnClickListener mItemListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
TextView tv = (TextView) v.findViewById(R.id.tvItems);
String selected = tv.getText().toString();
boolean checked = itemsList.get(recyclerView.getChildAdapterPosition(v)).isChecked();
switch (selected){
case "Item1":
if(checked){
deleteItem(v);
itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(false);
}else {
addItem(v);
itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(true);
}
break;
case "Item2":
if(checked){
deleteItem(v);
itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(false);
}else {
addItem(v);
itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(true);
}
break;
default:
//In my case I have checkList in subItems,
//checkItem(v);
break;
}
}
};
3) Add your addItem() and deleteItem()
private void addItem(View view){
int position = recyclerView.getChildLayoutPosition(view);
if (position != RecyclerView.NO_POSITION){
navDrawItems.add(position+1,new mObject());
navDrawItems.add(position+2,new mObject());
notifyItemRangeInserted(position+1,2);
}
}
private void deleteItem(View view) {
int position = recyclerView.getChildLayoutPosition(view);
if (position != RecyclerView.NO_POSITION) {
navDrawItems.remove(position+2);
navDrawItems.remove(position+1);
notifyItemRangeRemoved(position+1,2);
}
}
4) If your RecyclerViewAdapter is not in the same Activity as Recycler View, pass instance of recyclerView to the Adapter while creating
5) itemList is a ArrayList of type mObject which helps maintain states of item (Open/Close) , name, type of Item(subItems/mainItem) and set Theme based on values
public class mObject{
private String label;
private int type;
private boolean checked;
}