How to have a GridLayoutManager with header, without changing its adapter? - android

Background
I have an app that shows a list of items in a grid-like way, yet not exactly...
The problem
Some of the items have a different height, so the ones near them should gain the same height as they have. This one works on GridLayoutManager.
Some items (actually only the first one in my case) need to span the entire row (which is why I used StaggeredGridLayoutManager ).
Using the normal GridLayoutManager, the first requirement worked fine: each row could have a different height. But because of #2, it actually ruined #1.
The question
Is it possible to use StaggeredGridLayoutManager so that when items have different height, it won't make them move in the Y coordinate?
I was thinking: maybe I could use NestedScrollView and GridLayoutManager (because only the first item is spanned), but still, I would like to know if it's possible in the rest of the cases (and also this solution).

OK, so I've found a possible solution by still using GridLayoutManager:
use getItemViewType and return a specific value for spanned items, and a different value for normal items .
Create the needed view in onCreateViewHolder, according to its type, and cast to the needed viewHolder class in onBindViewHolder according to the type.
use setSpanSizeLookup, and inside getSpanSize, return the spanned cells in case the type is for such items
Example:
#Override
public int getItemViewType(final int position)
{
return position==0?VIEW_TYPE_HEADER:VIEW_TYPE_NORMAL;
}
public ViewHolder onCreateViewHolder(final ViewGroup parent,final int viewType)
{
if(viewType==VIEW_TYPE_HEADER) ... //create header ViewHolder
else ... // create normal item
}
...
_layoutManager=new GridLayoutManager(...,3,LinearLayoutManager.VERTICAL,false);
_layoutManager.setSpanSizeLookup(new SpanSizeLookup()
{
#Override
public int getSpanSize(final int position)
{
return _adapter.getItemViewType(position)==VIEW_TYPE_HEADER?_layoutManager.getSpanCount():1;
}
});
Still, I would like to know how to use the NestedScrollView solution
One issue that I have noticed, is that if I try to set the visibility of the header to GONE, it still takes space.

Related

RecyclerView - some variables of one items are empty - don't show item at all

Is there a possibility using the RecyclerView in Android Studio to not show one whole item if there are variables empty that should be shown in that item?
For example if I got 3 variables to be shown in the item, but only 2 of them have a value - don't show the item at all.
Thanks in advance :)
There's no supported API for this. The best idea I have is to set the view's height to 0 when you don't want to show it. Here's some code that hides the view at position 3 and shows the others:
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
params.height = (position == 3) ? 0 : ViewGroup.LayoutParams.WRAP_CONTENT;
holder.itemView.setLayoutParams(params);
// other binding code here
}
If your item views aren't using wrap_content for their height, then you'd have to set the height to that fixed value. You could use this code to fetch that fixed size:
holder.itemView.getResources().getDimensionPixelSize(R.dimen.your_size)
If you use your own implementation of BaseAdapter or ArrayAdapter, you could filter ListView or RecyclerView with holding two lists in your adapter class: one for all values, and one (shown) for filtered values. Also there's no need in hacks like setting some items height to 0 (which may produce problems).

Android RecyclerView add horizontal gape in beginning and in ending

I am trying to replicate a view from iOS so that user have same look and feel throughout the android application as well.
I am having a RecyclerView with LinearLayoutManager and horizontal orientation. So far so good.
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
tools:listitem="#layout/item_recycler_view" />
With the output design:
However, in case of iOS design we have items starting from center however the horizontal view is completely scrollable (meaning the scrolling can be done to full width even if the item loading from center).
I know there is no use of adding padding/margin or using a different view like HorizontalScrollView. How can we obtain such behaviour so that i give nearly same experience to users.
Let me know if there is anything that i can provide to clarify the problem statement.
Quick solution
Add an empty item on the beginning and one on the end of your list, and make your index access account for those two extra items. That should help you get the desired effect.
Not so quick solution
Android allows us to write our own custom Layout Managers for RecyclerView. It comes with three types that will cover most of the user cases:
LinearLayoutManger (For lists in general);
GridLayoutManager (For grids);
StaggeredGridLayoutManager (For grids with items with custom sizes).
I believe you could write one to always start placing the first item on the center of the screen. That will require more work, but it won't mess with your data indexes.
Read this, and this, on how to create custom Layout Managers. Also, take a look at the docs. That should be a good place to start.
There are two ways you could do this. The simplest by far would be to add horizontal padding to your RecyclerView and set the view to not clip based on padding. Something like this:
android:paddingLeft="100dp"
android:paddingRight="100dp"
android:clipToPadding="false"
The other way would be to create an ItemDecoration and add it to your RecyclerView. You could then override the getItemOffsets() method to add a left-hand offset to your first item and a right-hand offset to your last item.
This second approach is better because it won't affect the RecyclerView's scrollbars, but it is a little more complex. Here's an example to get you started:
private static class MyItemDecoration extends RecyclerView.ItemDecoration {
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int parentWidth = parent.getWidth();
int childWidth = view.getWidth();
int margin = (parentWidth - childWidth) / 2;
int position = parent.getChildAdapterPosition(view);
outRect.left = position == 0 ? margin : 0;
outRect.right = position == (parent.getAdapter().getItemCount() - 1) ? margin : 0;
}
}
I think the only way will be adding different layout for first and last position in adapter of recyclerview.
It can be done using viewType parameter in createViewHolder(ViewGroup parent, int viewType)

notifyItemChanged() make the RecyclerView scroll and jump to UP

i have a RecycleView with an adapter that show a list of servers
and the user must select one server.
when i call notifyItemChanged(previousPosition) inside the onClick() method
to make the old server unselected and the new server selected,
that's make the RecycleView list jump to up exactly in the middle of list.
and this problem happen just when i click on one of the last 2 or 3 servers inside the RecycleView list
here is the code of my RecyclerView.Adapter :
public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.ServerViewHolder> {
private List<Server> listServers = new ArrayList<>();
private int[] icons = new int[]{R.drawable.server1,R.drawable.server2,R.drawable.server3,R.drawable.server4,R.drawable.server5,R.drawable.server6,R.drawable.offline};
private int selected = 0;
private int previousSelected = 0;
public ServerAdapter(List<Server> listServers){
this.listServers = listServers;
}
#Override
public ServerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.server_relative_layout,parent,false);
return new ServerViewHolder(view);
}
#Override
public void onBindViewHolder(final ServerViewHolder holder, final int position) {
if(position == selected){
holder.getBackground().setSelected(true);
}else{
holder.getBackground().setSelected(false);
}
holder.getBackground().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(position != selected){
previousSelected = selected;
selected = position;
holder.getBackground().setSelected(true);
notifyItemChanged(previousSelected);
}
}
});
holder.getImageServer().setImageResource(icons[position%6]);
holder.getTextNameServer().setText(listServers.get(position).getName());
holder.getTextConnected().setText(listServers.get(position).getUrl());
}
#Override
public int getItemCount() {
return listServers.size();
}
public class ServerViewHolder extends RecyclerView.ViewHolder{
private ImageView imageServer;
private TextView textNameServer;
private TextView textConnected;
private View background;
public ServerViewHolder(View itemView) {
super(itemView);
imageServer = (ImageView)itemView.findViewById(R.id.imageServer);
textNameServer = (TextView)itemView.findViewById(R.id.textNameServer);
textConnected = (TextView)itemView.findViewById(R.id.textConnected);
background = itemView;
}
public ImageView getImageServer() {
return imageServer;
}
public TextView getTextConnected() {
return textConnected;
}
public TextView getTextNameServer() {
return textNameServer;
}
public View getBackground() {
return background;
}
}
}
any solutions to solve this problem ? thanks.
The problem happened exactly when i specify the layout height and do not let it to wrap_content
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="400dp"
android:id="#+id/serverRecyclerView"
android:layout_margin="10dp"
/>
or when i put it below something for expample like that :
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/serverRecyclerView"
android:layout_margin="10dp"
android:layout_below="#+id/image"/>
my code exactly is :
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/serverRecyclerView"
android:layout_margin="10dp"
android:layout_alignTop="#+id/imageBall"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_toRightOf="#+id/camera"
android:layout_toEndOf="#+id/camera"/>
Looks like this is a bug: https://code.google.com/p/android/issues/detail?id=203574
The best workaround seems to be Bart's answer to set the RecyclerView's LinearLayoutManager's AutoMeasure property to false.
LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setAutoMeasureEnabled(false);
recyclerView.setLayoutManager(llm);
The set FixedSize to true solution had way too many side-effects...
RecyclerView.setHasFixedSize(true)
I don't know why, but I used:
RecyclerView.setHasFixedSize(true)
This worked for me. I hope it can help.
android:descendantFocusability="blocksDescendants"
android:descendantFocusability="blocksDescendants"
this attr solve my bug
RecyclerView.ItemAnimator animator = myRecyclerListView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator)animator).setSupportsChangeAnimations(false);
}
My RecyclerView was inside ConstraintLayout, and I also had such problem and calling setAutoMeasureEnabled(false) of RecyclerView's LayoutManager did not fix the issue for me, furthermore this method is deprecated in 28.0.0 version. What I did is that, I wrapped my RecyclerView with RelativeLayout and now it works like a charm. As mentioned in bugtracker, this "issue" is intented behaviour in LinearLayout and is not going to be fixed. So if it is possible, just wrap your RecyclerView something like this:
<RelativeLayout
android:id="#+id/container_messages_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#drawable/chat_back_pattern"
app:layout_constraintBottom_toTopOf="#+id/bottom_view"
app:layout_constraintTop_toBottomOf="#+id/toolbar">
<android.support.v7.widget.RecyclerView
android:id="#+id/messages_list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
for anyone who stumbles upon this issue, try using
yourRecyclerView.notifyItemChanged(int position, Object payload);
This one did the trick for me.
Using
setAutoMeasureEnabled(false);
also worked but in some edge cases recycler view was acting weird. Good luck!
RecyclerView can perform several optimizations if it can know in advance that RecyclerView's size is not affected by the adapter contents. RecyclerView can still change its size based on other factors (e.g. its parent's size) but this size calculation cannot depend on the size of its children or contents of its adapter (except the number of items in the adapter).
If your use of RecyclerView falls into this category, set this to true. It will allow RecyclerView to avoid invalidating the whole layout when its adapter contents change.
If we have a RecyclerView with match_parent as height/width, we should add setHasFixedSize(true) since the size of the RecyclerView itself does not change inserting or deleting items into it.
setHasFixedSize should be false if we have a RecyclerView with wrap_content as height/width because each element inserted by the adapter could change the size of the RecyclerView depending on the items inserted/deleted, so, the size of the RecyclerView will be different each time we add/delete items.
recyclerView.setHasFixedSize(true);
true if adapter changes cannot affect the size of the RecyclerView.
References
Android Developers Reference - RecyclerView
Understanding RecyclerView setHasFixedSize - Gastón Saillén
I came across the similar problem, just take care of the xml layout file.
Do not use the layout_below , layout_above or others similar properties in RecyclerView or RecyclerView's parent view.
You can use LinearLayout weight , layout_marginBottom or sth to achieve
layout_below or other.
The late answer better than nothing, if you're using NestedScrollView as the parent view of RecyclerView you should delete it.
I had a similar problem and I tryed all solutions listed above, but noone worked.
I was already padding the "Payloads" to "notifyItemChanged(position, payloads)" because I just needed to "upload" a checkbox value so I was passing the value inside "Payloads" without recalling the update of the entire viewholder.
This solution worked for all view holders in my recycler view except for the last one (and probably for all "recycled" ones, I mean those who recall the "onBindViewHolder" by "recycling" an existing view).
I think using "notifyItemChanged" will works if you have only the recyclerview and I also think that this problem of "auto-scrolling" is raised by nested scroll views & recycler views.
I was in the case exposed by "raed", so "ScroolView -> RecyclerView -> "n" x RecyclerView". I have a scroolview wich contains a recyclerview whose viewholders can contains a recycler views.
Delete the parent ScrollView is a really weird solution and I couldn't use it, so I setted the "onStopNestedScroll" inside the "ScrollView" and not inside the RecyclerView.
Personally I used it programmatically before the code part which calls the "notifyItemChanged" method by doing:
msvContainer.onStopNestedScroll(mRecyclerView);
Where "msvContainer" is my ScrollView which contains the RecyclerView, and "mRecyclerView" is my RecyclerView contained by the ScrollView.
This way worked 99% because the first time I call "notifyItemChanged" the view scroll up only for the ScrollView, so it hides a button inside my ScrollView which is below my RecyclerView but it doesn't scroll the RecyclerView items. After the first call "notifyItemChanged" works properly.
I found that calling:
msvContainer.stopNestedScroll();
works too. But i suggest to use the first method with the target view if you have multiple nested scroll views.
Anyway you should call "startNestedScroll" after you ran out of the critical part of re-updating your view holder because the targeted view, so in my case the RecyclerView, won't scroll until you call this method so it won't recycler his view holders too.
(In my case that I have multiple Recycler View inside a parent Recycler View inside a parent Scroll View if I was in need to call "notifyItemChanged" inside the most inner Recycler View i would use the "stopNestedScroll" method for every parent view and then re-activated the scroll after the scroll-critical part)
Hope this is helpful, have a nice coding!
Bye
In my case, all I did was to set the height of the recyclerview to "match_parent". Then in your MainActivity, do;
recyclerView.smoothScrollToPosition(yourAdapter.getItemCount()-1);
Thats all...

Best way to build and handle List view with differing rows

In my app, I show data in a list view. It looks as below.
Each row in list view is different (ie., they have a different view structure). But every row is made up of 1 to many subviews (all subview are same ).
Problem: If try to inflate the rows from xml then I need to have xml with
1. 2 subviews
2. 3 subviews
3. 5 subviews and so on
If I try to build the rows dynamically then I wont be able to take advantage of the holder pattern or the views re-usability of Listview. And I will end up building the subviews if the number of subviews, in the returned view is less than required or deleting in case the subviews are more than required.
In a nutshell, how can I have different views in listview which does not hamper view re-usability or holder pattern ?
Edit:
I have extended ArrayAdapter and overriden getView, getViewTypeCount, getItemViewType
#Override
public int getItemViewType(int position) {
return mListItems.get(position).getViewToBeRenderened().getViewId();
}
#Override
public int getViewTypeCount() {
return AppConstants.VwViewTypes.values().length;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final VWListItem listItem = mListItems.get(position);
if(listItem == null || listItem.getViewToBeRenderened() == null){
return convertView;
}
VWView renderingView = listItem.getViewToBeRenderened();
if(convertView == null ){
convertView = renderingView.buildView(mContext.appContext);
}
...
}
This approach worked initially when there were only 2-3 differing row views. For each of these row views i had a different xml layout and corresponding class which would build and inflate.
But now the number of such views is growing and I dont want to have a layout and class for each additional new row view. Instead I would like to reuse existing layouts and class and build on them. If there is a better solution then am open to it as well.
when you have to manage different kinds of views you should override getViewTypeCount() and getItemViewType() and you will receive a number of convertViews equals to the int returned by getViewTypeCount(). For instance, If it returns 2 you will get 2 differents convertView.
ListView is supposedly used to relieve the coders of some burden when each row has similar repetitive pattern. Yes, for slight variations getViewType is the correct choice to implement, but I see a better solution to your problem: you should create your own linearlayout if you have a lot of customization requirement. Remember that listview itself is an expensive object to create. Create a custom linearlayout is not that hard.

ExpandableListView onExpand always scroll expanded group to top

i`m looking for way to position expandableListView group on top, when expanded.
I tried onGroupExpand, or in performItemClick of ExpandableListView.
setSelectedPositionFromTop(int, int), partialy works, but if it starts in a time when system starts it's own scroll, than list is overscrolled, and group is out of screen.
Sorry for messy description, but it's hard to say without showing exactly what i need.
Change transcriptmode to disabled.
android:transcriptmode="disabled"
in expandable list layout.
Put this in your adapter:
public void onGroupExpanded(final int groupPosition) {
super.onGroupExpanded(groupPosition);
listView.setSelectedGroup(groupPosition);
}
Try setSelection(groupPosition) in onGroupExpand(int groupPosition).
I tried many solutions but the below code works for me.Hope this code will be helpful for some one.Implements the OnGroupExpandListener with in onGroupExpand
use the below code
public void onGroupExpand(final int groupPosition) {
super.onGroupExpand(groupPosition);
expandableListView.post(new Runnable() {
#Override
public void run() {
expandableListView.setSelection(groupPosition);
if(expandableListView.getChildAt(groupPosition)!=null)
expandableListView.requestChildRectangleOnScreen(expandableListView.getChildAt(groupPosition),
new Rect(0, 0, expandableListView.getChildAt(groupPosition).getRight(), expandableListView.getChildAt(groupPosition).getHeight()), false);
}
});
}
I have solution to this problem. It's not gentle one, but it works quite fine.
Expandable listView messures is it should move by child number. It seems, that system thinks that child is same height as parent(group), so if you put different size child it will cause problems with auto scrolling list.
My solution was to trick list to think that it has more children.
If my child is twice as big as group, i just return information that group has 2 children. Now, since it is data constructed list, i make check on my data, is there a data for 2 childs, or only 1, if i don't have data for second child, i know that i can put 0 height view as second child.
It worked fine when i had to make List with small group, and than with 1 big child.
Since there was no answer to my question, i post this one, it may help someone.

Categories

Resources