I have a recylcer view having grid images and I have a button at the bottom of the recycler view. This button should be at the bottom of the recyclerview, not the bottom of the parent.
I tried code for recyclerView. But the button stays as parent bottom not scrolling along with the recycler view.
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="#string/hello_blank_fragment"/>
I have found the solution.
I can create a separate view with Button and can add in the recyclerView Adapter.But there will be one problem - Since My Layout manager is GridLayout manager and having span of 2, I have to change the span to 1 when the current view is Button.
The changing of span when the view is Button is below:
final GridLayoutManager mGridManager = new GridLayoutManager(getActivity(),2);
mGridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
return adapter.isPositionHeader(position) ? mGridManager.getSpanCount() : 1;
}
});
recyclerView.setLayoutManager(mGridManager);
recyclerView.setAdapter(adapter);
This code I had to add in recyclerView Adapter, this below code means when button is the view return true, otherwise return false
public boolean isPositionHeader(int position) {
if(ADD_SOURCE_BUTTON == getItemViewType(position)){
return true;
} else {
return false;
}
}
Am not sure this is a good method, but it worked for me.
Try putting RecyclerView inside a ScrollView and place the button below RecyclerView. It will work fine now. Earlier it had some issues of scrolling but now its fixed by google I think and we can place RecyclerView inside scroll view, but am not sure its a good method, but it will work.
Related
I have a recycler view and a relative layout below recycler view. My relative layout consists of three text views.My recycler view consists of three text views and a button. My problem is recycler view is scrolling separately and textviews in relative layout are fixed. But I want both to be scrolled, which means while scrolling the screen scroll should be done for both recycler view and relative layout but not seperately. While scrolling my relative layout should be attached to the end of recycler view. I have searched a lot for doing that but there is no results for my search. So, ended up here please anybody help me out.
You have 2 options, first one (the better one) is to create a footer ViewHolder and add it to RecyclerView as a last item in adapter.
Or you can simply wrap your views in vertical LinearLayout and then wrap it in NestedScrollView like this:
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
In order to get your RelativeLayout to scroll with the content of your RecyclerView, you'd need to add the RelativeLayout with static content to the end of the list your Adapter iterates. You'd then override getItemViewType in the adapter and return one type ID for the data in your RecyclerView and another for the footer RelativeLayout. Then in onCreateViewHolder you'd use the view type to inflate the right kind of view (one that binds your data or another that displays your RelativeLayout).
This process can be pretty labor intensive. You might also consider using a library like Epoxy to help create a footer view in your RecyclerView.
You can add view with text views in different layout and add to your recycler as last element. Then check posotion in getItemViewType and if it last return footer type inside RecyclerAdapter.
private static final int FOOTER = 1;
private static final int CHILD = 2;
// inflates the row layout from xml when needed
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == CHILD){
View view = mInflater.inflate(R.layout.list_item, parent, false);
return new ViewHolder(view);
}
else if(viewType == FOOTER ){
View view = mInflater.inflate(R.layout.relative, parent, false);
return new ViewHolder(view);
}
return null;
}
#Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) {
return FOOTER;
} else {
return CHILD;
}
}
I have a pretty standard RecyclerView with a vertical LinearLayoutManager. I keep inserting new items at the top and I'm calling notifyItemInserted(0).
I want the list to stay scrolled to the top; to always display the 0th position.
From my requirement's point of view, the LayoutManager behaves differently based on the number of items.
While all items fit on the screen, it looks and behaves as I expect: The new item always appears on top and shifts everything below it.
However, as soon as the no. of items exceeds the RecyclerView's bounds, new items are added above the currently visible one, but the visible items stay in view. The user has to scroll to see the newest item.
This behavior is totally understandable and fine for many applications, but not for a "live feed", where seeing the most recent thing is more important than "not distracting" the user with auto-scrolls.
I know this question is almost a duplicate of Adding new item to the top of the RecyclerView... but all of the proposed answers are mere workarounds (most of them quite good, admittedly).
I'm looking for a way to actually change this behavior. I want the LayoutManager to act exactly the same, no matter the number of items. I want it to always shift all of the items (just like it does for the first few additions), not to stop shifting items at some point, and compensate by smooth-scrolling the list to the top.
Basically, no smoothScrollToPosition, no RecyclerView.SmoothScroller. Subclassing LinearLayoutManager is fine. I'm already digging through its code, but without any luck so far, so I decided to ask in case someone already dealt with this. Thanks for any ideas!
EDIT: To clarify why I'm dismissing answers from the linked question: Mostly I'm concerned about animation smoothness.
Notice in the first GIF where ItemAnimator is moving other items while adding the new one, both fade-in and move animations have the same duration. But when I'm "moving" the items by smooth scrolling, I cannot easily control the speed of the scroll. Even with default ItemAnimator durations, this doesn't look as good, but in my particular case, I even needed to slow down the ItemAnimator durations, which makes it even worse:
Although I wrote this answer and this is the accepted solution, I suggest a look at the other later answers to see if they work for you before attempting this.
When an item is added to the top of the RecyclerView and the item can fit onto the screen, the item is attached to a view holder and RecyclerView undergoes an animation phase to move items down to display the new item at the top.
If the new item cannot be displayed without scrolling, a view holder is not created so there is nothing to animate. The only way to get the new item onto the screen when this happens is to scroll which causes the view holder to be created so the view can be laid out on the screen. (There does seem to be an edge case where the view is partially displayed and a view holder is created, but I will ignore this particular instance since it is not germane.)
So, the issue is that two different actions, animation of an added view and scrolling of an added view, must be made to look the same to the user. We could dive into the underlying code and figure out exactly what is going on in terms of view holder creation, animation timing, etc. But, even if we can duplicate the actions, it can break if the underlying code changes. This is what you are resisting.
An alternative is to add a header at position zero of the RecyclerView. You will always see the animation when this header is displayed and new items are added to position 1. If you don't want a header, you can make it zero height and it will not display. The following video shows this technique:
This is the code for the demo. It simply adds a dummy entry at position 0 of the items. If a dummy entry is not to your liking, there are other ways to approach this. You can search for ways to add headers to RecyclerView.
(If you do use a scrollbar, it will misbehave as you can probably tell from the demo. To fix this 100%, you will have to take over a lot of the scrollbar height and placement computation. The custom computeVerticalScrollOffset() for the LinearLayoutManager takes care of placing the scrollbar at the top when appropriate. (Code was introduced after video taken.) The scrollbar, however, jumps when scrolling down. A better placement computation would take care of this problem. See this Stack Overflow question for more information on scrollbars in the context of varying height items.)
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TheAdapter mAdapter;
private final ArrayList<String> mItems = new ArrayList<>();
private int mItemCount = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager =
new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) {
#Override
public int computeVerticalScrollOffset(RecyclerView.State state) {
if (findFirstCompletelyVisibleItemPosition() == 0) {
// Force scrollbar to top of range. When scrolling down, the scrollbar
// will jump since RecyclerView seems to assume the same height for
// all items.
return 0;
} else {
return super.computeVerticalScrollOffset(state);
}
}
};
recyclerView.setLayoutManager(layoutManager);
for (mItemCount = 0; mItemCount < 6; mItemCount++) {
mItems.add(0, "Item # " + mItemCount);
}
// Create a dummy entry that is just a placeholder.
mItems.add(0, "Dummy item that won't display");
mAdapter = new TheAdapter(mItems);
recyclerView.setAdapter(mAdapter);
}
#Override
public void onClick(View view) {
// Always at to position #1 to let animation occur.
mItems.add(1, "Item # " + mItemCount++);
mAdapter.notifyItemInserted(1);
}
}
TheAdapter.java
class TheAdapter extends RecyclerView.Adapter<TheAdapter.ItemHolder> {
private ArrayList<String> mData;
public TheAdapter(ArrayList<String> data) {
mData = data;
}
#Override
public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == 0) {
// Create a zero-height view that will sit at the top of the RecyclerView to force
// animations when items are added below it.
view = new Space(parent.getContext());
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0));
} else {
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item, parent, false);
}
return new ItemHolder(view);
}
#Override
public void onBindViewHolder(final ItemHolder holder, int position) {
if (position == 0) {
return;
}
holder.mTextView.setText(mData.get(position));
}
#Override
public int getItemViewType(int position) {
return (position == 0) ? 0 : 1;
}
#Override
public int getItemCount() {
return mData.size();
}
public static class ItemHolder extends RecyclerView.ViewHolder {
private TextView mTextView;
public ItemHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.textView);
}
}
}
activity_main.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="vertical"
app:layout_constraintBottom_toTopOf="#+id/button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:text="Button"
android:onClick="onClick"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
list_item.xml
<LinearLayout
android:id="#+id/list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<View
android:id="#+id/box"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:background="#android:color/holo_green_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#id/box"
app:layout_constraintTop_toTopOf="parent"
tools:text="TextView" />
</LinearLayout>
I'm also displaying a live feed of items, and when a new item is added, it's before the first item. But in the recycler view, I need to scroll up to see the new item.
To solve the problem, add to the Adapter a RecyclerView.AdapterDataObserver() and override the onItemRangeInserted(). When new data is added, check if the data is on at position 0, and the recycler view was on top (You don't want to autoscroll to first position if you were scrolling in the list).
Exemple :
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
if (positionStart == 0 && positionStart == layoutManager.findFirstCompletelyVisibleItemPosition()) {
layoutManager.scrollToPosition(0);
}
}
});
This solution worked fine for me, with different type of Adapter, like ListAdapter and PagedListAdapter.
I firstly wanted to use a similar implementation of the accepted solution, where you add a dumb first item, but in PagedListAdapter it's impossible as list are immutable.
This worked for me:
val atTop = !recycler.canScrollVertically(-1)
adapter.addToFront(item)
adapter.notifyItemInserted(0)
if (atTop) {
recycler.scrollToPosition(0)
}
The only solution that worked for me was to reverse the recycler's layout by calling setReverseLayout() and setStackFromEnd() on its LinearLayoutManager.
This might sound stupid, but the way RecyclerView handles adding items at the end of the list is what you need at the top. The only downsize of this is that you'd have to reverse your list and start adding items to the end instead.
Use adapter.notifyDataSetChanged() instead of adater.notifyItemInserted(0). This will scroll recylerView to zero position if current scroll position is one(old zero).
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.
I am using recycle view in my application. I have one button above recycle view in relative layout for changing layout manager. It first displays grid layout manager and when a button is clicked it displays Linearlayoutmanager. While I am changing layout manager, recycle view again starts from the first position.
Can anyone help me to keep the position as it is when making the change in layout manager dynamically?
You have to remember the firstVisibleItem and scroll to that position after changing the LayoutManager it works fine I just tried it out by myself:
E.g. Button click to change LayoutManager:
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int i = ((LinearLayoutManager) rcView.getLayoutManager()).findFirstVisibleItemPosition();
if(rcView.getLayoutManager() instanceof GridLayoutManager) {
rcView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
} else {
rcView.setLayoutManager(new GridLayoutManager(MainActivity.this, 3));
}
rcView.scrollToPosition(i);
}
});
I would like to have the listview in a ListActivity be displayed with the header and footer visible all the time even if the list data is empty.
An empty list causes the empty view to appear and the header and footer to disappear. However my header has filtering UI and should therefore always be visible.
The only way I can make it happen at the moment is if I take the header and footer out of the listview and implement them as static views outside in the activity layout. However then these are always visible and only the data scrolls.
I would prefer for both to just be on top and bottom of the scrolling list. Wrapping it all in a scroll view does not work since then there are two nested scroll views( the list view outside and the wrapping one).
Is there a way to do this nicely apart from a hack like adding a fake record?
Make your empty view include your header.
i.e.
<ListView />
<RelativeLayout
android:id="#android:id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
// Add whatever you want here including your header
</RelativeLayout>
This answer is a bit late, but you can simply override BaseAdapter.isEmpty() to return false.
public boolean isEmpty() {
return false;
}
You could use my MergeAdapter for this. Add the header view, add the data adapter, and add the footer view. They will all scroll in unison, and the header and footer will persist even if the adapter in the middle has no rows.
Ok, this workaround is a hack. But, without having to create a fake item.
For an ArrayAdapter you can add a null item... i.e.
myAdapter.add(null);
And, in the MyAdapter getView(..) you do something like:
myItem = getItem(position);
if (myItem == null) {
return getLayoutInflater().inflate(R.layout.my_empty_row, null);
} else {
...
}
I found that header/footer will always be visible if you don't call ListView.setEmptyView.
Instead, manually show/hide your empty view:
emptyView.setVisibility(list.size() == 0 ? View.VISIBLE : View.GONE);
I meet the same issue, if ListView has no item, you could ListView.setAdapter(new SomeAdapter(new ArrayList())); and the header is also visible.
In my case I had both header and footer views.
I placed my mEmptyView in footer view.
First, as user1034295 mentioned, I overrided adapter's isEmpty() method:
#Override
public boolean isEmpty() {
// If not overridden, then this will return `true` if your list is empty
// Thus, you won't see header/footer views
return false;
}
ExpandableListView adapter's getGroupCount(). Override appropriate method for your adapter.
#Override
public int getGroupCount() {
int size = null != mData ? mData.size() : 0;
if (0 == size) mHostFragment.toggleEmptyView(true);
else mHostFragment.toggleEmptyView(false);
return size;
}
In you activity/fragment
public void toggleEmptyView(boolean show) {
if(show && mEmptyView.getVisibility() != View.VISIBLE)
mEmptyView.setVisibility(View.VISIBLE);
else if (!show && mEmptyView.getVisibility() != View.GONE)
mEmptyView.setVisibility(View.GONE);
}
mEmptyView is a child in ListView's footer view.
So, when your list becomes 0 sized you'll get your header/footer views remained + mEmptyView will become visible.
You can put the listView and the empty layout together in a FrameLayout, and put
the empty layout margin in the size of the header, then, you can change the visibility of the empty view when an item is added to listview.
layout will be as similar to this:
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1">
<ListView
android:id="#+id/myListView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listheader="#layout/myHeader"/>
<LinearLayout
android:layout_marginTop="#dimen/headerHeight"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
android:id="#+id/emptyLayout">
//insert your layout here
</LinearLayout>
</FrameLayout>