มุมมอง Recycler ภายใน NestedScrollView ทำให้การเลื่อนเริ่มตรงกลาง


130

ฉันมีพฤติกรรมแปลก ๆ ในการเลื่อนเมื่อฉันเพิ่ม RecyclerView ภายใน NestedScrollView

สิ่งที่เกิดขึ้นคือเมื่อใดก็ตามที่ scrollview มีแถวมากกว่าที่จะแสดงในหน้าจอได้ทันทีที่มีการเปิดกิจกรรม NestedScrollView จะเริ่มต้นด้วยการชดเชยจากด้านบน (ภาพที่ 1) หากมีรายการเพียงไม่กี่รายการในมุมมองแบบเลื่อนเพื่อให้สามารถแสดงทั้งหมดได้ในครั้งเดียวสิ่งนี้จะไม่เกิดขึ้น (ภาพที่ 2)

ฉันกำลังใช้เวอร์ชัน 23.2.0 ของไลบรารีการสนับสนุน

ภาพที่ 1 : ผิด - เริ่มต้นด้วยการชดเชยจากด้านบน

ภาพที่ 1

รูปภาพ 2 : ถูกต้อง - มีไม่กี่รายการในมุมมองผู้รีไซเคิล

ภาพที่ 2

ฉันกำลังวางโค้ดด้านล่างของฉัน:

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

ฉันพลาดอะไรไปรึเปล่า? ใครมีความคิดวิธีแก้ไขปัญหานี้หรือไม่?

อัปเดต 1

ทำงานได้อย่างถูกต้องถ้าฉันวางรหัสต่อไปนี้เมื่อเริ่มต้นกิจกรรมของฉัน:

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

โดยที่ sv อ้างอิงถึง NestedScrollView แต่ดูเหมือนว่าจะเป็นการแฮ็ก

อัปเดต 2

ตามที่ร้องขอนี่คือรหัสอะแดปเตอร์ของฉัน:

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

และนี่คือ 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);
    }
}

และนี่คือเลย์เอาต์ของแต่ละรายการใน RecyclerView (เป็นเพียงandroid.R.layout.simple_spinner_itemหน้าจอนี้ใช้สำหรับแสดงตัวอย่างของจุดบกพร่องนี้เท่านั้น):

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

พยายามด้วยandroid:focusableInTouchMode="false"เพื่อRecyclerView? smth อย่างชัดเจนคือ "บังคับให้เลย์เอาต์ของคุณไปที่ด้านล่าง ... (เริ่มต้นที่ใดเมื่อคุณมี 1295 รายการด้านล่างหรือออฟเซ็ตด้านบนเล็ก ๆ เช่นหน้าจอแรก)
snachmsm

ตั้งค่า android: clipToPadding =“ true” เป็น NestedScrollView ของคุณ
นาตาริโอ

ยัง: การรักษามุมมองที่เลื่อนได้ภายในมุมมองที่เลื่อนได้อีกแบบหนึ่งไม่ใช่รูปแบบที่ดีมาก ... หวังว่าคุณจะใช้อย่างเหมาะสมLayoutManager
snachmsm

ลองทั้งสองคำแนะนำของคุณแล้ว แต่ก็ไม่ได้ผล @snachmsm ไม่ว่ารายการในมุมมองผู้รีไซเคิลจะมีจำนวนเท่าใดค่าชดเชยจะเท่ากันเสมอ การวาง RecyclerView ไว้ใน NestedScrollView นั้นเป็นรูปแบบที่ดีหรือไม่วิศวกรของ Google ได้รับการแนะนำที่plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa

1
แม้ว่าฉันจะมีปัญหาเดียวกันเมื่อใช้ RecyclerView ภายใน NestedScrollView เป็นรูปแบบที่ไม่ดีเนื่องจากรูปแบบรีไซเคิลใช้ไม่ได้ มุมมองทั้งหมดจะถูกดึงออกมาพร้อมกัน (เนื่องจาก WRAP_CONTENT ต้องการความสูงของมุมมองรีไซเคิล) จะไม่มีการรีไซเคิลมุมมองใด ๆ ในพื้นหลังดังนั้นจุดประสงค์หลักของมุมมองรีไซเคิลจึงไม่ทำงาน แต่ง่ายต่อการจัดการข้อมูลและวาดเค้าโครงโดยใช้มุมมองผู้รีไซเคิลนั่นเป็นเหตุผลเดียวที่คุณสามารถใช้รูปแบบนี้ได้ ดังนั้นดีกว่าที่จะไม่ใช้จนกว่าคุณจะต้องการอย่างแน่นอน
Ashok Varma

คำตอบ:


254

ฉันแก้ไขปัญหาดังกล่าวโดยการตั้งค่า:

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

ในมุมมองของฉันด้านบน RecyclerView (ซึ่งซ่อนอยู่หลังจากการเลื่อนที่ไม่ต้องการ) พยายามตั้งค่าคุณสมบัตินี้เป็น LinearLayout ของคุณเหนือ RecyclerView หรือ LinearLayout ซึ่งเป็นคอนเทนเนอร์ของ RecyclerView (ช่วยฉันในอีกกรณีหนึ่ง)

อย่างที่ฉันเห็นในซอร์ส NestedScrollView มันพยายามที่จะโฟกัสลูกคนแรกที่เป็นไปได้ใน onRequestFocusInDescendants และถ้ามีเพียง RecyclerView เท่านั้นที่โฟกัสได้ก็จะชนะ

แก้ไข (ขอบคุณ Waran): และเพื่อการเลื่อนที่ราบรื่นอย่าลืมตั้งค่า yourRecyclerView.setNestedScrollingEnabled(false);


12
โดยวิธีนี้คุณต้องเพิ่ม yourRecyclerView.setNestedScrollingEnabled (false); เพื่อให้การเลื่อนราบรื่น
Waran-

1
@Kenji เท่านั้นเพื่อดูครั้งแรกภายใน LinearLayout ที่วาง RecyclerView ไว้ หรือ LinearLayout (RecyclerView container) นั้นเองหากการเปลี่ยนแปลงครั้งแรกไม่ได้ผล
มิทรี Gavrilko

1
@DmitryGavrilko ฉันมีปัญหาโดยที่ RecycleView อยู่ใน NestedScrollview ฉันไม่สามารถใช้ recycleview.scrollToPosition (X); มันก็ไม่ได้ผล ฉันพยายามทุกอย่างในช่วง 6 วันที่ผ่านมา แต่ฉันสามารถเอาชนะมันได้ ข้อเสนอแนะใด ๆ ผมจะขอบคุณมาก !
Karoly

3
คุณยังสามารถตั้งค่าandroid:focusableInTouchMode="false"เป็น RecyclerView เพื่อที่คุณไม่จำเป็นต้องตั้งค่าจริงสำหรับมุมมองอื่น ๆ ทั้งหมด
Aksiom

1
เห็นด้วยกับ Dmitry Gavrilko ซึ่งทำงานได้หลังจากตั้งค่า android: focusableInTouchMode = "true" เป็น linearlayout ซึ่งมีรายการรีไซเคิล การตั้งค่า @Aksiom android: focusableInTouchMode = "false" ไปที่ RecyclerView ไม่ได้ผลสำหรับฉัน
Shendre Kiran

103

ในLinearLayoutทันทีของคุณNestedScrollViewใช้android:descendantFocusabilityในลักษณะต่อไปนี้

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

แก้ไข

เนื่องจากหลายคนได้รับคำตอบนี้มีประโยชน์จึงจะให้คำอธิบายด้วย

การใช้descendantFocusabilityจะได้รับที่นี่ และเป็นของfocusableInTouchModeมากกว่าที่นี่ ดังนั้นการใช้blocksDescendantsอินdescendantFocusabilityจะไม่อนุญาตให้เด็กได้โฟกัสในขณะที่สัมผัสและด้วยเหตุนี้จึงสามารถหยุดพฤติกรรมที่ไม่ได้วางแผนไว้ได้

สำหรับfocusInTouchModeทั้งสองAbsListViewและRecyclerViewเรียกใช้เมธอดsetFocusableInTouchMode(true);ในตัวสร้างตามค่าเริ่มต้นดังนั้นจึงไม่จำเป็นต้องใช้แอตทริบิวต์นั้นในเค้าโครง XML ของคุณ

และสำหรับNestedScrollViewวิธีการต่อไปนี้ใช้:

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

ที่นี่setFocusable()ใช้วิธีการแทนsetFocusableInTouchMode(). แต่ตามนี้โพสต์ , focusableInTouchModeควรหลีกเลี่ยงเว้นแต่เงื่อนไขที่จะแบ่งความสอดคล้องกับ Android พฤติกรรมปกติ เกมเป็นตัวอย่างที่ดีของแอปพลิเคชันที่สามารถใช้ประโยชน์จากคุณสมบัติที่โฟกัสได้ในโหมดสัมผัส MapView หากใช้แบบเต็มหน้าจอเช่นเดียวกับใน Google แผนที่เป็นอีกตัวอย่างที่ดีที่คุณสามารถใช้โฟกัสได้ในโหมดสัมผัสอย่างถูกต้อง


ขอบคุณ! มันใช้ได้ในกรณีของฉัน น่าเสียดายที่ android: focusableInTouchMode = "true" ไม่ทำงานสำหรับฉัน
Mikhail

1
สิ่งนี้ได้ผลสำหรับฉัน ฉันเพิ่มบรรทัดของรหัสนี้ในมุมมองหลักของมุมมองรีไซเคิลของฉัน (ในกรณีของฉันมันเป็น LinearLayout) และมันใช้งานได้ดี
amzer

ฉันใช้ NestedScrollView ตามด้วย LinearLayout ซึ่งมี RecyclerView และ EditText พฤติกรรมการเลื่อนได้รับการแก้ไขโดยใช้ android: inheritantFocusability = "blocksDescendants" ภายใน LinearLayout แต่ EditText ไม่สามารถรับโฟกัสได้ มีความคิดว่าเกิดอะไรขึ้น?
Sagar Chapagain

@SagarChapagain เป็นคุณสมบัติของblocksDescendantsการปิดกั้นการโฟกัสของลูกหลานทั้งหมด ฉันไม่แน่ใจ แต่ลองbeforeDescendants
Jimit Patel

2
@JimitPatel ปัญหาของฉันได้รับการแก้ไขโดยใช้recyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain

17
android:descendantFocusability="blocksDescendants"

ภายใน LinearLayout ทำงานให้ฉัน


6
แต่หมายความว่าการมุ่งเน้นไปที่มุมมองภายในจะถูกบล็อกและผู้ใช้จะไม่สามารถเข้าถึงได้ใช่หรือไม่
นักพัฒนา Android

11

ฉันมีปัญหาเดียวกันและแก้ไขโดยการขยาย NestedScrollView และปิดใช้งานการโฟกัสเด็ก ด้วยเหตุผลบางประการ RecyclerView ขอโฟกัสเสมอแม้ว่าฉันจะเพิ่งเปิดและปิดลิ้นชักก็ตาม

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

มันใช้งานได้ แต่ยังทำลายแนวคิดการโฟกัสและคุณสามารถรับสถานการณ์ได้อย่างง่ายดายด้วยฟิลด์ที่เน้นสองช่องบนหน้าจอ ดังนั้นการใช้มันเฉพาะถ้าคุณมี EditText ภายในไม่มีRecyclerViewผู้ถือ
Andrey Chernoprudov

4

ในกรณีของฉันรหัสนี้ช่วยแก้ปัญหาของฉันได้

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

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

//populate recyclerview here

เลย์เอาต์ของฉันมีเค้าโครงหลักเป็น NestedScrollView ซึ่งมีลูก LinearLayout LinearLayout มีการวางแนว "แนวตั้ง" และเด็ก RecyclerView และ EditText การอ้างอิง


2

ฉันเดาได้สองอย่าง

ขั้นแรก: ลองวางบรรทัดนี้บน NestedScrollView ของคุณ

app:layout_behavior="@string/appbar_scrolling_view_behavior"

ประการที่สอง: ใช้

<android.support.design.widget.CoordinatorLayout

ตามที่ผู้ปกครองของคุณเห็นเช่นนี้

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

ทางออกสุดท้ายที่เป็นไปได้ของฉัน ฉันสาบาน :)


และการเพิ่ม CoordinatorLayout เนื่องจากมุมมองหลักก็ไม่ได้ผลเช่นกัน ... ขอบคุณยัง
ไง

2

ปัญหานี้มาถึงเนื่องจากโฟกัสมุมมองรีไซเคิล

โฟกัสทั้งหมดจะไปที่มุมมองรีไซเคิลโดยอัตโนมัติหากขนาดขยายขนาดของหน้าจอ

เพิ่มandroid:focusableInTouchMode="true"การ ChildView แรกเหมือนTextView, Buttonและอื่น ๆ (ไม่ได้อยู่ViewGroupเหมือนLinear, Relativeและอื่น ๆ ) ให้ความรู้สึกในการแก้ปัญหา แต่ API ระดับ 25 และการแก้ไขปัญหาดังกล่าวข้างต้นไม่ทำงาน

เพียงแค่เพิ่มทั้ง 2 สายใน ChildView ของคุณเช่นTextView, Buttonและอื่น ๆ (ไม่ได้อยู่ViewGroupเหมือนLinear, Relativeและอื่น ๆ )

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

ฉันเพิ่งประสบปัญหานี้ใน API ระดับ 25 ฉันหวังว่าคนอื่นจะไม่เสียเวลากับเรื่องนี้

สำหรับการเลื่อนอย่างราบรื่นบน RecycleView ให้เพิ่มบรรทัดนี้

 android:nestedScrollingEnabled="false"

แต่การเพิ่มคุณสมบัตินี้ใช้ได้กับ API ระดับ 21 ขึ้นไปเท่านั้น หากคุณต้องการให้การเลื่อนที่ราบรื่นทำงานที่ต่ำกว่า API ระดับ 25 ให้เพิ่มบรรทัดนี้ในชั้นเรียนของคุณ

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

0

ในโค้ด Java หลังจากกำหนดค่าเริ่มต้น RecyclerView และตั้งค่าอะแด็ปเตอร์ให้เพิ่มบรรทัดนี้:

recyclerView.setNestedScrollingEnabled(false)

คุณยังสามารถลองตัดเลย์เอาต์โดยใช้ relativeLayout เพื่อให้มุมมองยังคงอยู่ที่ตำแหน่งเดิม แต่จะมีการเรียกดูรีไซเคิล (ซึ่งเลื่อน) เป็นลำดับแรกในลำดับชั้น xml คำแนะนำสุดท้ายเป็นความพยายามอย่างยิ่ง: p


0

เนื่องจากฉันตอบช้า แต่อาจช่วยคนอื่นได้ เพียงใช้เวอร์ชันด้านล่างหรือสูงกว่าใน build.gradle ระดับแอปของคุณปัญหาจะถูกลบออก

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

0

เพื่อเลื่อนขึ้นไปด้านบนเรียกสิ่งนี้ในsetcontentview:

scrollView.SmoothScrollTo(0, 0);

สิ่งนี้ไม่ได้ให้คำตอบสำหรับคำถาม
Umar Ata

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.