NestedScrollView内のリサイクラービューにより、スクロールが途中で開始される


129

NestedScrollView内にRecyclerViewを追加すると、奇妙なスクロール動作が発生します。

何が起こるかというと、スクロールビューに画面に表示できるよりも多くの行があるときはいつでも、アクティビティが起動するとすぐに、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に設定します。
natario 2016年

また、別の同じ方法でスクロール可能なビュー内にスクロール可能なビューを維持することは、あまり良いパターンではありません...適切な LayoutManager
snachmsm

あなたの提案の両方を試してみましたが、残念ながらどちらもうまくいきませんでした。@snachmsmリサイクラビューのアイテム数に関係なく、オフセットは常に同じです。NestedScrollView内RecyclerViewを置くと良いパターンであるかどうかについては、これは実際にGoogleのエンジニアが推奨されているplus.google.com/u/0/+AndroidDevelopers/posts/9kZ3SsXdT2T
Luccasコレア

1
NestedScrollView内でRecyclerViewを使用すると、同じ問題が発生します。リサイクラーパターン自体が機能しないため、これは悪いパターンです。すべてのビューが一度に描画されます(WRAP_CONTENTにはリサイクラービューの高さが必要なため)。バックグラウンドでビューのリサイクルは行われないため、リサイクラービュー自体の主な目的は機能していません。しかし、データを管理し、リサイクラービューを使用してレイアウトを描画するのは簡単です。それが、このパターンを使用できる唯一の理由です。したがって、確実に必要になるまでは、使用しない方がよいでしょう。
Ashok Varma

回答:


253

私は設定することでそのような問題を解決しました:

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

RecyclerView(不要なスクロールの後に非表示にされた)の上の私のビューに。このプロパティをRecyclerViewの上のLinearLayoutまたはRecyclerViewのコンテナーであるLinearLayoutに設定してみてください(別のケースで私を助けました)。

NestedScrollViewソースで見たように、onRequestFocusInDescendantsで可能な最初の子をフォーカスしようとし、RecyclerViewのみがフォーカス可能である場合、それが優先されます。

編集(おかげでWaran):スムーズなスクロールのために設定することを忘れないでください yourRecyclerView.setNestedScrollingEnabled(false);


12
ちなみに、yourRecyclerView.setNestedScrollingEnabled(false);を追加する必要があります。スクロールをスムーズにするため
Waran-

1
@Kenjiは、RecyclerViewが配置されているLinearLayout内の最初のビューにのみ表示されます。または、最初の変更が役に立たない場合は、そのLinearLayout(RecyclerViewコンテナー)自体に。
Dmitry Gavrilko 2016

1
@DmitryGavrilko RecycleViewがNestedScrollview内にあるという問題があります。recycleview.scrollToPosition(X);を使用できません。、それだけでは機能しません。私は過去6日間ですべてを試しましたが、それを乗り越えることができます。なにか提案を?私はとても感謝しています!
Karoly

3
android:focusableInTouchMode="false"他のすべてのビューにtrueを設定する必要がないように、recyclerViewに設定することもできます。
Aksiom 2017年

1
android:focusableInTouchMode = "true"をrecyclerviewを含むlinearlayoutに設定した後で作業したDmitry Gavrilkoに同意します。@Aksiomがandroid:focusableInTouchMode = "false"をrecyclerViewに設定しても機能しません。
Shendre Kiran

103

あなたのLinearLayout直後にNestedScrollViewandroid:descendantFocusability次のように使用します

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

編集

彼らの多くがこの答えを役に立つようにするので、説明も提供します。

ここでの使用法をdescendantFocusability示します。そして、のようにfocusableInTouchModeオーバーこちら。そのため、blocksDescendantsin を使用すると、descendantFocusability子がタッチしているときにフォーカスを取得することができなくなるため、予期しない動作を停止できます。

についてはfocusInTouchMode、との両方がデフォルトでコンストラクターでメソッドAbsListViewRecyclerView呼び出す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マップのようにフルスクリーンで使用される場合、タッチモードでフォーカス可能を正しく使用できるもう1つの良い例です。


ありがとうございました!私の場合はうまくいきます。残念ながら、android:focusableInTouchMode = "true"は私には機能しませんでした。
ミハイル

1
これでうまくいきました。このコード行をrecyclerviewの親ビュー(私の場合は、LinearLayout)に追加しました。
amzer

私はNestedScrollViewに続いて、RecyclerViewとEditTextを含むLinearLayoutを使用していました。LinearLayout内でandroid:descendantFocusability = "blocksDescendants"を使用してスクロール動作を修正しましたが、EditTextがフォーカスを取得できません。何が起こっているのでしょうか?
Sagar Chapagain 2017年

@SagarChapagain blocksDescendantsすべての子孫のフォーカスをブロックするのはプロパティです。よくbeforeDescendants
わかり

2
@JimitPatel私の問題は使用して解決しましたrecyclerView.setFocusable(false); nestedScrollView.requestFocus();
Sagar Chapagain

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

それは機能しますが、フォーカスの概念を壊し、画面上の2つのフォーカスされたフィールドで状況を簡単に取得できます。したがって、RecyclerViewホルダー内にEditTextがない場合にのみ使用してください。
Andrey Chernoprudov

4

私の場合、このコードは私の問題を解決します

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

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

//populate recyclerview here

私のレイアウトには、子LinearLayoutを持つNestedScrollViewとして親レイアウトが含まれています。LinearLayoutには方向「垂直」があり、子RecyclerViewとEditTextがあります。参照


2

2つの推測があります。

まず、この行をNestedScrollViewに配置してみてください

app:layout_behavior="@string/appbar_scrolling_view_behavior"

2番目:使用

<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を親ビューとして追加しても機能しませんでした...とにかくありがとう
Luccas Correa

2

この問題は、ビューフォーカスのリサイクルが原因で発生します。

そのサイズが画面のサイズを拡張した場合、自動的にすべてのフォーカスがリサイクルビューに移動しました。

追加android:focusableInTouchMode="true"のように最初のChildViewにTextViewButtonというように(未上ViewGroupのようにLinearRelativeなど)メイク感覚の問題を解決するのではなく、APIレベル25と上記溶液は仕事をしません。

のようにTextView、これらの2行をChildViewに追加します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を使用してレイアウトをラップして、ビューが同じ位置に留まるようにしますが、recyclerView(スクロール)はxml階層の最初にあります。最後の提案は必死の試みです:p


0

返答が遅いので、他の人を助けることができるかもしれません。アプリレベルのbuild.gradleで以下のバージョン以上を使用するだけで、問題は解消されます。

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

0

一番上までスクロールするには、これをsetcontentview次のように呼び出します:

scrollView.SmoothScrollTo(0, 0);

これは質問に対する答えを提供しません
Umar Ata '28

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.