Usando GridLayoutManager de esta manera, es posible que realmente no lo intentes

https://www.jianshu.com/p/60aa2fc17870
Prólogo

La semana pasada mencioné en el artículo "RecyclerView-LayoutManager" que la GridLayoutManagersiguiente 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 GridLayoutManagersolo se puede lograr un diseño de cuadrícula estándar. No fue hasta que decidí estudiar hace un tiempo RecyclerViewy leer GridLayoutManagerel 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 :

  1. ViewTener una comprensión simple del proceso de dibujo.
  2. CanvasSencillo y práctico.
  3. RecyclerView+GridLayoutManageruso de.

Tabla de contenido

Tabla de contenido

Uno, la escena

Utilice RecyclerView+ GridLayoutManager+ ItemDecorationescenarios 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 RecyclerViewrealizarse), 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 :

  1. Cómo mostrar diferentes números de subvistas para diferentes filas
  2. Dibujo del título de cada módulo

Las soluciones a estos dos problemas corresponden GridLayoutManagery ItemDecoration, respectivamente , las entendemos una por una.

1. GridLayoutManager

GridLayoutManagerDe hecho, ya estamos familiarizados con él, pero normalmente no entendemos SpanSizeeste 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 SpanSizede una fila es 6 y el valor predeterminado de cada vista secundaria SpanSizees 1. Por lo tanto, GridLayoutManagercada 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 SpanSizeconfiguro 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, SpanSizesubiré 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 GridLayoutManagerla 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 SpanSizea 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 ItemDecorationes 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 ItemDecorationprincipio. Aquí asumo que Viewel proceso de levantamiento y mapeo que los estudiantes ya han entendido se divide principalmente en dos partes:

  1. Dibuje la línea divisoria debajo de la RecyclerViewvista secundaria, porque el ItemDecorationprimer método de dibujo de la línea divisoria ItemDecoration#onDrawocurre antes de dibujar la RecyclerViewvista secundaria. Si desea que se muestre, debe ItemDecorationestablecer un desplazamiento para que la vista secundaria esté desplazada de modo que que no se bloqueará ItemDecoration.
  2. Dibuje la línea divisoria en RecyclerViewla capa superior de la vista secundaria, porque el método de dibujo se ItemDecoration#onDrawOverproduce después de que se completa el dibujo de la RecyclerViewvista secundaria, lo que también ItemDecorationpuede 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 ItemDecorationnecesario 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:

  1. Si la posición de la RecyclerViewsubvista está debajo del título, entonces necesita reservar espacio, colóquelo en el outRectmedio. Cabe señalar que varias subvistas en la misma fila necesitan reservar espacio.
  2. Dibuja el título de los datos actuales que sea diferente del título de los datos anteriores.
  3. 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 GridLayoutManagerno es lo mismo, lo GridLayoutManagerque debe establecer SpanSizeLookUpes que debemos configurar para cada vista secundaria SpanSize, porque hemos logrado cada IGridIteminterfaz 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 GridLayoutManagerel 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 RecyclerViewuna comprensión más profunda.

 

Sitio de demostración: https://github.com/mCyp/Orient-Ui

Si quieres y RecyclerViewtienes unos intercambios más profundos, bienvenido a mi desentrañar la RecyclerViewserie 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.

Supongo que te gusta

Origin blog.csdn.net/az44yao/article/details/112973589
Recomendado
Clasificación