问题
I am getting a weird scrolling behavior when I add a RecyclerView inside a NestedScrollView.
What happens is that whenever the scrollview has more rows than can be shown in the screen, as soon as the activity is launched, the NestedScrollView starts with an offset from the top (image 1). If there are few items in the scroll view so that they can all be shown at once, this doesn't happen (image 2).
I am using version 23.2.0 of the support library.
Image 1: WRONG - starts with offset from the top
Image 2: CORRECT - few items in the recycler view
I am pasting below my layout code:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="fill_vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Title:"
style="@style/TextAppearance.AppCompat.Caption"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/bodyPadding"
style="@style/TextAppearance.AppCompat.Body1"
android:text="Neque porro quisquam est qui dolorem ipsum"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Subtitle:"
style="@style/TextAppearance.AppCompat.Caption"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Body1"
android:padding="@dimen/bodyPadding"
android:text="Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:focusable="false"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
Am I missing something? Does anyone have any idea how to fix this?
Update 1
It works correctly if I place the following code when initializing my Activity:
sv.post(new Runnable() {
@Override
public void run() {
sv.scrollTo(0,0);
}
});
Where sv is a reference to the NestedScrollView, however it looks like quite a hack.
Update 2
As requested, here is my adapter code:
public abstract class ArrayAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private List<T> mObjects;
public ArrayAdapter(final List<T> objects) {
mObjects = objects;
}
/**
* Adds the specified object at the end of the array.
*
* @param object The object to add at the end of the array.
*/
public void add(final T object) {
mObjects.add(object);
notifyItemInserted(getItemCount() - 1);
}
/**
* Remove all elements from the list.
*/
public void clear() {
final int size = getItemCount();
mObjects.clear();
notifyItemRangeRemoved(0, size);
}
@Override
public int getItemCount() {
return mObjects.size();
}
public T getItem(final int position) {
return mObjects.get(position);
}
public long getItemId(final int position) {
return position;
}
/**
* Returns the position of the specified item in the array.
*
* @param item The item to retrieve the position of.
* @return The position of the specified item.
*/
public int getPosition(final T item) {
return mObjects.indexOf(item);
}
/**
* Inserts the specified object at the specified index in the array.
*
* @param object The object to insert into the array.
* @param index The index at which the object must be inserted.
*/
public void insert(final T object, int index) {
mObjects.add(index, object);
notifyItemInserted(index);
}
/**
* Removes the specified object from the array.
*
* @param object The object to remove.
*/
public void remove(T object) {
final int position = getPosition(object);
mObjects.remove(object);
notifyItemRemoved(position);
}
/**
* Sorts the content of this adapter using the specified comparator.
*
* @param comparator The comparator used to sort the objects contained in this adapter.
*/
public void sort(Comparator<? super T> comparator) {
Collections.sort(mObjects, comparator);
notifyItemRangeChanged(0, getItemCount());
}
}
And here is my ViewHolder:
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView txt;
public ViewHolder(View itemView) {
super(itemView);
txt = (TextView) itemView;
}
public void render(String text) {
txt.setText(text);
}
}
And here is the layout of each item in the RecyclerView (it's just android.R.layout.simple_spinner_item
- this screen is only for showing an example of this bug):
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/spinnerItemStyle"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:textAlignment="inherit"/>
回答1:
I solved such issue by setting:
<ImageView ...
android:focusableInTouchMode="true"/>
to my view above RecyclerView (which was hidden after unwanted scroll). Try to set this property to your LinearLayout above RecyclerView or to LinearLayout which is container of RecyclerView (helped me in another case).
As I see in NestedScrollView source it tries to focus the first possible child in onRequestFocusInDescendants and if only RecyclerView is focusable it wins.
Edit (thanks to Waran): and for smooth scroll don't forget to set yourRecyclerView.setNestedScrollingEnabled(false);
回答2:
In your LinearLayout
immediate after NestedScrollView
, use android:descendantFocusability
in the following way
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp"
android:descendantFocusability="blocksDescendants">
EDIT
Since many of them getting this answer useful, will also provide explanation.
The use of descendantFocusability
is given here. And as of focusableInTouchMode
over here.
So using blocksDescendants
in descendantFocusability
do not allows it's child to gain focus while touching and hence unplanned behaviour can be stopped.
As for focusInTouchMode
, both AbsListView
and RecyclerView
calls the method setFocusableInTouchMode(true);
in their constructor by default, so it is not required to use that attribute in your XML layouts.
And for NestedScrollView
following method is used:
private void initScrollView() {
mScroller = ScrollerCompat.create(getContext(), null);
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
Here, setFocusable()
method is used instead of setFocusableInTouchMode()
. But according to this post, focusableInTouchMode
should be avoided unless for certain conditions as it breaks consistency with Android normal behaviour. A game is a good example of an application that can make good use of the focusable in touch mode property. MapView, if used in fullscreen as in Google Maps, is another good example of where you can use focusable in touch mode correctly.
回答3:
android:descendantFocusability="blocksDescendants"
inside LinearLayout Worked for me .
回答4:
I had the same issue and sovled by extending NestedScrollView and disabling focusing children. For some reason, RecyclerView always requested focus even when I just opened and closed the drawer.
public class DummyNestedScrollView extends NestedScrollView {
public DummyNestedScrollView(Context context) {
super(context);
}
public DummyNestedScrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public DummyNestedScrollView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Fixind problem with recyclerView in nested scrollview requesting focus
* http://stackoverflow.com/questions/36314836/recycler-view-inside-nestedscrollview-causes-scroll-to-start-in-the-middle
* @param child
* @param focused
*/
@Override
public void requestChildFocus(View child, View focused) {
Log.d(getClass().getSimpleName(), "Request focus");
//super.requestChildFocus(child, focused);
}
/**
* http://stackoverflow.com/questions/36314836/recycler-view-inside-nestedscrollview-causes-scroll-to-start-in-the-middle
* @param direction
* @param previouslyFocusedRect
* @return
*/
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
Log.d(getClass().getSimpleName(), "Request focus descendants");
//return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
return false;
}
}
回答5:
In my case this code solves mine issue
RecyclerView recyclerView = findViewById(R.id.recyclerView);
NestedScrollView nestedScrollView= findViewById(R.id.nestedScrollView);
recyclerView.setFocusable(false);
nestedScrollView.requestFocus();
//populate recyclerview here
My layout contains a parent layout as NestedScrollView which has a child LinearLayout. The LinearLayout has orientation "vertical" and childs RecyclerView and EditText. Reference
回答6:
I have two guesses.
First:Try putting this line on your NestedScrollView
app:layout_behavior="@string/appbar_scrolling_view_behavior"
Second: Use
<android.support.design.widget.CoordinatorLayout
as your parent view Like this
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="fill_vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Title:"/>
<TextView
style="@style/TextAppearance.AppCompat.Body1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/bodyPadding"
android:text="Neque porro quisquam est qui dolorem ipsum"/>
<TextView
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Subtitle:"/>
<TextView
style="@style/TextAppearance.AppCompat.Body1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/bodyPadding"
android:text="Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
My last possible solution. I swear :)
回答7:
In Java code, after initializing your recyclerView and setting the adapter, add this line:
recyclerView.setNestedScrollingEnabled(false)
You can also try to wrap the layout withing a relativeLayout so that the views stay at the same position but recyclerView (which scroll) is first in xml hierarchy. The last suggestion a desperate attempt:p
回答8:
This problem arrives due to recycle view Focus.
Automatically all focus gone to recycle view if its size extended the size of screen.
adding android:focusableInTouchMode="true"
to first ChildView like TextView
, Button
and so on(Not on ViewGroup
like Linear
, Relative
and So on) make sense to solve the problem but API Level 25 and above solution doesn't work.
Just add these 2 line in your ChildView like TextView
, Button
and So on (Not on ViewGroup
like Linear
, Relative
and So on)
android:focusableInTouchMode="true"
android:focusable="true"
I just faced this problem on API level 25. I hope other people don't waste time in this.
For Smooth Scrolling On RecycleView add this line
android:nestedScrollingEnabled="false"
but adding this attiributes only works with API level 21 or above. If you want that smoothing scrolling work on below API level 25 then add this line in your class
mList = findViewById(R.id.recycle_list);
ViewCompat.setNestedScrollingEnabled(mList, false);
回答9:
As I am late in responding, but may can help someone else. Just use the below or higher version in your app level build.gradle and the issue is removed.
compile com.android.support:recyclerview-v7:23.2.1
回答10:
to scroll to the top, just call this in setcontentview
:
scrollView.SmoothScrollTo(0, 0);
回答11:
Just add android:descendantFocusability="blocksDescendants"
on the ViewGroup inside the NestedScrollView.
来源:https://stackoverflow.com/questions/36314836/recycler-view-inside-nestedscrollview-causes-scroll-to-start-in-the-middle