https://www.jianshu.com/p/60aa2fc17870
Prólogo
La semana pasada mencioné en el artículo "RecyclerView-LayoutManager" que la GridLayoutManager
siguiente página de inicio se puede realizar usando :
Algunos estudiantes expresaron interés en esto, pero no hay un caso listo, así que simplemente implementé uno por mí mismo. El resultado final es el siguiente:
efecto
Creo que muchos estudiantes tienen la misma sensación que yo, pensando que GridLayoutManager
solo se puede lograr un diseño de cuadrícula estándar. No fue hasta que decidí estudiar hace un tiempo RecyclerView
y leer GridLayoutManager
el código fuente que descubrí que puede hacer más cosas, por Por ejemplo, escriba una página de inicio.
Antes de leer este artículo, necesita algunas reservas de conocimientos :
View
Tener una comprensión simple del proceso de dibujo.Canvas
Sencillo y práctico.RecyclerView+GridLayoutManager
uso de.
Tabla de contenido
Tabla de contenido
Uno, la escena
Utilice RecyclerView
+ GridLayoutManager
+ ItemDecoration
escenarios aplicables de la página de inicio personalizada:
- Hay varios módulos funcionales
- Varios estilos de subvistas
- El último módulo debe actualizarse (si existe tal función, debe
RecyclerView
realizarse), por ejemplo, QQ music se desliza hacia abajo para recomendar música que pueda interesar a los usuarios.
Personalmente, creo que la importancia de esta solución es reducir el anidamiento del diseño y facilitar la administración de la interfaz , pero puede que no sea adecuada para situaciones comerciales particularmente complejas.
2. Pensando
Deben resolverse dos dificultades para realizar las funciones anteriores :
- Cómo mostrar diferentes números de subvistas para diferentes filas
- Dibujo del título de cada módulo
Las soluciones a estos dos problemas corresponden GridLayoutManager
y ItemDecoration
, respectivamente , las entendemos una por una.
1. GridLayoutManager
GridLayoutManager
De hecho, ya estamos familiarizados con él, pero normalmente no entendemos SpanSize
este concepto. Veamos el siguiente fragmento de código:
GridLayoutManager gll = new GridLayoutManager(this, 6);
mRecyclerView.setLayoutManager(gll);
En el código anterior, creamos una vista vertical con una capacidad máxima de 6 vistas secundarias por fila GridLayoutManager
. De forma predeterminada, el número total SpanSize
de una fila es 6 y el valor predeterminado de cada vista secundaria SpanSize
es 1. Por lo tanto, GridLayoutManager
cada fila será dividido en 6 sin procesar. Cada copia muestra una vista secundaria, como se muestra en la primera fila de la siguiente figura:
estilo
En este momento, si SpanSize
configuro todas las subvistas en 2, esta subvista ocupará todo el ancho disponible de RecyclerView 2/6
, como se muestra en la segunda fila de la figura anterior. De manera similar, SpanSize
subiré a 3, luego el ancho de la la subvista también aumentará Para el ancho disponible 3/6
, como se muestra en la tercera fila de la figura anterior, esta es también GridLayoutManager
la razón por la que se pueden establecer diferentes números de subvistas en diferentes filas. Por supuesto, también puede establecer las tres subvistas en la misma fila SpanSize
a 1, 2 y 3.
Está bien, cómo dibujar el título está a un paso del combate de código real.
2. ItemDecoration
La línea divisoria ItemDecoration
es algo muy interesante, porque puede lograr algunas cosas divertidas, como los siguientes títulos de letras y la línea de tiempo de la libreta de direcciones :
Título de la carta de la libreta de direcciones
Cronología
También puedes usarlo para hacer algunos efectos especiales, como el techo de los títulos de las letras. Aquí te recomiendo dos bibliotecas:
- Línea de tiempo de Vivian TimeLine
- El título de la libreta de direcciones de mcxtzhang, puede realizar el techo SuspensionIndexBar
Aquí hay una breve introducción al ItemDecoration
principio. Aquí asumo que View
el proceso de levantamiento y mapeo que los estudiantes ya han entendido se divide principalmente en dos partes:
- Dibuje la línea divisoria debajo de la
RecyclerView
vista secundaria, porque elItemDecoration
primer método de dibujo de la línea divisoriaItemDecoration#onDraw
ocurre antes de dibujar laRecyclerView
vista secundaria. Si desea que se muestre, debeItemDecoration
establecer un desplazamiento para que la vista secundaria esté desplazada de modo que que no se bloquearáItemDecoration
. - Dibuje la línea divisoria en
RecyclerView
la capa superior de la vista secundaria, porque el método de dibujo seItemDecoration#onDrawOver
produce después de que se completa el dibujo de laRecyclerView
vista secundaria, lo que tambiénItemDecoration
puede lograr el efecto de techo.
Tres, código de combate
Con la reserva de conocimientos anterior, lo siguiente es simple.
1. Decoración de artículos personalizados
Es ItemDecoration
necesario implementar tres métodos personalizados , con el principio relacionado que mencionamos anteriormente:
Nombre del método | Explicación |
---|---|
onDraw |
Dibuja la línea divisoria debajo de la subvista |
getItemOffsets |
El espacio normalmente reservado para mostrar el divisor inferior. |
onDrawOver |
Dibuja el divisor superior |
Nuestra tarea es solo dibujar un título, por lo que los dos métodos anteriores son suficientes.
1.1 Definir interfaz de datos
/**
* 数据约束
*/
public interface IGridItem {
/**
* 是否启用分割线
* @return true
*/
boolean isShow();
/**
* 分类标签
*/
String getTag();
/**
* 权重
*/
int getSpanSize();
}
1.2 Clase Custom ItemDecoration
El código central tiene más de 100 líneas:
/**
* 适用于GridLayoutManager的分割线
*/
public class GridItemDecoration extends RecyclerView.ItemDecoration {
// 记录上次偏移位置 防止一行多个数据的时候视图偏移
private List<Integer> offsetPositions = new ArrayList<>();
// 显示数据
private List<? extends IGridItem> gridItems;
// 画笔
private Paint mTitlePaint;
// 存放文字
private Rect mRect;
// 颜色
private int mTitleBgColor;
private int mTitleColor;
private int mTitleHeight;
private int mTitleFontSize;
private Boolean isDrawTitleBg = false;
private Context mContext;
// 总的SpanSize
private int totalSpanSize;
private int mCurrentSpanSize;
//... 省略一些方法
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
// 绘制标题的逻辑:
// 如果该行的数据的需要显示的标题不同于上行的标题,就绘制标题
final int paddingLeft = parent.getPaddingLeft();
final int paddingRight = parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int pos = params.getViewLayoutPosition();
IGridItem item = gridItems.get(pos);
if (item == null || !item.isShow())
continue;
if (i == 0) {
drawTitle(c, paddingLeft, paddingRight, child
, (RecyclerView.LayoutParams) child.getLayoutParams(), pos);
} else {
IGridItem lastItem = gridItems.get(pos - 1);
if (lastItem != null && !item.getTag().equals(lastItem.getTag())) {
drawTitle(c, paddingLeft, paddingRight, child,
(RecyclerView.LayoutParams) child.getLayoutParams(), pos);
}
}
}
}
/**
* 绘制标题
*
* @param canvas 画布
* @param pl 左边距
* @param pr 右边距
* @param child 子View
* @param params RecyclerView.LayoutParams
* @param pos 位置
*/
private void drawTitle(Canvas canvas, int pl, int pr, View child, RecyclerView.LayoutParams params, int pos) {
if (isDrawTitleBg) {
mTitlePaint.setColor(mTitleBgColor);
canvas.drawRect(pl, child.getTop() - params.topMargin - mTitleHeight, pl
, child.getTop() - params.topMargin, mTitlePaint);
}
IGridItem item = gridItems.get(pos);
String content = item.getTag();
if (TextUtils.isEmpty(content))
return;
mTitlePaint.setColor(mTitleColor);
mTitlePaint.setTextSize(mTitleFontSize);
mTitlePaint.setTypeface(Typeface.DEFAULT_BOLD);
mTitlePaint.getTextBounds(content, 0, content.length(), mRect);
float x = UIUtils.dip2px(20f);
float y = child.getTop() - params.topMargin - (mTitleHeight - mRect.height()) / 2;
canvas.drawText(content, x, y, mTitlePaint);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
// 预留逻辑:
// 只要是标题下面的一行,无论这行几个,都要预留空间给标题显示
int position = parent.getChildAdapterPosition(view);
IGridItem item = gridItems.get(position);
if (item == null || !item.isShow())
return;
if (position == 0) {
outRect.set(0, mTitleHeight, 0, 0);
mCurrentSpanSize = item.getSpanSize();
} else {
if (!offsetPositions.isEmpty() && offsetPositions.contains(position)) {
outRect.set(0, mTitleHeight, 0, 0);
return;
}
if (!TextUtils.isEmpty(item.getTag()) && !item.getTag().equals(gridItems.get(position - 1).getTag())) {
mCurrentSpanSize = item.getSpanSize();
} else
mCurrentSpanSize += item.getSpanSize();
if (mCurrentSpanSize <= totalSpanSize) {
outRect.set(0, mTitleHeight, 0, 0);
offsetPositions.add(position);
}
}
}
}
La lógica general es:
- Si la posición de la
RecyclerView
subvista está debajo del título, entonces necesita reservar espacio, colóquelo en eloutRect
medio. Cabe señalar que varias subvistas en la misma fila necesitan reservar espacio. - Dibuja el título de los datos actuales que sea diferente del título de los datos anteriores.
- Repite 1 y 2.
2. Parte de la interfaz
public class SpecialGridActivity extends AppCompatActivity {
// GridItem实现了IGridItem接口
private List<GridItem> values;
private RecyclerView mRecyclerView;
private GridItemDecoration itemDecoration;
// 自己封装的RecyclerAdapter
private RecyclerAdapter<GridItem> mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_special_grid);
initWidget();
}
private void initWidget() {
mRecyclerView = findViewById(R.id.rv_content);
// 创建GridLayoutManager,并设置SpanSizeLookup
GridLayoutManager gll = new GridLayoutManager(this, 3);
gll.setSpanSizeLookup(new SpecialSpanSizeLookup());
mRecyclerView.setLayoutManager(gll);
values = initData();
// 自己封装的RecyclerAdapter
mRecyclerView.setAdapter(mAdapter = new RecyclerAdapter<GridItem>(values,null) {
@Override
public ViewHolder<GridItem> onCreateViewHolder(View root, int viewType) {
switch (viewType) {
case R.layout.small_grid_recycle_item:
return new SmallHolder(root);
case R.layout.normal_grid_recycle_item:
return new NormalHolder(root);
case R.layout.special_grid_recycle_item:
return new SpecialHolder(root);
default:
return null;
}
}
@Override
public int getItemLayout(GridItem gridItem, int position) {
switch (gridItem.getType()) {
case GridItem.TYPE_SMALL:
return R.layout.small_grid_recycle_item;
case GridItem.TYPE_NORMAL:
return R.layout.normal_grid_recycle_item;
case GridItem.TYPE_SPECIAL:
return R.layout.special_grid_recycle_item;
}
return 0;
}
});
//...
// 分隔线生成
// 之前的GridItemDecoration代码中我将构建者模式部分省略了
itemDecoration = new GridItemDecoration.Builder(this,values, 3)
.setTitleTextColor(Color.parseColor("#4e5864"))
.setTitleFontSize(22)
.setTitleHeight(52)
.build();
mRecyclerView.addItemDecoration(itemDecoration);
}
// 数据初始化
private List<GridItem> initData() {
List<GridItem> values = new ArrayList<>();
values.add(new GridItem("我很忙", "", R.drawable.head_1,"最近常听",1,GridItem.TYPE_SMALL));
values.add(new GridItem("治愈:有些歌比闺蜜更懂你", "", R.drawable.head_2,"最近常听",1,GridItem.TYPE_SMALL));
values.add(new GridItem("「华语」90后的青春纪念手册", "", R.drawable.head_3,"最近常听",1,GridItem.TYPE_SMALL));
values.add(new GridItem("流行创作女神你霉,泰勒斯威夫特的创作历程", "", R.drawable.special_2
,"更多为你推荐",3,GridItem.TYPE_SPECIAL));
values.add(new GridItem("行走的CD写给别人的歌", "给「跟我走吧」几分,试试这些", R.drawable.normal_1
,"更多为你推荐",3,GridItem.TYPE_NORMAL));
values.add(new GridItem("爱情里的酸甜苦辣,让人捉摸不透", "听完「靠近一点点」,他们等你翻牌", R.drawable.normal_2
,"更多为你推荐",3,GridItem.TYPE_NORMAL));
values.add(new GridItem("关于喜欢你这件事,我都写在了歌里", "「好想你」听罢,听它们吧", R.drawable.normal_3
,"更多为你推荐",3,GridItem.TYPE_NORMAL));
values.add(new GridItem("周杰伦暖心混剪,短短几分钟是多少人的青春", "", R.drawable.special_1
,"更多为你推荐",3,GridItem.TYPE_SPECIAL));
values.add(new GridItem("我好想和你一起听雨滴", "给「发如雪」几分,那这些呢", R.drawable.normal_4
,"更多为你推荐",3,GridItem.TYPE_NORMAL));
values.add(new GridItem("油管周杰伦热门单曲Top20", "「周杰伦」的这些哥,你听了吗", R.drawable.normal_5
,"更多为你推荐",3,GridItem.TYPE_NORMAL));
return values;
}
class SpecialSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
@Override
public int getSpanSize(int i) {
// 返回在数据中定义的SpanSize
GridItem gridItem = values.get(i);
return gridItem.getSpanSize();
}
}
class SmallHolder extends RecyclerAdapter.ViewHolder<GridItem> {
//... 代码省略,就是设置图片和文字的操作
// 小的Holder
}
class NormalHolder extends RecyclerAdapter.ViewHolder<GridItem> {
//... 中等的Holder
}
class SpecialHolder extends RecyclerAdapter.ViewHolder<GridItem> {
//... 横向大的Holder
}
}
Usualmente usamos GridLayoutManager
no es lo mismo, lo GridLayoutManager
que debe establecer SpanSizeLookUp
es que debemos configurar para cada vista secundaria SpanSize
, porque hemos logrado cada IGridItem
interfaz de datos , que se proporcionará desde afuera SpanSize
, por lo que volvemos aquí en el conjunto de datos SpanSize
.
Debido a limitaciones de espacio, el archivo de diseño y el paquete de ReyclerAdapter no se publicarán. Los estudiantes interesados pueden consultar el código fuente. Lo siguiente es lo que logramos:
efecto
Cuatro, resumen
para resumir
Algunos detalles en el código fuente son muy interesantes Es por GridLayoutManager
el código fuente que he leído que apareció este artículo. Después de leer este artículo, creo que usted, como yo, tiene RecyclerView
una comprensión más profunda.
Sitio de demostración: https://github.com/mCyp/Orient-Ui
Si quieres y RecyclerView
tienes unos intercambios más profundos, bienvenido a mi desentrañar la RecyclerView
serie de artículos :
Primero: "desentrañar RecyclerView - LayoutManager"
El segundo: "desentrañar RecyclerView - poco a poco"
Parte III: "desentrañar RecyclerView - ItemAnimator & Adapter"
Autor: Nine heart _
enlace: https: //www.jianshu.com/p/60aa2fc17870
Fuente: Los libros de Jane
tienen derechos de autor del autor. Para reimpresiones comerciales, comuníquese con el autor para obtener autorización. Para reimpresiones no comerciales, indique la fuente.