NestedScrollView内にRecyclerViewを追加すると、奇妙なスクロール動作が発生します。
何が起こるかというと、アクティビティが開始されるとすぐに、スクロールビューに画面に表示できるよりも多くの行がある場合、NestedScrollViewは上部からのオフセットで始まります(画像1)。一度にすべてを表示できるようにスクロールビューにアイテムがほとんどない場合、これは起こりません(画像2)。
サポートライブラリのバージョン23.2.0を使用しています。
画像1:間違っている-上からのオフセットで始まる
画像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"/>
次のように設定することでこのような問題を解決しました:
<ImageView ...
Android:focusableInTouchMode="true"/>
recyclerView(不要なスクロールの後に隠されていた)の上に私のビューに。このプロパティをRecyclerViewの上のLinearLayoutに設定するか、RecyclerViewのコンテナであるLinearLayoutに設定してみてください(別の場合に役立ちました)。
NestedScrollViewソースで見たように、onRequestFocusInDescendantsで最初の可能な子にフォーカスしようとし、RecyclerViewのみがフォーカス可能であれば勝ちます。
編集(Warhanに感謝):スムーズなスクロールのためにyourRecyclerView.setNestedScrollingEnabled(false);
を設定することを忘れないでください
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
の使用は here で与えられます。そして、focusableInTouchMode
over here 以上。したがって、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()
メソッドが使用されます。しかし、これによれば、 post 、特定の条件の場合を除いて、Androidの通常の動作との一貫性が損なわれるため、focusableInTouchMode
は避ける必要があります。ゲームは、フォーカス可能タッチモードプロパティをうまく利用できるアプリケーションの良い例です。 MapViewをGoogleマップのようにフルスクリーンで使用する場合、タッチモードでフォーカス可能を正しく使用できる別の良い例です。
私は同じ問題を抱えていたため、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;
}
}
Android:descendantFocusability="blocksDescendants"
linearLayout内で私のために働いた。
私の場合、このコードは私の問題を解決します
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つの推測があります。
最初: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>
私の最後の可能な解決策。私は誓います :)
この問題は、リサイクルビューフォーカスが原因で発生します。
ビューのサイズが画面のサイズを拡張すると、自動的にすべてのフォーカスがビューをリサイクルするようになります。
TextView
、Button
などの最初のChildViewにAndroid:focusableInTouchMode="true"
を追加する(ViewGroup
、Linear
などのRelative
ではない)は、問題を解決する意味がありますが、APIレベル25以上のソリューションは機能しません。
TextView
、Button
などのようにChildViewにこれらの2行を追加するだけです(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);
Javaコードで、recyclerViewを初期化し、アダプターを設定した後、次の行を追加します。
recyclerView.setNestedScrollingEnabled(false)
ビューは同じ位置に留まるが、recyclerView(スクロールする)はxml階層で最初になるように、relativeLayoutでレイアウトをラップすることもできます。最後の提案は必死の試みです:p
私は対応が遅れていますが、他の人を助けることができます。アプリレベルのbuild.gradleで以下のバージョン以上を使用するだけで、問題は解決します。
compile com.Android.support:recyclerview-v7:23.2.1
上にスクロールするには、setcontentview
でこれを呼び出すだけです。
scrollView.SmoothScrollTo(0, 0);