i am building an app on android and am using several recyclerviews.
in the first page that i used a recyclerview, everything worked flawlessly and my custom ItemDecoration (divider) was applied to all views.
then i started a new page in which i use the same divider on a similar RecyclerView, but on the last list item the bottom divider is not present.
seeing that it worked perfectly in one place, i don't believe the issue is with the custom ItemDecoration class, nor with the xml. I also tried using the same viewholder for the one that worked, but still the last divider was not drawn.
here is the code where is set up my adapter in the problematic recyclerview:
private void setUpPracticesList() {
lstPractices = (RecyclerView) getActivity().findViewById(R.id.lstPractices);
lstPractices.setLayoutManager(new LinearLayoutManager(getActivity()));
RecyclerView.Adapter<PracticeHolder> adapter = new RecyclerView.Adapter<PracticeHolder>() {
String[] titles = getResources().getStringArray(R.array.practices_names);
int[] imageIds = {R.drawable.consultation_meeting_icon,
R.drawable.monitoring_meeting_icon,
R.drawable.parent_lectures_icon,
R.drawable.parent_support_groups_icon,
R.drawable.staff_courses_icon};
#Override
public PracticeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.kidsense_practices_list_item, parent, false);
return new PracticeHolder(v);
}
#Override
public void onBindViewHolder(PracticeHolder holder, int position) {
holder.setName(titles[position]);
holder.setImage(imageIds[position]);
}
#Override
public int getItemCount() {
return titles.length;
}
};
lstPractices.setAdapter(adapter);
lstPractices.setHasFixedSize(true);
lstPractices.addItemDecoration(new DividerItemDecoration(getActivity(), null));
}
does anyone have any ideas?
I have found the solution:
what i have found is that a recyclerview will not show a divider at the bottom of the last view if there is no more space left in the recyclerview.
in my case, the problem was that my recyclerview was in a layout that had a height of "wrap_content", meaning that there was no extra space left under the recyclerview to display the divider. as soon as i rearranged my layout so that my recyclerview had more space underneath with a parent layout height of "match_parent" the final divider was displayed.
Related
The app I am working on contains lots of listviews. In one case, I have a recyclerView that leverages the GridLayoutManager to create a two column view. I haven't worked with recyclerViews yet as far as adapters go but here is the problem I am having. Each item in the view is being sized to the same height despite having the appropriate wrap_content attributes. I guess my question would be, is there a trick to pull this off where each child element has a different height? Is something going on behind the scenes with a recyclerView that causes all children to have a fixed height of the tallest child element? Does this sound like a recycler problem with the view holders?
My adapter logic is as follows
public ClubViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.onboard_club, parent, false);
return new ClubViewHolder(itemView, mRecyclerClickListener);
}
public void onBindViewHolder(final ClubViewHolder holder, int position) {
Item club = clubList.get(position);
if (null != club) {
holder.clubTitleText.setText(club.getName());
holder.subtitleText.setText(context.getResources().getString(R.string.club_members,club.getNumberMembers()+""));
// Do some imageView logic here
}
}
Do I need to set height in here programmatically?
EDIT: To clarify further, the layout I inflate is the layout in question here. It is a Vertical LinearLayout containing an imageView, and two text views. I need the LinearLayout to wrap the children XML attributes but it currently isn't doing that despite the LL having a height of wrap_content and all children height set to wrap_content as well
SOLUTION: Solution was posted by a user below. I will leave the update here for anyone struggling in the future.
Layout I need: Two columns whose children vary in height
Tie it in with an adapter as you normally would.
Set a StaggeredGridLayoutManager on the recyclerView widget
In the manager constructor pass in a spanCount of 2 and an Orientation of Vertical
Boom, close the sprint ticket and forget about it
Thanks again Stack Community, cheers!
The best way to control placement and size of item for RecyclerView is through its LayoutManager.
If you're looking for GridView with elements that resize themselves according to the content you can use StaggeredGridLayoutManager
Nice example of Staggered Grid Layout can be found here.
On the other hand if you need just few "groups" of different Items you can inflate different ViewHolders depending on some criteria. Here is the sample code for two types of views.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == FILE_FLAG) {
ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext())
.inflate(R.layout.file_item, parent, false);
return new FileViewH(view);
} else if (viewType == DIR_FLAG) {
ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext())
.inflate(R.layout.dir_item, parent, false);
return new DirViewH(view);
}
}
I am using a StaggeredGridLayoutManager to layout Cardviews, where each card has a button which can expand and contract the card to show different information. The cards are laid out in two columns vertically across the screen. The cards are backed by a RealmObject and I am using a RealmRecyclerViewAdapter to lay them out in the recycler view but I don't think that is the problem.
When I tap on a button which causes onBindViewHolder() to be called, occasionally my cards will swap places across the columns. Only cards that are in adjacent columns swap and I can show this by increasing the number of columns to three or four and watching the behaviour. By changing the number of columns to one the cards stop swapping. There is a nice animation when the cards swap with eachother but if I turn off the animations (recyclerView.setItemAnimator(null)) the cards still swap just without the animation.
The cards only swap position when onBindViewHolder() is called and it happens often enough to be obvious but I am not able to replicate the functionality, it seems to happen randomly.
Here is some code:
Fragment that displays the RecyclerView:
public class DeviceCardListFragment extends Fragment {
private RecyclerView recyclerView;
private RealmResults<Bank> deviceList;
public int locationId = 0;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.card_list_fragment, container, false);
recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
//Turn off the blinking animation when the Realm Refreshes
RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator)animator).setSupportsChangeAnimations(false);
}
StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
manager.setGapStrategy(GAP_HANDLING_NONE);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(new StaggeredDeviceAdapter(getContext(), deviceList));
recyclerView.setItemViewCacheSize(40);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
}
StaggeredDeviceAdapter:
public class StaggeredDeviceAdapter extends RealmRecyclerViewAdapter<Bank, StaggeredDeviceViewHolder> {
private RealmResults<Bank> devices;
private Context context;
public StaggeredDeviceAdapter(Context context, RealmResults<Bank> list) {
super(list, true);
this.devices = list;
this.context = context;
}
#Override
public StaggeredDeviceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(context).inflate(R.layout.device_card, null);
//setHasStableIds(true);
StaggeredDeviceViewHolder holder = new StaggeredDeviceViewHolder(layoutView);
return holder;
}
#Override
public void onBindViewHolder(StaggeredDeviceViewHolder holder, int position) {
holder.setValues(devices.get(position), context);
}
}
StaggeredDeviceViewHolder:
I have pulled out my actual view configuration into another class for reusability which is quite large but I don't think anything in there is important.
public class StaggeredDeviceViewHolder extends RecyclerView.ViewHolder {
private View view;
DeviceCardHolder holder;
public StaggeredDeviceViewHolder(View view) {
super(view);
holder = new DeviceCardHolder(view, false);
//holder.initialise();
this.view = view;
}
//Called from outside the class
//Sets all of the fields to whatever values we want
public void setValues(Bank bank, Context context) {
holder.initialise();
holder.setValues(bank, context);
}
}
Please let me know if you want any other information from me.
EDIT:
Adding Realm Information
deviceList = realm.where(Bank.class).equalTo("locationSection",location).findAllSorted("locationID");
None of those fields are being changed in code, in fact they don't even have setters.
Because the realm is sorted and the cards only swap along the horizontal axis makes me think that the issue is with the StaggeredGridLayoutManager rather than Realm.
EDIT:
Through additional testing I have been able to reliably replicate the issue.
I am using a StaggeredGridLayoutManager because my cards can change size. This is done by pressing a button on the card. Expanding and returning the card to its original size does not cause onBindViewHolder() to be called. The cards below the ones who's size is changing swap columns when onBindViewHolder() is called after one of the cards has changed size. The cards don't swap every time but I would say it happens around 80% of the time. Only cards directly underneath the ones I am manipulating swap positions.
This makes me think that the StaggeredGridLayoutManager is getting confused as to where its indexes are supposed to be when the cards change size, maybe there is a setting about indexes that I am missing....
EDIT:
The issue I am having is basically the same one outlined in this question:
… but it doesn't have an answer
I am trying to create a chat application with RecyclerView for displaying the list of messages in chatBubble form.
In recyclerView, in each row layout I have two text views. One for displaying the message and other for displaying the timestamp. For short messages, it works. However, for long messages, the chat bubble grows too big and the corresponding TextView for displaying timestamp can't be seen in this case.
Why is this happening and how to correct this,
Also, the space between each item in RecyclerView needs to be increased, I tried using android:dividerHeight="12dp" but it didn't work.
As #Mohammed Atif commented, instead of using
android:layout_toRightOf="#+id/message
use this
android:layout_alignParentRight="true"
Now, for adding space between recyclerview's items, you need to add itemDecorator
public class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {
private final int mVerticalSpaceHeight;
public VerticalSpaceItemDecoration(int mVerticalSpaceHeight) {
this.mVerticalSpaceHeight = mVerticalSpaceHeight;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
if (parent.getChildAdapterPosition(view) != parent.getAdapter().getItemCount() - 1) {
outRect.bottom = mVerticalSpaceHeight;
}
}
}
Then add this item decorator to recyclerview like this
recyclerview.addItemDecoration(new VerticalSpaceItemDecoration(2));
Here, 2 is the space between the recyclerview list items.
To show a list of items I usually use a RecyclerView but I have a requirement that makes it easier for me to use a vertical LinearLayout. I don't want to loose the good resource management of the RecyclerView so I'm looking for a different solution and hope someone can help me.
I think of a layout with some views on top, then a list of items followed by some more views on the bottom. Or a layout that contains more than one RecyclerView. So you have some Views on the top, a list of items followed by some more views and another list of items etc.
My layout could look like this:
View1
View2
RecyclerView
View3
RecyclerView
View4
Usually a RecyclerView works like a scrollable frame in which you can scroll some content. It means that if you start scrolling, your whole screen gets stuck in a certain position and you are just scrolling in the RecyclerView until its end. Then you can continue scrolling the whole screen.
What I want is a RecyclerView that is fully inflated so you are always scrolling the whole screen instead of just the RecyclerView but not loosing the resource management of the RecyclerView.
Does anybody know of a solution of this?
add below code to your custom adapter of recycler view
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_NORMAL) {
View normalView = LayoutInflater.from(getContext()).inflate(R.layout.my_normal_row, null);
return new MyNormalViewHolder(normalView); // view holder for normal items
} else if (viewType == ITEM_TYPE_HEADER) {
View headerRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header_row, null);
return new MyHeaderViewHolder(headerRow); // view holder for header items
}
}
What i suggest is to use a single recyclerview with custom adapter and inflate each row of recyclerview with diff layout ie.... based on your condition
In this situation i use only one RecycleView implementing my own getItemViewType
The best answer will depend on what behaviour you want for the screen as a whole.
"What I want is a RecyclerView that is fully inflated so you are always scrolling the whole screen instead of just the RecyclerView but not loosing the resource management of the RecyclerView."
This requirement makes me think that the best option indeed is a single RecyclerView using different ViewHolder types like #Rissmon Suresh and #betorcs mentioned.
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class HeaderViewHolder extends RecyclerView.ViewHolder {
...
}
class NormalViewHolder1 extends RecyclerView.ViewHolder {
...
}
#Override
public int getItemViewType(int position) {
// use position to decide what kind of View you want
// can be fixed or you can access the data to check dinamically
if(position == 0)
return 0;
else
return 1;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new HeaderViewHolder(...);
case 1: return new NormalViewHolder1(...);
//...
}
}
}
Hope this helps =)
I'm trying to implement the below image. My first thought was to have everything above the grid layout be the first row of the grid and use SpanSizeLookup to set the span size to the number of columns in the RecyclerView, but this feels like something that will give me a lot of problems.
I've been reading about putting a RecyclerView inside a NestedScrollView, people say it works, but I can't seem to get it to work properly. The scrolling doesn't seem to work right, I can't get the grid to even show up without setting a minHeight, but then it just looks bad.
Is there another option I'm not considering or is one of these the direction I should be going?
What kind of problems are you anticipating from SpanSizeLookup? You can implement it with a few lines as follows (I'd recommend using values from integers.xml for flexibility).
GridLayoutManager glm = new GridLayoutManager(getContext(), 3);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override public int getSpanSize(int position) {
return (position == 0) ? 3 : 1;
}
});
If your header layout needs views and fields that your regular layout doesn't have, you'll want to create separate views and tell your adapter about them. Something like this.
#Override
public int getItemViewType(int position) {
if (position == 0)
return TYPE_HEADER;
else
return TYPE_REGULAR;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
MyHeaderView view = (MyHeaderView) LayoutInflater
.from(parent.getContext())
.inflate(R.layout.my_header_view, parent, false);
return new MyHeaderViewHolder(view);
} else {
MyRegularView view = (MyRegularView) LayoutInflater
.from(parent.getContext())
.inflate(R.layout.my_regular_view, parent, false);
return new MyRegularViewHolder(view);
}
}
An example header view could be like this (you'd call bindTo() from MyHeaderViewHolder).
public final class MyHeaderView extends LinearLayout {
#Bind(R.id.image) ImageView imageView;
#Bind(R.id.title) TextView titleView;
public MyHeaderView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
ButterKnife.bind(this);
}
public void bindTo(String imageUrl, String title) {
Glide.with(getContext())
.load(imageUrl).into(imageView);
titleView.setText(title);
}
}
use StaggeredGridLayoutManager
use a different layout for your first item (the whole complex view)
get its layout params and setFullSpan
This makes the item as wide as the RecyclerView itself (similar to match_parent).
Set various click listeners in the specific ViewHolder that's responsible for this item. Using this approach would set the whole complex view to behave (scroll) as part of the RecyclerView while still making it available for (input) events.
You could have a look at Bookends
Or another(the way i usually do it) way would be to use a GridLayouManager with a SpanSizeLookUp. And use multiple ViewTypes i.e. one for Header,Footer and Items.
Go for 1 if you have only a single header and what a ListView-ish interface in your code.
Go for 2 if you are not sure about how many Custom ViewTypes you would be adding.It assures you have maximum scalability in the future.
If you are considering massive scalability,I suggest you read this article by Hannes Dorfman .