android - hacer - RecyclerView y SwipeRefreshLayout
swiperefreshlayout android (10)
desafortunadamente, este es un error conocido en LinearLayoutManager. No computaScrollOffset correctamente cuando el primer elemento está visible. será arreglado cuando sea lanzado.
Estoy usando el nuevo RecyclerView-Layout
en SwipeRefreshLayout
y experimenté un comportamiento extraño. Cuando se desplaza la lista hacia atrás, a veces la vista en la parte superior se recorta.
Si trato de desplazarme a la parte superior ahora, los activadores Pull-To-Refresh se activan.
Si intento y elimino el diseño de actualización deslizante alrededor de la vista recicladora, el problema desapareció. Y es reproducible en cualquier teléfono (no solo dispositivos 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>
Ese es mi diseño: las filas están compiladas dinámicamente por RecyclerViewAdapter (2 tipos de vista en esta lista).
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;
}
Aquí está el adaptador.
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));
Y el código del Fragment
contiene el Recycler
y el SwipeRefreshLayout
.
Si alguien más ha experimentado este comportamiento y lo resolvió o al menos encontró la razón para ello?
Antes de usar esta solución: RecyclerView aún no está completo, ¡INTENTE NO UTILIZARLO EN LA PRODUCCIÓN A MENOS QUE USTED SEA COMO YO!
En noviembre de 2014, todavía hay errores en RecyclerView que podrían hacer que canScrollVertically
devuelva falso prematuramente. Esta solución resolverá todos los problemas de desplazamiento.
La gota en la solución:
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);
}
}
¡Ni siquiera necesita reemplazar RecyclerView
en su código con FixedRecyclerView
, reemplazar la etiqueta XML sería suficiente! (El asegura que cuando se completa RecyclerView, la transición sería rápida y simple)
Explicación:
Básicamente, canScrollVertically(boolean)
devuelve falso demasiado pronto, por lo que verificamos si RecyclerView
se desplaza hasta la parte superior de la primera vista (donde la parte superior del primer hijo sería 0) y luego regresa.
EDITAR: Y si no desea extender RecyclerView
por alguna razón, puede extender SwipeRefreshLayout
y anular el método canChildScrollUp()
y poner allí la lógica de verificación.
EDIT2: RecyclerView ha sido lanzado y hasta ahora no hay necesidad de usar esta solución.
He experimentado el mismo problema. Lo resolví agregando un detector de desplazamiento que esperará hasta que se dibuje el primer elemento visible en el RecyclerView
. También puede enlazar otros oyentes de desplazamiento, a lo largo de este. El primer valor visible esperado se agrega para usarlo como posición de umbral cuando SwipeRefreshLayout
debe habilitar en los casos en que se usan titulares de vista de encabezado.
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);
}
}
}
Uso:
SwipeRefreshLayoutToggleScrollListener listener = new SwipeRefreshLayoutToggleScrollListener(mSwiperRefreshLayout);
listener.addScrollListener(this); //optional
listener.addScrollListener(mScrollListener1); //optional
mRecyclerView.setOnScrollLIstener(listener);
Me encontré con el mismo problema recientemente. Probé el enfoque sugerido por @Krunal_Patel, pero funcionó la mayoría de las veces en mi Nexus 4 y no funcionó en absoluto en samsung galaxy s2. Al depurar, recyclerView.getChildAt (0) .getTop () no siempre es correcto para RecyclerView. Entonces, después de pasar por varios métodos, calculé que podemos hacer uso del método findFirstCompletelyComisiblyItemPosition () del LayoutManager para predecir si el primer elemento de RecyclerView es visible o no, para habilitar SwipeRefreshLayout. Encontrar el siguiente código. Espero que ayude a alguien que intenta solucionar el mismo problema. Aclamaciones.
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);
}
});
Me encuentro con el mismo problema. Mi solución está anulando el método OnScrollListener
de OnScrollListener
.
La solución está aquí:
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);
}
}
});
Esta es una forma de manejar esto, que también maneja 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);
}
}
escriba el siguiente código en addOnScrollListener
of the RecyclerView
Me gusta esto:
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);
}
});
La solución de krunal es buena, pero funciona como una revisión y no cubre algunos casos específicos, por ejemplo este:
Digamos que RecyclerView contiene un EditText en el medio de la pantalla. Comenzamos la aplicación (topRowVerticalPosition = 0), toques en EditText. Como resultado, aparece el teclado del software, se reduce el tamaño de RecyclerView, se desplaza automáticamente por el sistema para mantener el EditText visible y topRowVerticalPosition no debe ser 0, pero no se llama aScrolled y no se vuelve a calcular la posición de TopRowVertical.
Por lo tanto, sugiero esta solución:
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);
}
}
Después de especificar RecyclerView interno para SupportSwipeRefreshLayout, enviará automáticamente el evento táctil a SupportSwipeRefreshLayout si RecyclerView no se puede desplazar hacia arriba y RecyclerView de lo contrario.
Así es como he resuelto este problema en mi caso. Podría ser útil para otra persona que termine aquí buscando soluciones similares a esta.
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);
}
}
});
Espero que esto definitivamente ayude a alguien que está buscando una solución similar.
Código fuente https://drive.google.com/open?id=0BzBKpZ4nzNzURkRGNVFtZXV1RWM
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);
}
});