Chế độ xem tái chế bên trong NestedScrollView khiến cuộn bắt đầu ở giữa


129

Tôi nhận được một hành vi cuộn kỳ lạ khi tôi thêm RecyclerView bên trong NestedScrollView.

Điều xảy ra là bất cứ khi nào scrollview có nhiều hàng hơn mức có thể hiển thị trên màn hình, ngay khi hoạt động được khởi chạy, NestedScrollView sẽ bắt đầu với phần bù từ trên cùng (hình 1). Nếu có một vài mục trong chế độ xem cuộn để tất cả chúng có thể được hiển thị cùng một lúc, điều này không xảy ra (hình 2).

Tôi đang sử dụng phiên bản 23.2.0 của thư viện hỗ trợ.

Hình 1 : SAI - bắt đầu với phần bù từ trên xuống

Hình 1

Hình 2 : ĐÚNG - một vài mục trong chế độ xem tái chế

Hình 2

Tôi dán bên dưới mã bố trí của tôi:

<?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>

Tui bỏ lỡ điều gì vậy? Liệu có ai đó có bất cứ ý kiến ​​nào để sửa thứ này?

Cập nhật 1

Nó hoạt động chính xác nếu tôi đặt mã sau đây khi khởi tạo Hoạt động của mình:

sv.post(new Runnable() {
        @Override
        public void run() {
            sv.scrollTo(0,0);
        }
});

Trong đó sv là một tham chiếu đến NestedScrollView, tuy nhiên có vẻ như nó khá là hack.

Cập nhật 2

Theo yêu cầu, đây là mã bộ điều hợp của tôi:

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());
    }
}

Và đây là ViewHolder của tôi:

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);
    }
}

Và đây là cách bố trí của từng mục trong RecyclerView (chỉ là android.R.layout.simple_spinner_item- màn hình này chỉ để hiển thị một ví dụ về lỗi này):

<?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"/>

thử với android:focusableInTouchMode="false"cho RecyclerView? rõ ràng là "buộc bố cục của bạn phải xuống dưới ... (nơi nó bắt đầu khi bạn có 1295 vật phẩm? dưới cùng hoặc chỉ là phần bù nhỏ như màn hình đầu tiên)
snachmsm

Đặt android: clipToPadding = xông đúng sự thật vào NestedScrollView của bạn.
natario 30/03/2016

Ngoài ra: việc giữ chế độ xem có thể cuộn bên trong một chế độ xem có thể cuộn tương tự khác không phải là mô hình rất tốt ... hy vọng bạn đang sử dụng đúng cáchLayoutManager
snachmsm

Đã thử cả hai đề xuất của bạn và không may không làm việc. @snachmsm bất kể số lượng mục trong chế độ xem tái chế, phần bù luôn giống nhau. Về việc đặt RecyclerView bên trong NestedScrollView có phải là một mô hình tốt hay không, điều này thực sự đã được một kỹ sư của Google khuyến nghị tại plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa

1
Ngay cả tôi cũng gặp vấn đề tương tự khi sử dụng RecyclerView bên trong NestedScrollView. Đó là một mẫu xấu vì bản thân mẫu tái chế sẽ không hoạt động. Tất cả các chế độ xem sẽ được rút ra tại một thời điểm (vì WRAP_CONTENT cần chiều cao của chế độ xem tái chế). Sẽ không có bất kỳ chế độ xem tái chế nào trong nền, vì vậy mục đích chính của chế độ xem tái chế tự nó không hoạt động. Nhưng thật dễ dàng để quản lý dữ liệu và vẽ bố cục bằng cách sử dụng chế độ xem tái chế, đó là lý do duy nhất bạn có thể sử dụng mẫu này. Vì vậy, tốt hơn là không sử dụng nó cho đến khi bạn yêu cầu nó chắc chắn.
Ashok Varma

Câu trả lời:


253

Tôi đã giải quyết vấn đề như vậy bằng cách thiết lập:

<ImageView ...
android:focusableInTouchMode="true"/>

theo quan điểm của tôi ở trên RecyclerView (đã bị ẩn sau khi cuộn không mong muốn). Hãy thử đặt thuộc tính này thành linearLayout của bạn phía trên RecyclerView hoặc linearLayout, là nơi chứa RecyclerView (giúp tôi trong trường hợp khác).

Như tôi thấy trong nguồn NestedScrollView, nó cố gắng tập trung đứa trẻ đầu tiên có thể vào onRequestF FocusInDescendants và nếu chỉ RecyclerView có thể lấy nét được thì nó sẽ thắng.

Chỉnh sửa (nhờ Waran): và để cuộn mượt mà, đừng quên thiết lập yourRecyclerView.setNestedScrollingEnabled(false);


12
Nhân tiện, bạn cần thêm yourRecyclerView.setNestedScrollingEnables (false); để làm cho cuộn trơn tru
Waran-

1
@Kenji chỉ để xem đầu tiên bên trong linearLayout nơi RecyclerView được đặt. Hoặc với chính tuyến tính này (thùng chứa RecyclerView) nếu thay đổi đầu tiên không có ích.
Dmitry Gavrilko

1
@DmitryGavrilko Tôi có một vấn đề, trong đó RecyclView nằm trong NestedScrollview. Tôi không thể sử dụng recyclview.scrollToP vị trí (X); , nó không hoạt động. Tôi đã thử mọi thứ trong 6 ngày qua, nhưng tôi có thể vượt qua nó. Bất kì lời đề nghị nào? Tôi sẽ rất biết ơn !
Karoly

3
Bạn cũng có thể chỉ đặt thành android:focusableInTouchMode="false"recyclerView để không cần phải đặt đúng cho tất cả các chế độ xem khác.
Aksiom

1
Đồng ý với Dmitry Gavrilko, đã làm việc sau khi cài đặt android: FocusableInTouchMode = "true" cho linearlayout có chứa recyclerview. @Aksiom cài đặt android: FocusableInTouchMode = "false" cho recyclerView không hoạt động với tôi.
Shendre Kiran

103

Trước LinearLayoutmắt của bạn NestedScrollView, sử dụng android:descendantFocusabilitytheo cách sau

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp"
        android:descendantFocusability="blocksDescendants">

BIÊN TẬP

Vì nhiều người trong số họ nhận được câu trả lời này hữu ích, cũng sẽ cung cấp giải thích.

Việc sử dụng descendantFocusabilityđược đưa ra ở đây . Và như focusableInTouchModetrên đây . Vì vậy, sử dụng blocksDescendantstrong descendantFocusabilitykhông cho phép trẻ em lấy được trọng tâm trong khi chạm và do đó hành vi không có kế hoạch có thể bị dừng lại.

Về phần focusInTouchMode, cả hai AbsListViewRecyclerViewgọi phương thức setFocusableInTouchMode(true);trong hàm tạo của chúng theo mặc định, do đó không bắt buộc phải sử dụng thuộc tính đó trong các bố cục XML của bạn.

Và cho NestedScrollViewphương pháp sau đây được sử dụng:

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();
    }

Ở đây, setFocusable()phương pháp được sử dụng thay vì setFocusableInTouchMode(). Nhưng theo bài đăng này , focusableInTouchModenên tránh trừ khi đối với một số điều kiện nhất định vì nó phá vỡ tính nhất quán với hành vi thông thường của Android. Trò chơi là một ví dụ điển hình về một ứng dụng có thể sử dụng tốt tính năng có thể lấy nét trong chế độ cảm ứng. MapView, nếu được sử dụng ở chế độ toàn màn hình như trong Google Maps, là một ví dụ điển hình khác về việc bạn có thể sử dụng tiêu cự ở chế độ cảm ứng một cách chính xác.


Cảm ơn bạn! Nó hoạt động trong trường hợp của tôi. Thật không may, android: FocusableInTouchMode = "true" không hoạt động với tôi.
Mikhail

1
Điều này làm việc cho tôi. Tôi đã thêm dòng mã này vào chế độ xem chính của recyclerview của tôi (trong trường hợp của tôi, đó là một linearLayout) và nó hoạt động như một bùa mê.
amzer

Tôi đã sử dụng NestedScrollView, theo sau là linearLayout có chứa RecyclerView và EditText. Hành vi cuộn đã được khắc phục bằng cách sử dụng android: descendantF Focusability = "blocksDescendants" bên trong linearLayout nhưng EditText không thể lấy nét. Bất cứ ý tưởng những gì đang xảy ra?
Sagar Chapagain

@SagarChapagain Đây là tài sản của blocksDescendantsviệc chặn tất cả sự tập trung của con cháu. Tôi không chắc, nhưng hãy thửbeforeDescendants
Jimit Patel

2
@JimitPatel vấn đề của tôi đã được giải quyết bằng cách sử dụngrecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain

16
android:descendantFocusability="blocksDescendants"

bên trong linearLayout Làm việc cho tôi.


6
Nhưng điều đó có nghĩa là việc tập trung vào các chế độ xem bên trong sẽ bị chặn và người dùng sẽ không thể tiếp cận với họ?
nhà phát triển Android

11

Tôi đã có cùng một vấn đề và được giải quyết bằng cách mở rộng NestedScrollView và vô hiệu hóa trẻ em tập trung. Vì một số lý do, RecyclerView luôn yêu cầu sự tập trung ngay cả khi tôi vừa mở và đóng ngăn kéo.

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;
}
}

Nó hoạt động nhưng nó cũng phá vỡ khái niệm tập trung và bạn có thể dễ dàng có được tình huống với hai trường tập trung trên màn hình. Vì vậy, chỉ sử dụng nó nếu bạn không có EditText bên trong RecyclerViewchủ sở hữu.
Andrey Chernoprudov

4

Trong trường hợp của tôi, mã này giải quyết vấn đề của tôi

RecyclerView recyclerView = findViewById(R.id.recyclerView);
NestedScrollView nestedScrollView= findViewById(R.id.nestedScrollView);

recyclerView.setFocusable(false);
nestedScrollView.requestFocus();

//populate recyclerview here

Bố cục của tôi chứa bố cục cha là NestedScrollView có tuyến tính conLayout. LinearLayout có định hướng "dọc" và RecyclerView và EditText dành cho trẻ em. Tài liệu tham khảo


2

Tôi có hai dự đoán.

Đầu tiên: Hãy thử đặt dòng này trên NestedScrollView của bạn

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Thứ hai: Sử dụng

<android.support.design.widget.CoordinatorLayout

như cha mẹ của bạn xem như thế này

<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>

Giải pháp cuối cùng của tôi. Tôi thề :)


Và thêm Điều phối
viênLayout

2

Vấn đề này đến do chế độ xem tái chế Tập trung.

Tự động tất cả tiêu điểm sẽ chuyển sang chế độ xem tái chế nếu kích thước của nó mở rộng kích thước màn hình.

thêm android:focusableInTouchMode="true"để ChildView đầu tiên như TextView, Buttonvà vân vân (Không phải ViewGroupnhư Linear, Relativevà vân vân) làm cho tinh thần để giải quyết vấn đề nhưng API Cấp 25 trở lên giải pháp không làm việc.

Chỉ cần thêm 2 dòng này trong ChildView của bạn như TextView, Buttonvà vân vân (Không phải ViewGroupnhư Linear, Relativevà vân vân)

 android:focusableInTouchMode="true"
 android:focusable="true"

Tôi chỉ gặp phải vấn đề này ở cấp độ API 25. Tôi hy vọng người khác không lãng phí thời gian vào việc này.

Để di chuyển mượt mà trên RecyclView, hãy thêm dòng này

 android:nestedScrollingEnabled="false"

nhưng việc thêm các chứng nhận này chỉ hoạt động với API cấp 21 trở lên. Nếu bạn muốn công việc cuộn trơn tru hoạt động ở bên dưới API cấp 25 thì hãy thêm dòng này vào lớp của bạn

 mList = findViewById(R.id.recycle_list);
 ViewCompat.setNestedScrollingEnabled(mList, false);

0

Trong mã Java, sau khi khởi tạo recyclerView của bạn và đặt bộ điều hợp, hãy thêm dòng này:

recyclerView.setNestedScrollingEnabled(false)

Bạn cũng có thể thử bao bọc bố cục với một phần tương đối để các khung nhìn giữ nguyên vị trí nhưng recyclerView (cuộn nào) trước tiên trong hệ thống phân cấp xml. Gợi ý cuối cùng là một nỗ lực tuyệt vọng: p


0

Khi tôi trả lời trễ, nhưng có thể giúp đỡ người khác. Chỉ cần sử dụng phiên bản dưới đây hoặc cao hơn trong build.gradle cấp ứng dụng của bạn và vấn đề được xóa.

compile com.android.support:recyclerview-v7:23.2.1

0

để cuộn lên trên cùng, chỉ cần gọi nó trong setcontentview:

scrollView.SmoothScrollTo(0, 0);

điều này không cung cấp câu trả lời cho câu hỏi
Umar Ata

0

Chỉ cần thêm android:descendantFocusability="blocksDescendants"vào Viewgroup bên trong NestedScrollView.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.