I have a simple horizontal recyclerview. As the user scrolls, the current item looses focus has background set to transparent and the new focused item is to have its background color changed to green. I have a simple method that takes in the focused position, changes it's color and sets the reset of the items to transparent.
public void resetRecycleColor(int rowindex){
for(int i=0; i < mRecyclerView.getAdapter().getItemCount(); i++){
if(i== this.rowindex){
Log.v("SCROLLS ", "COLOR GREEN "+ i);
mRecyclerView.getLayoutManager().findViewByPosition(i).setBackgroundColor(Color.GREEN);
}else{
Log.v("SCROLLS ", "COLOR TRANSPARENT "+i );
mRecyclerView.getLayoutManager().findViewByPosition(i).setBackgroundResource(R.color.gridBackgroundBlack);
}
}
}
Am I attempting to change the item background correctly? Something is wrong because it crashes on the line where the color is set. Comment out those lines and scrolling works fine. Can somebody point out why the crash may occur?
Thanks
You don't seem to understand the ViewHolder pattern.
You only have enough views (plus about 2) to fill RecyclerView. For example if you have 2000 items but they are represented in list that shows 3 items each, you only have 5 ViewHolders created. That way when scrolling views that exit the screen, reenter from the other side but displayed values are changed (in OnBindViewHolder).
Crash is occurring in findViewByPosition(i) since you're trying to get views for entire list but you only have views for a few of them, so this returns null.
I don't understand your use case entirely, but if you're repeatedly calling your method during onScroll then it has really bad performance, cleaner solution would be modifying your adapters onBindViewHolder to properly modify focused view and calling notifyItemChanged() to trigger rebinds on new/old focused view.
Related
I have a recylcerview where the method shown below gets called on each selection of an item. The method only runs sucessfully when I do not scroll the recyclerview, so the firstvisible is 0. The moment I scroll down a bit and select something the first and last visible are set correctly in the method, but as can be seen in the screenshot, for reasons I do not understand the childAt returns null instead of the view I can see on my app screen. In the screenshot for position 7, the first 4-6 returned a child.
Can someone explain to me how this can happen? From my pov, getChildAt() should always return a view in this scenario.
first and last are going to be the adapter positions of the data and not the position as laid out in the layout manager. See LinearLayoutManager#findFirstVisibleItemPosition. The children will always start with zero and increase from there.
That is why it works before your scroll since the child at the zeroth index in the layout manager is also the zeroth item in the adapter.
Here is a discussion about the various positions in RecyclerView.
It looks like you want to make changes to all visible items. Your first and last variables will have the correct start/end adapter positions that correspond to what is visible on the screen. You need the adapter positions to call the various "notify" methods.
So, given the adapter positions, we need a map to the views that are represented on the screen. As an example, the following code loops through every visible view and changes the background color of each view.
LinearLayoutManager lm = (LinearLayoutManager) Recycler.getLayoutManager();
// Get adapter positions for first and last visible items on screen.
int firstVisible = lm.findFirstVisibleItemPosition();
int lastVisible = lm.findLastVisibleItemPosition();
for (int i = firstVisible; i <= lastVisible; i++) {
// Find the view that corresponds to this position in the adapter.
View visibleView = lm.findViewByPosition(i);
visibleView.setBackgroundColor(getResources().getColor(android.R.color.holo_red_light));
}
If you use the child methods of the layout manager, you will need to loop from zero to LayoutManager.getChildCount() - 1 to make the changes. You will see each attached view which, I believe, can exceed the number of visible views.
I'm trying to toggle the background color of a single item in a RecyclerView on click. I do this in the onClick function of a ViewHolder that implements OnClickListener. Here is the onClick implementation:
int position = getLayoutPosition();
if (selected.containsKey(position)) {
view.setBackgroundColor(context.getResources().getColor(R.color.white));
selected.remove(position);
} else {
view.setBackgroundColor(context.getResources().getColor(R.color.highlight));
selected.put(position, view);
}
The issue arises when I test this in a populated RecyclerView. If there are many items, selecting one at the start also highlights other items further in the list that are out of view. The other highlighted views are not added to the map 'selected'. Also, after scrolling away from selected views and returning to them, they sometimes change colors. Why does this happen, and how can I fix this?
That is a side effect of the views being recycled. They are literally being reused. Just make sure when your views are being bound you explicitly set the background color to the default color. That should do it.
I've got a GridView with a custom Adapter, displaying thumbnails of pictures to the user. I can select and deselect pictures (I use setAlpha(0.25) on the views to notify the user of the change), and that's all well and fine.
Now what I want to do next, is have a button on top of the gridview that clears the whole selection, i.e. call setAlpha(1.0) on all views that were changed. So far I can reset my elements in the adapter, and I can setAlpha to 1, but the view doesn't update unless I scroll it out of the display, and then get back to it, or notify the adapter of changes, which redraws all my views, which does not look too pretty if only one element was selected.
I already dynamically set and reset individual elements through the GridView's onClickListener, but can't do this for more. I even tried calling performClick on all selected items through my button, but again it only displays the changes after the views have been out of the screen and are shown again.
Here's how I simulate the clicks:
for (int i = 0; i < mAdapter.getCount(); i++) {
PictureForSelection tempPic = (PictureForSelection) mAdapter.getItem(i);
if (tempPic.isPicSelected()) {
//tempPic.setIfSelected(false);
gridview.performItemClick(mAdapter.getView(i, null, null), i, i);
}
}
EDIT:
Conclusion - don't simulate clicks like this :)
So now I skip click simulation and use this:
gridview.getChildAt(i).setAlpha((float) 1.0);
In general it does exactly what I wanted it to do, except for one case:
If I have for example 3 pictures selected, one is off screen, one is partly shown, and one is fully shown, the ones shown (even partially) don't update their views.
If all the selected pictures are displayed it works flawlessly, but if some are out of the display, the rest do not update until the adapter's getView() gets called by Android. Still, thanks #mmlooloo for getting me so far.
If anyone knows a way around this, please do share :)
After setting your alpha you can call imageview.invalidate(); this causes your imageview to redraw itself.
if notifyDataSetChanged() is not working you can try like this.
i'm not sure whether this works,
1.onclick of the button you set the gridView.setAdapter(null);
2. Then set a new CustomAdapter object to gridview with default values.
I'm having a weird behaviour in my application... A have a ListView, where odd items have #ffffff background and even items have #f2f7f7, and looks like this:
However, as soon as I start scrolling the list, the even items slightly change color, becoming #f7f7f7, like this:
Any ideas? If you need more info, please ask. I'm baffeld with this behaviour.
Additional info:
I'm setting each item's background color in getView() method, like this:
protected final int[] colors = new int[] { R.drawable.states_list_odd, R.drawable.states_list_even };
...
// Alternates list items' background
int colorPos = position % colors.length;
convertView.setBackgroundResource(colors[colorPos]);
Simple stuff, actually. android:scrollingCache="false" solved this issue.
The Views used for the items are being reused when you scroll the list (there's no need to create the new ones). So, when you scroll down the list, the item at position 0 may be rendered at position 10. It will be the same View object, and before showing getView will be called with convertView parameter set to this particular View - so you can fill it with data (and specify your styles). It's just an optimization.
So, you need to specify the background color for the item each time getView is called for it in your list adapter.
programmaticly try this. yourListView.setScrollingCacheEnabled(false);
I need all of the other views in my arrayadapter to check their attributes and return to a default attribute at certain times
use case: listview items (a)(b)(c)(d) , when you touch (a) it's background turns black. This is done in the ontouchListener. But when you touch view (b) it's background turns black BUT (a)'s needs to turn back to the default which is not black
I dabbled around with recalling the arrayadapter at the end of the view.OnClickListener but the problem with this is that it also resets the scroll position of the view, so if this list was much longer - which it is - and user touched item (r) , then it would reset the list and put the user back at item (a) at the top
I was looking at notifydatasetchangedbut I am not sure how to use that and where it should work
insight appreciated
notifyDataSetChanged will do the job. You can call that at any time in your code and this will trigger the adapter to call getView for the visible list items again and so update their content. So basically the only thing you have to do is to update the list item states or information before calling that method.