I made a RecyclerView inside a NavigationView with a custom first item as an equivalent to ListView's addHeaderView(). On the bottom part of that header layout I placed an EditText so that I can filter out the list below.
What I wanted to do was to scroll the RecyclerView all the way up until the EditText would be the topmost item whenever the EditText gains focus. However if my item count is small enough that it wouldn't be able to fill-up the entire height of the NavigationView, the RecyclerView refuses to scroll all the way up and thus showing a bit more of the header other than the EditText. As an added problem, even if the list is large enough, it stills scrolls back down once I start filtering the list items.
How can I force my RecyclerView to still scroll all the way up even if I don't have enough items in to fill the height of my view so that the rest of my header do not take a peek?
Here's a visualization of what I'm trying to achieve:
_______________
| HEADER LINE 1 |
| HEADER LINE 2 |
| HEADER LINE 3 |
| ------------- |
| Search box |
| ------------- |
| List item 1 |
| List item 2 |
| List item 3 |
---------------
Expected behavior when I focus on the search box:
_______________
| ------------- |
| Search box | <- becomes the topmost item. I can already achieve this
| ------------- | with RecyclerView.smoothScrollBy(dx, dy)
| List item 1 | except when the list is small enough (see below)
| List item 2 |
| List item 3 |
| List item 4 |
| | <- empty space is okay and is desired if dataset
| | is small enough
---------------
What actually happens:
_______________
| HEADER LINE 2 | <- the header is 'bleeding' on top
| HEADER LINE 3 |
| ------------- |
| Search box | <- search box cannot take the topmost spot
| ------------- | as the recycler tries to position itself to fill in
| List item 1 | the bottom part with items
| List item 2 |
| List item 3 |
| List item 4 | <- items are pushed down to the bottom to fill
--------------- in the space
Here's a snippet of my NavigationView xml (just your typical setup):
<android.support.design.widget.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true">
<android.support.v7.widget.RecyclerView
android:id="@+id/drawer_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.design.widget.NavigationView>
ViewHolder with custom header here (RecyclerView Adapter):
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == HEADER) {
return new HeaderHolder(mHeaderView);
} else {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item, parent, false);
return new ItemViewHolder(v);
}
}
The drawer header is just an xml layout I inflated in the main activity (I assigned listeners and animations there) and passed on to my RecyclerView adapter via a setHeaderView(View header) I made.
What you need is a footer which is the same height as your header.
There are two scenarios to consider here:
In the first case, the solution is very simple. Just define the height of your header in some value like <dimen name="header_height">300dp</dimen>
and then define your header and footer like the following:
<FrameLayout
android:layout_height="@dimen/header_height">
<!-- Content goes here -->
</FrameLayout>
In the second case, we're going to have to do a couple of things:
There are lots of ways to do this. One such way that I have used before is that you could use a ViewTreeObserver.OnPreDrawListener
on the RecyclerView to detect when the header has a height (and subsequently if it has changed) and then pass that height to your RecyclerView.Adapter
which will use it to set the requested height for your footer when it inflates it, and then you just need to call notifyItemChanged(int)
on the adapter with your footer's index to tell it to re-inflate your footer. Here's most of what I just described in code:
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.drawer_content);
recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if(recyclerView.getChildCount() < 1 || recyclerView.getChildAt(0) == null || recyclerView.getChildAt(0).getHeight() == 0) {
return true;
}
int headerHeight = recyclerView.getChildAt(0).getHeight();
adapter.setFooterHeight(headerHeight);
return true;
}
});
And in your adapter:
int footerHeight = 0;
public void setFooterHeight(int footerHeight){
if(this.footerHeight == footerHeight){
return;
}
this.footerHeight = footerHeight;
notifyItemChanged(getItemCount() - 1);
}
And then the only other thing you'd need to do is inflate your footer like you are your header already and set its height to footerHeight
.
Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With