Tampilan pendaur ulang di dalam NestedScrollView menyebabkan gulir dimulai di tengah

129

Saya mendapatkan perilaku gulir yang aneh ketika saya menambahkan RecyclerView di dalam NestedScrollView.

Apa yang terjadi adalah bahwa setiap kali scrollview memiliki lebih banyak baris daripada yang dapat ditampilkan di layar, segera setelah aktivitas diluncurkan, NestedScrollView dimulai dengan offset dari atas (gambar 1). Jika ada beberapa item dalam tampilan gulir sehingga semuanya dapat ditampilkan sekaligus, ini tidak terjadi (gambar 2).

Saya menggunakan versi 23.2.0 dari perpustakaan dukungan.

Gambar 1 : SALAH - dimulai dengan offset dari atas

Gambar 1

Gambar 2 : BENAR - beberapa item dalam tampilan pendaur ulang

Gambar 2

Saya menempelkan di bawah kode tata letak saya:

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

Apakah saya melewatkan sesuatu? Adakah yang tahu bagaimana cara memperbaikinya?

Perbarui 1

Ini berfungsi dengan benar jika saya menempatkan kode berikut ketika menginisialisasi Aktivitas saya:

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

Di mana sv adalah referensi ke NestedScrollView, namun sepertinya ini adalah peretasan.

Perbarui 2

Seperti yang diminta, ini adalah kode adaptor saya:

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

Dan inilah ViewHolder saya:

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

Dan di sini adalah tata letak setiap item dalam RecyclerView (hanya android.R.layout.simple_spinner_item- layar ini hanya untuk menunjukkan contoh bug ini):

<?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"/>
Luccas Correa
sumber
mencoba dengan android:focusableInTouchMode="false"untuk RecyclerView? pertanda jelas adalah "memaksa tata letak Anda untuk pergi ke bawah ... (di mana ia dimulai ketika Anda memiliki 1.295 item? bawah atau hanya offset kecil atas seperti layar pertama)
snachmsm
Set android: clipToPadding = "true" ke NestedScrollView Anda.
natario
juga: menjaga tampilan yang dapat digulir di dalam tampilan cara-yang-dapat-digulir yang lain bukanlah pola yang sangat baik ... harap Anda menggunakan yang benarLayoutManager
snachmsm
Mencoba kedua saran Anda dan sayangnya tidak berhasil. @ snachmsm, berapa pun jumlah item dalam tampilan pendaur ulang, offset selalu sama. Mengenai apakah menempatkan RecyclerView di dalam NestedScrollView adalah pola yang baik, ini sebenarnya telah direkomendasikan oleh insinyur Google di plus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccas Correa
1
Bahkan saya memiliki masalah yang sama ketika menggunakan RecyclerView di dalam NestedScrollView. Ini adalah pola yang buruk karena pola pendaur ulang itu sendiri tidak akan berfungsi. Semua tampilan akan diambil pada satu waktu (karena WRAP_CONTENT membutuhkan ketinggian tampilan pendaur ulang). Tidak akan ada tampilan daur ulang di latar belakang, sehingga tujuan utama tampilan pendaur ulang itu sendiri tidak berfungsi. Tetapi mudah untuk mengelola data dan menggambar tata letak dengan menggunakan tampilan pendaur ulang, itulah satu-satunya alasan Anda dapat menggunakan pola ini. Jadi lebih baik tidak menggunakannya sampai kecuali Anda membutuhkannya.
Ashok Varma

Jawaban:

253

Saya memecahkan masalah tersebut dengan menetapkan:

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

untuk pandangan saya di atas RecyclerView (yang disembunyikan setelah gulir yang tidak diinginkan). Cobalah untuk mengatur properti ini ke LinearLayout Anda di atas RecyclerView atau ke LinearLayout yang merupakan wadah dari RecyclerView (membantu saya dalam kasus lain).

Seperti yang saya lihat di sumber NestedScrollView, ia mencoba memfokuskan anak pertama yang mungkin ada di onRequestFocusInDescendants dan jika hanya RecyclerView yang fokus, ia menang.

Sunting (terima kasih kepada Waran): dan untuk scroll yang lancar jangan lupa atur yourRecyclerView.setNestedScrollingEnabled(false);

Dmitry Gavrilko
sumber
12
Ngomong-ngomong, Anda perlu menambahkan YourRecyclerView.setNestedScrollingEnabled (false); untuk membuat gulir lancar
Waran-
1
@ Kenji hanya untuk melihat pertama di dalam LinearLayout di mana RecyclerView ditempatkan. Atau ke LinearLayout (wadah RecyclerView) itu sendiri jika perubahan pertama tidak membantu.
Dmitry Gavrilko
1
@DmitryGavrilko Saya punya masalah, di mana RecycleView berada di dalam NestedScrollview. Saya tidak bisa menggunakan recycleview.scrollToPosition (X); , itu tidak bekerja. Saya mencoba semuanya dalam 6 hari terakhir, tetapi saya bisa mengatasinya. ada saran? Saya akan sangat berterima kasih!
Karoly
3
Anda juga dapat mengatur android:focusableInTouchMode="false"ke recyclerView sehingga Anda tidak perlu mengatur true untuk semua tampilan lainnya.
Aksiom
1
Setuju dengan Dmitry Gavrilko, bekerja setelah mengatur android: focusableInTouchMode = "true" ke linearlayout yang berisi recyclerview. @Aksiom pengaturan android: focusableInTouchMode = "false" ke recyclerView tidak bekerja untuk saya.
Shendre Kiran
103

Dalam Anda LinearLayoutsetelah langsung NestedScrollView, penggunaan android:descendantFocusabilitydengan cara berikut

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

EDIT

Karena banyak dari mereka yang mendapatkan jawaban ini bermanfaat, juga akan memberikan penjelasan.

Penggunaan descendantFocusabilitydiberikan di sini . Dan sampai focusableInTouchModedi sini . Jadi gunakan blocksDescendantsdidescendantFocusability tidak memungkinkan anak untuk mendapatkan fokus sambil menyentuh dan karenanya perilaku yang tidak direncanakan dapat dihentikan.

Adapun focusInTouchMode, keduanya AbsListViewdan RecyclerViewmemanggil metodesetFocusableInTouchMode(true); dalam konstruktor mereka secara default, sehingga tidak diperlukan untuk menggunakan atribut itu dalam layout XML Anda.

Dan untuk NestedScrollViewmetode berikut digunakan:

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

Di sini, setFocusable()metode digunakan sebagai ganti setFocusableInTouchMode(). Tetapi menurut posting ini , focusableInTouchModeharus dihindari kecuali untuk kondisi tertentu karena merusak konsistensi dengan perilaku normal Android. Gim adalah contoh bagus aplikasi yang dapat memanfaatkan properti mode sentuh dengan baik. MapView, jika digunakan dalam layar penuh seperti pada Google Maps, adalah contoh bagus lainnya di mana Anda dapat menggunakan fokus pada mode sentuh dengan benar.

Jimit Patel
sumber
Terima kasih! Ini berfungsi dalam kasus saya. Sayangnya android: focusableInTouchMode = "true" tidak berfungsi untuk saya.
Mikhail
1
Ini berhasil untuk saya. Saya menambahkan baris kode ini ke tampilan induk dari recyclerview saya (dalam kasus saya, itu adalah LinearLayout) dan itu bekerja seperti pesona.
amzer
Saya menggunakan NestedScrollView diikuti oleh LinearLayout yang berisi RecyclerView dan EditText. Perilaku bergulir diperbaiki dengan menggunakan android: descendantFocusability = "blocksDescendants" di dalam LinearLayout tetapi EditText tidak bisa mendapatkan fokus. Adakah yang tahu apa yang terjadi?
Sagar Chapagain
@SagarChapagain Ini properti blocksDescendantsuntuk memblokir semua fokus keturunan. Saya tidak yakin, tetapi cobalahbeforeDescendants
Jimit Patel
2
@JimitPatel masalah saya terpecahkan dengan menggunakanrecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain
16
android:descendantFocusability="blocksDescendants"

di dalam LinearLayout Bekerja untuk saya.

Subash Bhattarai
sumber
6
Tetapi apakah itu berarti bahwa fokus pada tampilan di dalam akan diblokir, dan pengguna tidak akan dapat menjangkau mereka?
pengembang android
11

Saya memiliki masalah yang sama dan tersedot dengan memperpanjang NestedScrollView dan menonaktifkan anak-anak yang fokus. Untuk beberapa alasan, RecyclerView selalu meminta fokus bahkan ketika saya baru saja membuka dan menutup laci.

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;
}
}
Lubos Horacek
sumber
Ini berfungsi tetapi juga merusak konsep fokus dan Anda dapat dengan mudah mendapatkan situasi dengan dua bidang fokus pada layar. Jadi gunakan hanya jika Anda tidak memiliki RecyclerViewpemegang EditText di dalamnya .
Andrey Chernoprudov
4

Dalam kasus saya, kode ini memecahkan masalah saya

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

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

//populate recyclerview here

Tata letak saya berisi tata letak induk sebagai NestedScrollView yang memiliki LinearLayout anak. LinearLayout memiliki orientasi "vertikal" dan childs RecyclerView dan EditText. Referensi

Sagar Chapagain
sumber
2

Saya punya dua tebakan.

Pertama: Coba letakkan baris ini di NestedScrollView Anda

app:layout_behavior="@string/appbar_scrolling_view_behavior"

Kedua: Gunakan

<android.support.design.widget.CoordinatorLayout

sebagai tampilan orang tua Anda Suka ini

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

Solusi terakhir saya. Aku bersumpah :)

Andre Haueisen
sumber
Dan menambahkan CoordinatorLayout sebagai tampilan induk juga tidak berfungsi ... Terima kasih
Luccas Correa
2

Masalah ini muncul karena daur ulang tampilan Fokus.

Secara otomatis semua fokus hilang untuk mendaur ulang tampilan jika ukurannya memperluas ukuran layar.

menambahkan android:focusableInTouchMode="true"untuk ChildView pertama seperti TextView, Buttondan sebagainya (Tidak pada ViewGroupseperti Linear, Relativedan Jadi pada) masuk akal untuk memecahkan masalah tapi API Level 25 dan di atas solusi tidak bekerja.

Cukup tambahkan 2 baris ini di ChildView Anda TextView, Buttondan Sebagainya (Tidak ViewGroupsuka Linear, Relativedan Sebagainya )

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

Saya baru saja menghadapi masalah ini pada API level 25. Saya harap orang lain tidak membuang waktu dalam hal ini.

Untuk Pengguliran yang Halus Pada RecycleView tambahkan baris ini

 android:nestedScrollingEnabled="false"

tetapi menambahkan attiributes ini hanya berfungsi dengan API level 21 atau lebih tinggi. Jika Anda ingin agar pengguliran bekerja di bawah API level 25 maka tambahkan baris ini di kelas Anda

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

Dalam kode Java, setelah menginisialisasi recyclerView Anda dan mengatur adaptor, tambahkan baris ini:

recyclerView.setNestedScrollingEnabled(false)

Anda juga dapat mencoba untuk membungkus tata letak dengan relatifLayout sehingga pandangan tetap di posisi yang sama tetapi recyclerView (yang gulir) pertama dalam hierarki xml. Saran terakhir upaya putus asa: p

johnny_crq
sumber
0

Karena saya terlambat merespons, tetapi dapat membantu orang lain. Cukup gunakan versi di bawah atau lebih tinggi di build.gradle tingkat aplikasi Anda dan masalah ini dihapus.

compile com.android.support:recyclerview-v7:23.2.1
Amit
sumber
0

untuk menggulir ke atas, panggil saja ini di setcontentview:

scrollView.SmoothScrollTo(0, 0);
pengguna3844824
sumber
ini tidak memberikan jawaban atas pertanyaan
Umar Ata
0

Cukup tambahkan android:descendantFocusability="blocksDescendants"pada ViewGroup di dalam NestedScrollView.

Gilbert Parreno
sumber