63

私は新しいものを使用していRecyclerView-LayoutますSwipeRefreshLayoutが、奇妙な動作を経験しました。リストをスクロールして一番上に戻すと、一番上のビューが切り取られることがあります。

一番上までスクロールしたリスト

今すぐ一番上までスクロールしようとすると、Pull-To-Refresh がトリガーされます。

刈り取られた

Recycler-View の周りの Swipe-Refresh-Layout を削除しようとすると、問題はなくなります。L-Preview デバイスだけでなく、どの電話でも再現可能です。

 <android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/contentView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="gone">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/hot_fragment_recycler"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.v4.widget.SwipeRefreshLayout>

それが私のレイアウトです - 行は RecyclerViewAdapter (このリストの 2 つのビュータイプ) によって動的に構築されます。

public class HotRecyclerAdapter extends TikDaggerRecyclerAdapter<GameRow> {

private static final int VIEWTYPE_GAME_TITLE = 0;
private static final int VIEWTYPE_GAME_TEAM = 1;

@Inject
Picasso picasso;

public HotRecyclerAdapter(Injector injector) {
    super(injector);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position, int viewType) {
    switch (viewType) {
        case VIEWTYPE_GAME_TITLE: {
            TitleGameRowViewHolder holder = (TitleGameRowViewHolder) viewHolder;
            holder.bindGameRow(picasso, getItem(position));
            break;
        }
        case VIEWTYPE_GAME_TEAM: {
            TeamGameRowViewHolder holder = (TeamGameRowViewHolder) viewHolder;
            holder.bindGameRow(picasso, getItem(position));
            break;
        }
    }
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    switch (viewType) {
        case VIEWTYPE_GAME_TITLE: {
            View view = inflater.inflate(R.layout.game_row_title, viewGroup, false);
            return new TitleGameRowViewHolder(view);
        }
        case VIEWTYPE_GAME_TEAM: {
            View view = inflater.inflate(R.layout.game_row_team, viewGroup, false);
            return new TeamGameRowViewHolder(view);
        }
    }
    return null;
}

@Override
public int getItemViewType(int position) {
    GameRow row = getItem(position);
    if (row.isTeamGameRow()) {
        return VIEWTYPE_GAME_TEAM;
    }
    return VIEWTYPE_GAME_TITLE;
}

こちらがアダプターです。

   hotAdapter = new HotRecyclerAdapter(this);

    recyclerView.setHasFixedSize(false);
    recyclerView.setAdapter(hotAdapter);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

    contentView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            loadData();
        }
    });

    TypedArray colorSheme = getResources().obtainTypedArray(R.array.main_refresh_sheme);
    contentView.setColorSchemeResources(colorSheme.getResourceId(0, -1), colorSheme.getResourceId(1, -1), colorSheme.getResourceId(2, -1), colorSheme.getResourceId(3, -1));

そして、とFragmentを含むのコード。RecyclerSwipeRefreshLayout

他の誰かがこの動作を経験して解決した場合、または少なくともその理由を見つけた場合は?

4

13 に答える 13

75

の に次のコードを記述しますaddOnScrollListenerRecyclerView

このような:

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            int topRowVerticalPosition =
                    (recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
            swipeRefreshLayout.setEnabled(topRowVerticalPosition >= 0);

        }

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }
    });
于 2014-08-07T13:21:24.397 に答える
53

このソリューションを使用する前に: RecyclerView はまだ完全ではありません。あなたが私のようでない限り、本番環境では使用しないでください!

2014 年 11 月の時点で、RecyclerView にはまだcanScrollVertically早期に false を返すバグがあります。このソリューションは、すべてのスクロールの問題を解決します。

ソリューションのドロップ:

public class FixedRecyclerView extends RecyclerView {
    public FixedRecyclerView(Context context) {
        super(context);
    }

    public FixedRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FixedRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean canScrollVertically(int direction) {
        // check if scrolling up
        if (direction < 1) {
            boolean original = super.canScrollVertically(direction);
            return !original && getChildAt(0) != null && getChildAt(0).getTop() < 0 || original;
        }
        return super.canScrollVertically(direction);

    }
}

RecyclerViewコード内でを に置き換える必要さえありませんFixedRecyclerView。XML タグを置き換えるだけで十分です。(RecyclerView が完了すると、移行が迅速かつ簡単になります)

説明:

基本的に、canScrollVertically(boolean)false を返すのが早すぎるためRecyclerView、最初のビューの上部 (最初の子の上部が 0 になる場所) までスクロールされたかどうかを確認してから戻ります。

RecyclerView編集:また、何らかの理由で拡張したくない場合はSwipeRefreshLayout、メソッドを拡張してオーバーライドしcanChildScrollUp()、チェックロジックをそこに入れることができます。

EDIT2: RecyclerView がリリースされました。これまでのところ、この修正を使用する必要はありません。

于 2014-08-10T10:44:50.290 に答える
13

最近同じ問題に遭遇しました。@Krunal_Patelが提案したアプローチを試しましたが、Nexus 4ではほとんどの場合機能し、samsung galaxy s2ではまったく機能しませんでした. デバッグ中、recyclerView.getChildAt(0).getTop() は常に RecyclerView に対して正しくありません。そこで、さまざまな方法を試した結果、LayoutManager の findFirstCompletelyVisibleItemPosition() メソッドを使用して、RecyclerView の最初の項目が表示されるかどうかを予測し、SwipeRefreshLayout.Find を有効にできることがわかりました。同じ問題を解決しようとしている人に役立つことを願っています。乾杯。

    recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        }

        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            swipeRefresh.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0);
        }
    });
于 2014-10-02T11:53:01.130 に答える
0

krunal のソリューションは優れていますが、修正プログラムのように機能し、次のような特定のケースには対応していません。

RecyclerView の画面中央に EditText が含まれているとします。アプリケーションを開始し (topRowVerticalPosition = 0)、EditText をタップします。その結果、ソフトウェア キーボードが表示され、RecyclerView のサイズが縮小され、システムによって自動的にスクロールされて EditText が表示されたままになり、topRowVerticalPosition が 0 になることはありませんが、onScrolled は呼び出されず、topRowVerticalPosition は再計算されません。

したがって、私はこの解決策を提案します:

public class SupportSwipeRefreshLayout extends SwipeRefreshLayout {
    private RecyclerView mInternalRecyclerView = null;

    public SupportSwipeRefreshLayout(Context context) {
        super(context);
    }

    public SupportSwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setInternalRecyclerView(RecyclerView internalRecyclerView) {
        mInternalRecyclerView = internalRecyclerView;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mInternalRecyclerView.canScrollVertically(-1)) {
            return false;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

内部 RecyclerView を SupportSwipeRefreshLayout に指定すると、RecyclerView を上にスクロールできない場合はタッチ イベントが自動的に SupportSwipeRefreshLayout に送信され、それ以外の場合は RecyclerView に送信されます。

于 2016-03-07T15:11:27.897 に答える
0

私は同じ問題に遭遇します。onScrolled私の解決策は、のメソッドをオーバーライドすることですOnScrollListener

回避策は次のとおりです。

    recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

    }
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        int offset = dy - ydy;//to adjust scrolling sensitivity of calling OnRefreshListener
        ydy = dy;//updated old value
        boolean shouldRefresh = (linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0)
                    && (recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) && offset > 30;
        if (shouldRefresh) {
            swipeRefreshLayout.setRefreshing(true);
        } else {
            swipeRefreshLayout.setRefreshing(false);
        }
    }
});
于 2015-06-10T07:26:31.263 に答える
0

私は同じ問題を経験しました。予想される最初の可視アイテムがに描画されるまで待機するスクロールリスナーを追加することで解決しましたRecyclerView。これに沿って、他のスクロールリスナーもバインドできます。SwipeRefreshLayoutヘッダー ビュー ホルダーを使用する場合に有効にする必要がある場合、最初に表示されると予想される値が追加され、しきい値の位置として使用されます。

public class SwipeRefreshLayoutToggleScrollListener extends RecyclerView.OnScrollListener {
        private List<RecyclerView.OnScrollListener> mScrollListeners = new ArrayList<RecyclerView.OnScrollListener>();
        private int mExpectedVisiblePosition = 0;

        public SwipeRefreshLayoutToggleScrollListener(SwipeRefreshLayout mSwipeLayout) {
            this.mSwipeLayout = mSwipeLayout;
        }

        private SwipeRefreshLayout mSwipeLayout;
        public void addScrollListener(RecyclerView.OnScrollListener listener){
            mScrollListeners.add(listener);
        }
        public boolean removeScrollListener(RecyclerView.OnScrollListener listener){
            return mScrollListeners.remove(listener);
        }
        public void setExpectedFirstVisiblePosition(int position){
            mExpectedVisiblePosition = position;
        }
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            notifyScrollStateChanged(recyclerView,newState);
            LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
            int firstVisible = llm.findFirstCompletelyVisibleItemPosition();
            if(firstVisible != RecyclerView.NO_POSITION)
                mSwipeLayout.setEnabled(firstVisible == mExpectedVisiblePosition);

        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            notifyOnScrolled(recyclerView, dx, dy);
        }
        private void notifyOnScrolled(RecyclerView recyclerView, int dx, int dy){
            for(RecyclerView.OnScrollListener listener : mScrollListeners){
                listener.onScrolled(recyclerView, dx, dy);
            }
        }
        private void notifyScrollStateChanged(RecyclerView recyclerView, int newState){
            for(RecyclerView.OnScrollListener listener : mScrollListeners){
                listener.onScrollStateChanged(recyclerView, newState);
            }
        }
    }

使用法:

SwipeRefreshLayoutToggleScrollListener listener = new SwipeRefreshLayoutToggleScrollListener(mSwiperRefreshLayout);
listener.addScrollListener(this); //optional
listener.addScrollListener(mScrollListener1); //optional
mRecyclerView.setOnScrollLIstener(listener);
于 2014-11-27T15:14:19.463 に答える
0

単線ソリューション。

setOnScrollListenerは非推奨です。

次のような同じ目的でsetOnScrollChangeListenerを使用できます。

recylerView.setOnScrollChangeListener((view, i, i1, i2, i3) -> swipeToRefreshLayout.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0));
于 2018-04-02T13:39:57.163 に答える