I'm using recyclerView to implement ExpandableList and I want when user clicks on parent view, other expanded parents collapse. It works nice with code below:
private void collapseOthers(int position) {
for (int i = 0; i < ol.size(); i++) {
OrderForShow o = ol.get(i);
if (i != position) {
if (!o.isChild()) {
if (o.getChilds() == null) {
RecyclerView.ViewHolder vh = rView.findViewHolderForAdapterPosition(i);
if (vh != null)
vh.itemView.findViewById(R.id.root_layer).performClick();
}
}
}
}
}
but when user scrolls down, upper items will be null and vh goes null and I can't collapse invisible item, Is there any way to handle this issue?
Try assigning ids to the layout (or item within layout) inside onBindViewHolder and make changes on basis of id only.
Another alternative is to maintain a HashMap<Integer,String> where Integer will be the position of the item and String can be used to store it's status like "visible" or "invisible".
This is because while scrolling, very often, internally RecyclerView does make some mess in maintaining the dirty views and maintain their ids. Better, we maintain it and take control of them.
This link explains how RecyclerView works.
Related
I am trying to get a ViewPager and a RecyclerView to work in sync in displaying an image. The ViewPager contains an ImageView which shows a full sized image and the recycler view shows thumbnails of previous and upcoming images.
If the user swipes on the ViewPager then the RecyclerView moves left/right in sync. If the user scrolls and then clicks a photo, the ViewPager sets that position.
This all works, just giving background to my app.
The part I am stuck on is showing a border around the thumbnail that is currently selected in the RecyclerView from onPageSelected. The element which is considered selected is the one that is currently show on the ViewPager
A lot of the other questions deal with this problem using the onClickListener. My onClickListener just calls
viewPager.setCurrentItem(index)
So any time an item is selected, onPageSelected is called, either naturally from the ViewPager or from the click event. So this is where I feel I need to set the border.
So far I'm using a bit of a hack since the number of images is only 20.
for (int i = 0, ii = recyclerViewAdapter.getItemCount(); i < ii; i++) {
//RecyclerView.ViewHolder holder = recyclerView.findViewHolderForItemId(recyclerViewAdapter.getItemId(i));
RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(position);
if (holder instanceof RecyclerViewAdapter.RecyclerViewHolder) {
if (i == position) {
...
((RecyclerViewAdapter.RecyclerViewHolder) holder).getViewHolderContainer().setBackground(border);
} else {
((RecyclerViewAdapter.RecyclerViewHolder) holder).getViewHolderContainer().setBackground(null);
}
}
}
This doesn't work. If get's the right position since I'm provided that by the ViewPager but neither method for retrieving the ViewHolder at 'position' seems to work.
So, after all that, my question is if anyone can recommend me a clean way (or really even a hack at this point) that will let me update the current position of an element based on the position from onPageSelected of the ViewPager.
Thank you kindly.
So I ended up getting this working using two interfaces. No ugly looping hacks required.
ViewPager.OnPageChangeListener and OnChildAttachStateListener.
Basically my issue was that if I scrolled away from the currently selected item in the RecyclerView then I couldn't unselect that item later because the ViewHolder was recycled or null. Basically I couldn't access the previous view to unselect it so multiple items would end up selected when you scroll through it.
So with the ViewPager it was easy.
#Override
public void onPageSelected(int position)
int previousItemPos = currentItemPos; //currentItemPos is a class attr
currentItemPos = position;
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation()
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270) { //portrait
portraitScroll(position) //scrolls the recyclerView to currently selected item based on viewWidth
} else if .. landscape do the same
changeItem(position, previousItemPos);
}
#Override
public void onChildViewAttachedToWindow(View view) {
int childPosition = recyclerView.getChildAdapterPosition(view);
if (childPosition == currentItemPos) {
highlightItem(view); //applies an effect
}
}
#Override
public void onChildViewDetachedToWindow(View view) {
unHighlightItem(view);
}
private void changeItem(int newItem, int oldItem) {
ViewHolder holder = recyclerView.findViewHolderForItemId(recyclerView.getItemId(newItem));
if (holder instance of RecyclerViewHolder) {
highlightItem((RecyclerViewHolder) holder).getViewHolderContainer());
}
ViewHolder oldHolder = recyclerView.findViewHolderForItemId(recyclerView.getItemId(oldItem));
if (oldHolder instance of RecyclerViewHolder) {
unHighlightItem((RecyclerViewHolder) holder).getViewHolderContainer());
}
So basically each time we scroll so that the currently selected item in the ViewPager goes out of sight, we unselect it when it is detached. If we scroll back to it and the position hasn't changed, we reselect it.
If the ViewPager changes item, first we scroll to that item based on the width of the each RecyclerViews view, then we select it in onPageSelected.
In my app I am using recycler view.I want to show and hide view on particular condition.But when I scroll recycler views I am not getting expected behaviour.When I Visible a view it gets visible for other rows as well randomly.
What I understand is when it recycles it reuses view and when previous view when it gets recycled it finds the visibility of that view.How can I hide view on particular condition? here is my adapter code
#Override
public void onBindViewHolder(UrduRhymesViewHolder holder, int position) {
RhymesModel current = mUrduRhymesList.get(position);
AppUtility.setCustomFont(mContext, holder.tvUrduRhymesName, Constants.HANDLEE_REGULAR);
holder.tvUrduRhymesName.setText(current.getRhymeName());
holder.ivUrduRhymesLogo.setImageUrl(current.getThumbnailUrl(), mImageRequest);
int status = AppUtility.getFavouriteStatus(mContext, current.getRhymeName(), new UrduRhymesDb(mContext));
if (status == 0)
holder.btnFavourite.setBackgroundResource(R.mipmap.btn_star_unactive);
else
holder.btnFavourite.setBackgroundResource(R.mipmap.btn_star);
ProgressbarDetails progressbarDetails = ProgressbarDetails.getProgressDetail(current.getRhymeName());
if (progressbarDetails == null) {
progressbarDetails = new ProgressbarDetails();
progressbarDetails.prgProgressBar = holder.pbRhymeDownload;
progressbarDetails.download_btn_settings = holder.downloadButtonLayout;
} else {
progressbarDetails.prgProgressBar = holder.pbRhymeDownload;
progressbarDetails.download_btn_settings = holder.downloadButtonLayout;
holder.pbRhymeDownload.setProgress(progressbarDetails.progress);
}
ProgressbarDetails.addUpdateProgressDetail(current.getRhymeName(), progressbarDetails);
if (progressbarDetails != null && progressbarDetails.isDownloading) {
Log.e("test","downloading foe position "+position );
holder.downloadButtonLayout.setBackgroundResource(R.mipmap.btn_download);
holder.pbRhymeDownload.setVisibility(View.VISIBLE);
holder.pbRhymeDownload.setProgress(progressbarDetails.progress);
} else {
Log.e("test","should not be visible for position "+position);
holder.pbRhymeDownload.setVisibility(View.GONE);
}
}
Here progressbarDetails.isDownloading (having value true) is the criteria when I want to show my view but is else clause it is not hiding my view
Edit: Here ProgressbarDetails (Singleton )is a class keeping reference of every row of adapter's progress bar.
No direct way of hiding and unhiding recylerview childitems.
Solution:
Let us assume that the recyclerview adapter is ArrayList
Now make another arraylist (temp_list)
Scenarios:
Hide: iterate through your adapter items and remove the ones that you want to hide. Put each of these into temp_list. After iteration is over, call notifyDataSetChanged()
Show: iterate through your temp_list items and remove the ones that you want to show. Put each of these into adapter. After iteration is over, call notifyDataSetChanged()
You should add a flag in your viewHolder that indicates if this view should be displayed or not . and check this flag every time in the onBindViewHolder.
because the recyclerView reuses the same views you should make a decision depending on something special for every view in you viewHolder.
do u mean when your data has been changed?
and your layout want to change ?
adapter.notifyDataSetChanged();
I followed this example here: https://developer.android.com/training/material/lists-cards.html
What's Happening:
The list (RecyclerView) is mixing up the data when I scroll. I.e. when I scroll back up after scrolling down, some of the list items are repeated, not displaying proper content.
Here is my onBindViewHolder method:
// cardsData is a List of MyCardItem (MyCardItem is just a holder of information,
// such as an id, String title, String summary, and other info to display on a card
#Override
public void onBindViewHolder(ViewHolder viewHolder, int position)
{
// Update view here
if (!cardsData.isEmpty())
{
viewHolder.updateDisplay(cardsData.get(position), activity, position);
}
}
Here is my updateDisplay method found inside the ViewHolder class:
// Set ID
if (item.getID() != null)
{
mIDView.setText(item.getID());
}
// Set Title
if (item.getTitle() != null)
{
mTitleView.setText(item.getTitle());
}
// Set Summary
if (item.getSummary() != null)
{
mSummaryView.setText(Html.fromHtml(item.getSummary()));
}
// Image
if (item.hasImage())
{
// Display image here
}
I believe it has something to do with the fact that sometimes the data is null, so I choose not to display it. Not all items (cards) will have an image to display, so this is why I check for if the item is supposed to have an image. If it does, then I do some fancy code to show an image (and making the other views GONE so I can only show that image, etc.).
While debugging, I forced all cards to display their id and position in the title. I can confirm that all ids and positions are in their proper places. When I force (removing the if check for id, title, and summary) the id, title, and summary to display, even if null, that content becomes unaffected. However, this does not solve my image situation (won't always have an image). I'll then have some images taking up the place for a certain id/position, where it should not (i.e. instead of displaying the summary text, it shows an image that isn't supposed to be there).
Any input as to why this is happening in my RecyclerView?
RecyclerView, just the same as ListView and GridView, reuses views whenever possible. That means while you scroll, views that fall off the top are used for the views on the bottom, etc. This greatly improves the memory efficiency and scroll performance. This means if you don't reset all of the state on a new item, you'll see state left over from the last time this view was used.
Instead, you should always reset all the of the state:
if (item.getID() != null) {
mIDView.setText(item.getID());
} else {
mIDView.setText("");
}
// etc...
Also, there shouldn't be any situations where you need to check for cardsData.isEmpty() - instead, whenever you update cardsData, you should be calling the appropriate notifyXXX method (such as notifyItemInserted() or in the worse case notifyDataSetChanged()) which will automatically refresh the list appropriately.
Implement
public long getItemId(int position) {}
otherwise, you'll see repeated items in RecyclerView.
Add these two and you are good to go !
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
Also in addition to previous answer you can check view on emptiness, for example:
if (TextUtils.isEmpty(viewHolder.item.getText())) {
viewHolder.item.setText(object.getItem());
}
To elaborate on ViliusK's answer. This implementation will work:
#Override
public long getItemId(int position) {
Object obj = getItemAtPosition(position);
return obj.hashCode();
}
I have a list of Items that are "seen" or "not seen" in ArrayList<Item>. If they're not seen I change the background color of the ListView item in my CustomArrayAdapter like this :
if(item.is_seen == null || item.is_seen == 0) {
row.setBackgroundResource(R.color.yellow);
} else {
row.setBackgroundResource(R.color.transparent);
}
Now what I want to do is set all items background to transparent after 3 seconds spent on the page.
I already tried to do something like this:
mScheduledExecutor.schedule(new Runnable() {
#Override
public void run() {
for(int i=0; i<mItems.size(); i++) {
final Item n = mItems.get(i);
if(n.is_seen == null || n.is_seen == 0) {
// update value in db
int isSeen = 1;
updateItem(n._id, isSeen);
// change the color of backgrounds
View view = listViewItem.getChildAt(i);
if(view != null) {
view.setBackgroundResource(R.color.red);
}
}
}
Updating the value in the DB works, but the rest doesn't. But I'd like to avoid to reload the data. I just need the color to change.
I don't have errors, it just does nothing.
I look everywhere for an answer and didn't find one.
Am I wrong since the beginning? Is what I want to achieve even possible?
I thank you in advance for all the help you could give me.
Instead of changing the color of the view like youre doing,
// change the color of backgrounds
View view = listViewItem.getChildAt(i);
if(view != null) {
view.setBackgroundResource(R.color.red);
}
(code above will not work, because the views are recycled in the ListAdapter) update the DATA off which you build your list - add a property to the class you are passing into your ListAdapter, then grab that instance from the list and update that property, you have the position at which it needs to be updated already, so that's easy. Then, call notifyDataSetChanged() on the list. It will not redraw the list if you didn't ADD/REMOVE items from list, but it will update correct view for you. This is the only way to do it - absolutely NO WAY to get to a view corresponding to a specific element in a list after list has been drawn already. Only way is to refresh/redraw the list with notifyDataSetChanged(), followed by refreshDrawableState().
I'm facing a very strange phenomenon. I'm using a ViewPager from the compatibility package to display profiles.
Every profile is just a ListView with custom elements in it. Every element has two states:
There is data - display it
There's no data - display place holder
Every time I swipe between profiles I reset the data within the item objects. When I remove the REST-Calls and just let the profile item empty - which should display the placeholder - all items keep blank.
What could be the problem?
EDIT:/
That's how I set the current item in the listview. The PageViewAdapter seems to be OK, if I use static content everything works perfect.
final TextView lblTitle = (TextView)convertView.findViewById(R.id.lblTitle);
final LinearLayout llContent = (LinearLayout)convertView.findViewById(R.id.llContent);
final ProfileItem item = getItem(userPosition);
String title = item.getTitle();
if(title == null || title.equals("")) {
lblTitle.setVisibility(View.GONE);
} else {
lblTitle.setText(item.getTitle());
}
if(item.getContent().getParent() != null) {
((ViewGroup)item.getContent().getParent()).removeAllViews();
}
llContent.removeAllViews();
llContent.addView(item.getContent());
The Items look all very similar like that:
#Override
public View getContent() {
if(isTextEmpty()) {
return noTextTherePlaceholder;
} else {
return view;
}
}
The effect keeps the same even if I return just view or noTextTherePlaceholder.
If I would instate new views like a TextView in the getView method everything works as expected.
With view pager you have to get all your data in your Activity.onCreate and then use that data in your ViewPagers adapter's onInstantiateItem.