android - masivos - ¿Cómo imitar la lista de elementos adhesivos como en la aplicación de contactos de Lollipop?
cómo se crea un grupo de contactos en gmail (3)
Bueno, el código fuente de la aplicación es siempre un buen lugar para comenzar.
Fondo
Google ha publicado recientemente una nueva versión de Android, que tiene una aplicación de contacto que se ve así:
El problema
Necesito imitar este tipo de lista, pero no puedo descubrir cómo hacerlo bien.
Este consta de 3 componentes principales:
los títulos adhesivos, que permanecen mientras el contacto superior tenga el nombre que comienza con esta letra.
Las fotos circulares o círculo + letra (para contactos que no tienen fotos)
el PagerTitleStrip, que tiene un espacio uniforme entre los elementos, e intenta mostrarlos todos en la pantalla (o permitir que se desplace si es necesario).
Lo que he encontrado
Hay un montón de bibliotecas de terceros, pero que manejan los encabezados adhesivos, que son parte de los elementos de la propia vista de lista. No tienen el título a la izquierda, sino en la parte superior de los elementos.
Aquí, el lado izquierdo se mueve de una manera diferente que el lado derecho. Puede pegarse, y si no hay necesidad de pegarla (porque la sección tiene un elemento, por ejemplo) se desplaza.
He notado que para las fotos circulares, puedo usar la nueva clase (?) Llamada " RoundedBitmapDrawableFactory " (que crea " RoundedBitmapDrawable "). Esto probablemente me permitirá poner una foto en una forma circular muy bien. Sin embargo, no funciona con las letras (utilizadas para los contactos que no tienen una foto), pero creo que puedo poner un TextView en la parte superior y establecer el fondo en un color.
Además, he notado que para usar el pozo "RoundedBitmapDrawable" (para hacerlo realmente circular), debo proporcionarle un mapa de bits de tamaño cuadrado. De lo contrario, tendrá una forma extraña.
He intentado usar " setTextSpacing " para minimizar el espacio entre los elementos, pero no parece funcionar. Tampoco pude encontrar ninguna manera de personalizar / personalizar el PagerTitleStrip para que sea como en la aplicación de contactos.
También he intentado usar " PagerTabStrip ", pero tampoco ayudó.
La pregunta
¿Cómo puedes imitar la forma en que Google ha implementado esta pantalla?
Más específicamente:
¿Cómo puedo hacer que el lado izquierdo se comporte como en la aplicación de contactos?
¿Es esta la mejor manera de implementar la foto circular? ¿Realmente tengo que recortar el mapa de bits en un cuadrado antes de usar el dibujo especial? ¿Hay alguna guía de diseño para qué colores usar? ¿Hay alguna forma más oficial de usar la celda de texto circular?
¿Cómo diseñas el PagerTitleStrip para que tenga la misma apariencia que en la aplicación de contactos?
Proyecto github
EDITAR: para los números 1 y 2, he hecho un proyecto en Github, here . Lamentablemente, también he encontrado un error importante, así que también lo he publicado here .
Puedes hacer lo siguiente:
- En el lado izquierdo de su RecyclerView, crea un TextView que contendrá el índice de letras;
- En la parte superior de la vista del reciclador (en el diseño que la envuelve) coloca un TextView para cubrir el que creaste en el paso 1, este será el pegajoso;
- Agregue un OnScrollListener en su RecyclerView. En el método onScrolled (), configure TextView creado en el paso 2 para el texto de referencia tomado de firstVisibleRow. Hasta aquí tendrás un índice de stiky, sin los efectos de la transición;
Para agregar el efecto de transición de entrada / salida, desarrolle una lógica que verifique si el elemento anterior del currentFirstVisibleItem es el último de la lista de letras anterior, o si secondVisibleItem es el primero de la nueva letra. Sobre la base de esta información, haga que el índice fijo sea visible / invisible y el índice de fila opuesto, agregando en este último el efecto alfa.
if (recyclerView != null) { View firstVisibleView = recyclerView.getChildAt(0); View secondVisibleView = recyclerView.getChildAt(1); TextView firstRowIndex = (TextView) firstVisibleView.findViewById(R.id.sticky_row_index); TextView secondRowIndex = (TextView) secondVisibleView.findViewById(R.id.sticky_row_index); int visibleRange = recyclerView.getChildCount(); int actual = recyclerView.getChildPosition(firstVisibleView); int next = actual + 1; int previous = actual - 1; int last = actual + visibleRange; // RESET STICKY LETTER INDEX stickyIndex.setText(String.valueOf(getIndexContext(firstRowIndex)).toUpperCase()); stickyIndex.setVisibility(TextView.VISIBLE); if (dy > 0) { // USER SCROLLING DOWN THE RecyclerView if (next <= last) { if (isHeader(firstRowIndex, secondRowIndex)) { stickyIndex.setVisibility(TextView.INVISIBLE); firstRowIndex.setVisibility(TextView.VISIBLE); firstRowIndex.setAlpha(1 - (Math.abs(firstVisibleView.getY()) / firstRowIndex.getHeight())); secondRowIndex.setVisibility(TextView.VISIBLE); } else { firstRowIndex.setVisibility(TextView.INVISIBLE); stickyIndex.setVisibility(TextView.VISIBLE); } } } else { // USER IS SCROLLING UP THE RecyclerVIew if (next <= last) { // RESET FIRST ROW STATE firstRowIndex.setVisibility(TextView.INVISIBLE); if ((isHeader(firstRowIndex, secondRowIndex) || (getIndexContext(firstRowIndex) != getIndexContext(secondRowIndex))) && isHeader(firstRowIndex, secondRowIndex)) { stickyIndex.setVisibility(TextView.INVISIBLE); firstRowIndex.setVisibility(TextView.VISIBLE); firstRowIndex.setAlpha(1 - (Math.abs(firstVisibleView.getY()) / firstRowIndex.getHeight())); secondRowIndex.setVisibility(TextView.VISIBLE); } else { secondRowIndex.setVisibility(TextView.INVISIBLE); } } } if (stickyIndex.getVisibility() == TextView.VISIBLE) { firstRowIndex.setVisibility(TextView.INVISIBLE); } }
He desarrollado un componente que hace la lógica anterior, se puede encontrar aquí: https://github.com/edsilfer/sticky-index
ok, he logrado resolver todos los problemas que he escrito sobre:
1. Cambié la forma en que funciona la biblioteca de terceros (no recuerdo de dónde obtuve la biblioteca, pero esta es muy similar), cambiando el diseño de cada fila, de modo que el encabezado esté a la izquierda del propio contenido. Es sólo una cuestión de un archivo XML de diseño y ya está bastante hecho. Tal vez publique una buena biblioteca para ambas soluciones.
2.Esta es la vista que he hecho. No es una implementación oficial (no encontré ninguna), así que hice algo por mi cuenta. Puede ser más eficiente, pero al menos es bastante fácil de entender y también bastante flexible:
public class CircularView extends ViewSwitcher {
private ImageView mImageView;
private TextView mTextView;
private Bitmap mBitmap;
private CharSequence mText;
private int mBackgroundColor = 0;
private int mImageResId = 0;
public CircularView(final Context context) {
this(context, null);
}
public CircularView(final Context context, final AttributeSet attrs) {
super(context, attrs);
addView(mImageView = new ImageView(context), new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT, Gravity.CENTER));
addView(mTextView = new TextView(context), new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT, Gravity.CENTER));
mTextView.setGravity(Gravity.CENTER);
if (isInEditMode())
setTextAndBackgroundColor("", 0xFFff0000);
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int measuredWidth = getMeasuredWidth();
final int measuredHeight = getMeasuredHeight();
if (measuredWidth != 0 && measuredHeight != 0)
drawContent(measuredWidth, measuredHeight);
}
@SuppressWarnings("deprecation")
private void drawContent(final int measuredWidth, final int measuredHeight) {
ShapeDrawable roundedBackgroundDrawable = null;
if (mBackgroundColor != 0) {
roundedBackgroundDrawable = new ShapeDrawable(new OvalShape());
roundedBackgroundDrawable.getPaint().setColor(mBackgroundColor);
roundedBackgroundDrawable.setIntrinsicHeight(measuredHeight);
roundedBackgroundDrawable.setIntrinsicWidth(measuredWidth);
roundedBackgroundDrawable.setBounds(new Rect(0, 0, measuredWidth, measuredHeight));
}
if (mImageResId != 0) {
mImageView.setBackgroundDrawable(roundedBackgroundDrawable);
mImageView.setImageResource(mImageResId);
mImageView.setScaleType(ScaleType.CENTER_INSIDE);
} else if (mText != null) {
mTextView.setText(mText);
mTextView.setBackgroundDrawable(roundedBackgroundDrawable);
// mTextView.setPadding(0, measuredHeight / 4, 0, measuredHeight / 4);
mTextView.setTextSize(measuredHeight / 5);
} else if (mBitmap != null) {
mImageView.setScaleType(ScaleType.FIT_CENTER);
mImageView.setBackgroundDrawable(roundedBackgroundDrawable);
mBitmap = ThumbnailUtils.extractThumbnail(mBitmap, measuredWidth, measuredHeight);
final RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(),
mBitmap);
roundedBitmapDrawable.setCornerRadius((measuredHeight + measuredWidth) / 4);
mImageView.setImageDrawable(roundedBitmapDrawable);
}
resetValuesState(false);
}
public void setTextAndBackgroundColor(final CharSequence text, final int backgroundColor) {
resetValuesState(true);
while (getCurrentView() != mTextView)
showNext();
this.mBackgroundColor = backgroundColor;
mText = text;
final int height = getHeight(), width = getWidth();
if (height != 0 && width != 0)
drawContent(width, height);
}
public void setImageResource(final int imageResId, final int backgroundColor) {
resetValuesState(true);
while (getCurrentView() != mImageView)
showNext();
mImageResId = imageResId;
this.mBackgroundColor = backgroundColor;
final int height = getHeight(), width = getWidth();
if (height != 0 && width != 0)
drawContent(width, height);
}
public void setImageBitmap(final Bitmap bitmap) {
setImageBitmapAndBackgroundColor(bitmap, 0);
}
public void setImageBitmapAndBackgroundColor(final Bitmap bitmap, final int backgroundColor) {
resetValuesState(true);
while (getCurrentView() != mImageView)
showNext();
this.mBackgroundColor = backgroundColor;
mBitmap = bitmap;
final int height = getHeight(), width = getWidth();
if (height != 0 && width != 0)
drawContent(width, height);
}
private void resetValuesState(final boolean alsoResetViews) {
mBackgroundColor = mImageResId = 0;
mBitmap = null;
mText = null;
if (alsoResetViews) {
mTextView.setText(null);
mTextView.setBackgroundDrawable(null);
mImageView.setImageBitmap(null);
mImageView.setBackgroundDrawable(null);
}
}
public ImageView getImageView() {
return mImageView;
}
public TextView getTextView() {
return mTextView;
}
}
3. He encontrado una buena biblioteca que lo hace, llamada PagerSlidingTabStrip . Sin embargo, no encontré una manera oficial de estilizar a la nativa.
Otra forma es mirar la muestra de Google, que está disponible directamente en Android-Studio, y se llama "SlidingTabLayout". Muestra cómo se hace.
EDITAR: aquí está también una mejor biblioteca para el # 3, llamada "PagerSlidingTabStrip".