Intereting Posts
Возможно ли иметь анимированный чертеж? Android: EditText в Dialog не подтягивает мягкую клавиатуру Приложение Android открывается из ссылок, отправленных по электронной почте Когда / почему мой экземпляр singleton Java уничтожается? Изменение текста из другого вида деятельности Как отклонить Snackbar, используя собственную кнопку действия? Если я использую новую «библиотеку V7 Appcompat», мне по-прежнему нужна «Библиотека поддержки V4» для минимального SDK = 7? GSON: Ожидалась строка, но BEGIN_OBJECT? Веселая активность активности Android при отключении экрана? ActionBar Совместимость не отображает логотип на Android 2.3.3 Как получить почтовые сообщения Gmail программно в android Generic OR вместо AND <T extends Number | CharSequence> Определите, работает ли сенсор, когда экран выключен в android Изображение пользовательской камеры Android не появляется в галерее, пока я не перезагружу свой телефон Активность Android внутри диалога

RecyclerView и SwipeRefreshLayout

Я использую новый RecyclerView-Layout в SwipeRefreshLayout и испытываю странное поведение. При прокрутке списка вверх до вершины иногда открывается вид сверху.

Список прокручивается вверх

Если я попытаюсь прокрутить вверх, теперь – триггеры Pull-To-Refresh.

Отрезанный ряд

Если я попытаюсь удалить Swipe-Refresh-Layout вокруг Recycler-View, проблема исчезнет. И его воспроизводимый на любом телефоне (не только устройства 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 содержащий Recycler и SwipeRefreshLayout .

Если кто-то еще испытал это поведение и решил его или, по крайней мере, нашел причину этого?

Solutions Collecting From Web of "RecyclerView и SwipeRefreshLayout"

Прежде чем использовать это решение: RecyclerView еще не завершен, НЕ ИСПОЛЬЗУЙТЕ ЭТО В ПРОИЗВОДСТВЕ, ЕСЛИ ВЫ НРАВИТСЯ!

Что касается ноября 2014 года, в 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), а затем возвращается.

EDIT: И если вы не хотите распространять RecyclerView по какой-либо причине, вы можете расширить SwipeRefreshLayout и переопределить метод canChildScrollUp() и поместить туда проверочную логику.

EDIT2: RecyclerView был выпущен, и до сих пор нет необходимости использовать это исправление.

Напишите следующий код в addOnScrollListener из RecyclerView

Как это:

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

Недавно я столкнулся с той же проблемой. Я попробовал подход, предложенный @Krunal_Patel, но он работал большую часть времени в моем Nexus 4 и вообще не работал в галактике samsung s2. При отладке recyclerView.getChildAt (0) .getTop () всегда не подходит для RecyclerView. Итак, пройдя различные методы, я понял, что мы можем использовать метод findFirstCompletelyVisibleItemPosition () LayoutManager, чтобы предсказать, является ли первый элемент RecyclerView видимым или нет, чтобы включить SwipeRefreshLayout.Найдите код ниже. Надеюсь, это поможет кому-то, кто пытается решить ту же проблему. Приветствия.

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

Вот как я решил эту проблему в моем случае. Это может быть полезно для кого-то еще, кто попадает сюда для поиска решений, подобных этому.

 recyclerView.addOnScrollListener(new OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { // TODO Auto-generated method stub super.onScrolled(recyclerView, dx, dy); } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { // TODO Auto-generated method stub //super.onScrollStateChanged(recyclerView, newState); int firstPos=linearLayoutManager.findFirstCompletelyVisibleItemPosition(); if (firstPos>0) { swipeLayout.setEnabled(false); } else { swipeLayout.setEnabled(true); } } }); 

Надеюсь, это может помочь кому-то, кто ищет подобное решение.

К сожалению, это известная ошибка в LinearLayoutManager. Он не правильно вычисляетScrollOffset, когда первый элемент виден. Будет исправлено, когда оно будет выпущено.

Я столкнулся с такой же проблемой. Я решил это, добавив прослушиватель прокрутки, который будет ждать, пока в 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); 

Я сталкиваюсь с той же проблемой. Мое решение переопределяет 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); } } }); 

Вот один из способов справиться с этим, который также обрабатывает ListView / GridView.

 public class SwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout { public SwipeRefreshLayout(Context context) { super(context); } public SwipeRefreshLayout(Context context,AttributeSet attrs) { super(context,attrs); } @Override public boolean canChildScrollUp() { View target=getChildAt(0); if(target instanceof AbsListView) { final AbsListView absListView=(AbsListView)target; return absListView.getChildCount()>0 &&(absListView.getFirstVisiblePosition()>0||absListView.getChildAt(0) .getTop()<absListView.getPaddingTop()); } else return ViewCompat.canScrollVertically(target,-1); } } 

Решение 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, он автоматически отправит событие касания к SupportSwipeRefreshLayout, если RecyclerView не сможет прокручиваться и в RecyclerView иначе.