android - reciclaje - como funciona reciclapp
Cómo ocultar el divisor cuando la animación de eliminación ocurre en la vista de reciclador (3)
Creo que el ItemDecorator que usas para dibujar un divisor después de cada fila está arruinando las cosas cuando se realiza el barrido para eliminar.
En lugar de utilizar ItemDecorator para dibujar un divisor en una vista de reciclaje, agregue una vista al final de su diseño de diseño secundario RecyclerView. Que dibujará una línea divisoria como ItemDecorator.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<!-- child layout Design !-->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray"
android:layout_gravity="bottom"
/>
</Linearlayout>
RecyclerView
de forma predeterminada, viene con una animación de eliminación agradable, siempre que setHasStableIds(true)
y proporcione la implementación correcta en getItemId
.
Recientemente, he añadido divisor en RecyclerView
través de https://stackoverflow.com/a/27037230/72437
El resultado se ve como sigue
https://www.youtube.com/watch?v=u-2kPZwF_0w
https://youtu.be/c81OsFAL3zY (Para hacer que los divisores sean más visibles cuando se reproduce la animación eliminada, cambio temporalmente el fondo RecyclerView
a rojo)
Los divisores siguen visibles cuando se reproduce la animación de eliminación.
Sin embargo, si miro el ejemplo de GMail, cuando se reproduce la animación de eliminación, las líneas divisorias ya no son visibles. Están siendo cubiertos un área de color sólido.
https://www.youtube.com/watch?v=cLs7paU-BIg
¿Puedo saber cómo puedo lograr el mismo efecto que GMail, al no mostrar líneas divisorias cuando se reproduce la animación de eliminación?
En primer lugar, lo siento por el tamaño de la respuesta masiva. Sin embargo, sentí que era necesario incluir toda la actividad de prueba para que pueda ver lo que he hecho.
La cuestión
El problema que tiene, es que DividerItemDecoration
no tiene idea del estado de su fila. No sabe si el elemento está siendo eliminado.
Por esta razón, hice un POJO que podemos usar para contener un entero (que usamos como itemId y una representación visual y un booleano que indica que esta fila se está eliminando o no).
Cuando decide eliminar las entradas (en este ejemplo, adapter.notifyItemRangeRemoved(3, 8);
), también debe configurar el Pojo
asociado para que se elimine (en este ejemplo, pojo.beingDeleted = true;
).
La posición del divisor cuando se elimina, se restablece al color de la vista principal. Para cubrir el divisor.
No me gusta mucho usar el conjunto de datos para administrar el estado de su lista principal. Quizás haya una mejor manera.
El resultado visualizado.
La actividad:
public class MainActivity extends AppCompatActivity {
private static final int VERTICAL_ITEM_SPACE = 8;
private List<Pojo> mDataset = new ArrayList<Pojo>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for(int i = 0; i < 30; i++) {
mDataset.add(new Pojo(i));
}
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new VerticalSpaceItemDecoration(VERTICAL_ITEM_SPACE));
recyclerView.addItemDecoration(new DividerItemDecoration(this));
RecyclerView.ItemAnimator ia = recyclerView.getItemAnimator();
ia.setRemoveDuration(4000);
final Adapter adapter = new Adapter(mDataset);
recyclerView.setAdapter(adapter);
(new Handler(Looper.getMainLooper())).postDelayed(new Runnable() {
@Override
public void run() {
int index = 0;
Iterator<Pojo> it = mDataset.iterator();
while(it.hasNext()) {
Pojo pojo = it.next();
if(index >= 3 && index <= 10) {
pojo.beingDeleted = true;
it.remove();
}
index++;
}
adapter.notifyItemRangeRemoved(3, 8);
}
}, 2000);
}
public class Adapter extends RecyclerView.Adapter<Holder> {
private List<Pojo> mDataset;
public Adapter(@NonNull final List<Pojo> dataset) {
setHasStableIds(true);
mDataset = dataset;
}
@Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_cell, parent, false);
return new Holder(view);
}
@Override
public void onBindViewHolder(final Holder holder, final int position) {
final Pojo data = mDataset.get(position);
holder.itemView.setTag(data);
holder.textView.setText("Test "+data.dataItem);
}
@Override
public long getItemId(int position) {
return mDataset.get(position).dataItem;
}
@Override
public int getItemCount() {
return mDataset.size();
}
}
public class Holder extends RecyclerView.ViewHolder {
public TextView textView;
public Holder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.text);
}
}
public class Pojo {
public int dataItem;
public boolean beingDeleted = false;
public Pojo(int dataItem) {
this.dataItem = dataItem;
}
}
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Paint mOverwritePaint;
private Drawable mDivider;
/**
* Default divider will be used
*/
public DividerItemDecoration(Context context) {
final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
mDivider = styledAttributes.getDrawable(0);
styledAttributes.recycle();
initializePaint();
}
/**
* Custom divider will be used
*/
public DividerItemDecoration(Context context, int resId) {
mDivider = ContextCompat.getDrawable(context, resId);
initializePaint();
}
private void initializePaint() {
mOverwritePaint = new Paint();
mOverwritePaint.setColor(ContextCompat.getColor(MainActivity.this, android.R.color.background_light));
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
Pojo item = (Pojo) child.getTag();
if(item.beingDeleted) {
c.drawRect(left, top, right, bottom, mOverwritePaint);
} else {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
}
public class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {
private final int mVerticalSpaceHeight;
public VerticalSpaceItemDecoration(int mVerticalSpaceHeight) {
this.mVerticalSpaceHeight = mVerticalSpaceHeight;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
outRect.bottom = mVerticalSpaceHeight;
}
}
}
El diseño de la actividad
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:background="@android:color/background_light"
tools:context="test.dae.myapplication.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
El diseño de la "fila" RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/text"
android:padding="8dp">
</TextView>
La solución es bastante fácil. Para animar una decoración, puede y debe usar view.getTranslation_()
y view.getAlpha()
. Hace un tiempo escribí una publicación en el blog sobre este tema exacto, puede leerlo here .
Traducción y desvanecimiento
El administrador de diseño predeterminado desvanecerá las vistas (alfa) y las traducirá, cuando se agreguen o eliminen. Tienes que tener esto en cuenta en tu decoración.
La idea es simple:
Sin embargo, usted dibuja su decoración, aplica el mismo alfa y la traducción a su dibujo utilizando
view.getAlpha()
yview.getTranslationY()
.
Siguiendo tu respuesta vinculada, tendría que ser adaptada como la siguiente:
// translate
int top = child.getBottom() + params.bottomMargin + view.getTranslationY();
int bottom = top + mDivider.getIntrinsicHeight();
// apply alpha
mDivider.setAlpha((int) child.getAlpha() * 255f);
mDivider.setBounds(left + view.getTranslationX(), top,
right + view.getTranslationX(), bottom);
mDivider.draw(c);
Una muestra completa
Me gusta dibujar cosas por mí mismo, ya que creo que dibujar una línea es menos costoso que diseñar un dibujable, esto sería lo siguiente:
public class SeparatorDecoration extends RecyclerView.ItemDecoration {
private final Paint mPaint;
private final int mAlpha;
public SeparatorDecoration(@ColorInt int color, float width) {
mPaint = new Paint();
mPaint.setColor(color);
mPaint.setStrokeWidth(width);
mAlpha = mPaint.getAlpha();
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// we retrieve the position in the list
final int position = params.getViewAdapterPosition();
// add space for the separator to the bottom of every view but the last one
if (position < state.getItemCount()) {
outRect.set(0, 0, 0, (int) mPaint.getStrokeWidth()); // left, top, right, bottom
} else {
outRect.setEmpty(); // 0, 0, 0, 0
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// a line will draw half its size to top and bottom,
// hence the offset to place it correctly
final int offset = (int) (mPaint.getStrokeWidth() / 2);
// this will iterate over every visible view
for (int i = 0; i < parent.getChildCount(); i++) {
final View view = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// get the position
final int position = params.getViewAdapterPosition();
// and finally draw the separator
if (position < state.getItemCount()) {
// apply alpha to support animations
mPaint.setAlpha((int) (view.getAlpha() * mAlpha));
float positionY = view.getBottom() + offset + view.getTranslationY();
// do the drawing
c.drawLine(view.getLeft() + view.getTranslationX(),
positionY,
view.getRight() + view.getTranslationX(),
positionY,
mPaint);
}
}
}
}