RecyclerView使用StaggeredGridLayoutManager实现瀑布流

Screenshot_2021-06-17-20-38-52-023_com.ww.myapplication.jpg

header是一个宽度占满全屏的item,下边是一个瀑布流。

要点:
  • 实现瀑布流的要点:设置itemView的高度不一致
  • 防止滑动过程中item错位的要点:来回滑动在复用itemView的时候固定itemView的高度,策略是在给itemView设置高度的Model数据中固定高度数据。
  • 更新数据要点:瀑布流添加数据更新的时候,需要使用notifyItemInserted(data.size()),data.size()是添加数据后集合的大小

下边展示代码:
MainActivity的布局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <!--    <data>-->
    <!--        <variable-->
    <!--            name="user"-->
    <!--            type="com.ww.myapplication.model.User" />-->
    <!--    </data>-->
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintBottom_toTopOf="@+id/btn"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            />

        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="添加数据"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

普通itemView的布局文件:item_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/show_iv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:contentDescription="展示图片"
            android:scaleType="centerCrop"
            tools:ignore="HardcodedText" />

        <TextView
            android:id="@+id/desc_tv"
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:gravity="start|center_vertical"
            android:paddingStart="8dp"
            android:textSize="16sp"
            tools:ignore="RtlSymmetry"
            tools:text="测试文字" />
    </LinearLayout>
</androidx.cardview.widget.CardView>

Header的布局文件:item_header.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

</LinearLayout>

给Adapter的数据,封装成了一个Model,每个itemView的高度来源于这个Model

package com.ww.myapplication.model;

public class ImageModel {
    private int imageRes;
    private String desc;
    // 图片的高度
    private double imageHeight;

    public ImageModel(int imageRes, String desc, double imageHeight) {
        this.imageRes = imageRes;
        this.desc = desc;
        this.imageHeight = imageHeight;
    }

    public int getImageRes() {
        return imageRes;
    }

    public void setImageRes(int imageRes) {
        this.imageRes = imageRes;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public double getImageHeight() {
        return imageHeight;
    }

    public void setImageHeight(double imageHeight) {
        this.imageHeight = imageHeight;
    }
}

MainActivity中调用的代码:

package com.ww.myapplication;

import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import com.ww.myapplication.adapter.MyAdapter;
import com.ww.myapplication.adapter.SpaceItemDecoration;
import com.ww.myapplication.databinding.ActivityMainBinding;
import com.ww.myapplication.model.ImageModel;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 练习constraintLayout
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static String TAG = "== MainActivity ==>";
    private ActivityMainBinding binding;

    private List<ImageModel> imageModels = new ArrayList<>();
    private int[] imgRes = {
            R.drawable.img1,
            R.drawable.img2,
            R.drawable.img3,
            R.drawable.cat,
            R.drawable.cat2,
            R.drawable.lion2,
            R.drawable.lion1
    };
    private String[] descNames = {
            "春风又绿江南岸",
            "十步杀一人,千里不留行",
            "明月何时照我还",
            "君不见,黄河之水天上来",
            "蜀道难,难于上青天",
            "落霞与孤鹜齐飞",
            "关山难越,谁悲失路之人",
            "两岸猿声啼不住",
            "海上生明月,天涯共此时",
            "乘舟侧畔千帆过",
            "青青子衿,依依我心"
    };
    private MyAdapter myAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(LayoutInflater.from(this));
        setContentView(binding.getRoot());

        binding.btn.setOnClickListener(this);

        increaseData();

        StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
        staggeredGridLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
        binding.rv.setLayoutManager(staggeredGridLayoutManager);

       // 设置网格布局
       //  GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);
       //   binding.rv.setLayoutManager(gridLayoutManager);

        SpaceItemDecoration dividerItemDecoration = new SpaceItemDecoration(16);
        binding.rv.addItemDecoration(dividerItemDecoration);

        myAdapter = new MyAdapter();

        TextView textView = new TextView(this);
        LinearLayout.LayoutParams textViewLp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 50);
        textViewLp.gravity = Gravity.CENTER;
        textView.setText("这是Header Item");
        textView.setTextColor(Color.parseColor("#000000"));
        textView.setGravity(Gravity.CENTER);
        textView.setLayoutParams(textViewLp);
        myAdapter.addHeader(textView);

        binding.rv.setAdapter(myAdapter);

        myAdapter.addData(imageModels);


    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn) {

            myAdapter.addData(increaseData());


        }
    }

    private List<ImageModel> increaseData() {
        imageModels.clear();

        Random random = new Random();
        // 初始数据
        for (int i = 0; i < 10; i++) {
            imageModels.add(new ImageModel(imgRes[random.nextInt(imgRes.length)],
                    descNames[random.nextInt(descNames.length)],
                    getRandomDouble()));
        }
        return imageModels;
    }

    public double getRandomDouble() {
        Random random = new Random();
        double nextDouble = random.nextDouble();
        if (nextDouble < 0.1) {
            nextDouble = nextDouble + 0.55;
        }
        return nextDouble;
    }

}

MyAdapter的代码:

package com.ww.myapplication.adapter;

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import com.ww.myapplication.R;
import com.ww.myapplication.model.ImageModel;

import java.util.ArrayList;
import java.util.List;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyHolder> {

    public static int ITEM_HEADER = 0x012;
    public static int ITEM_NORMAL = 0x013;
    public static int ITEM_FOOTER = 0x014;

    private List<View> headerList = new ArrayList<>();
    private List<View> footerList = new ArrayList<>();
    private List<ImageModel> data;

    public MyAdapter() {
        data = new ArrayList<>();
    }

    public void addData(List<ImageModel> newData) {
        if (newData != null && newData.size() > 0) {
            data.addAll(newData);
            notifyItemInserted(data.size());
        }
    }

    public void addFooter(View view) {
        if (view != null) {
            footerList.add(view);
        }
    }

    public void addHeader(View view) {
        if (view != null) {
            headerList.add(view);
        }
    }

    @Override
    public int getItemViewType(int position) {

        if (position < headerList.size()) {
            // header item
            return ITEM_HEADER;
        } else if (position >= (headerList.size() + data.size())) {
            // footer item
            return ITEM_FOOTER;
        } else {
            // 正常的Item
            return ITEM_NORMAL;
        }
    }

    @NonNull
    @Override
    public MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = null;
        if (viewType == ITEM_HEADER || viewType == ITEM_FOOTER) {
            // header item
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_header, parent, false);
        } else {
            // 正常普通的item
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_normal, parent, false);
        }
        return new MyHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyHolder holder, int position) {
        if (holder.getItemViewType() == ITEM_HEADER) {
            LinearLayout headerViewParent = (LinearLayout) holder.itemView;
            if (headerViewParent.getChildCount() == 0) {
                Log.d("==TAG==>", "添加 header 子View");
                for (View view : headerList) {
                    // 将header view添加到itemView中
                    headerViewParent.addView(view);
                }
            }
        } else if (holder.getItemViewType() == ITEM_FOOTER) {
            LinearLayout footerViewParent = (LinearLayout) holder.itemView;
            if (footerViewParent.getChildCount() == 0) {
                Log.d("==TAG==>", "添加 footer 子View");
                for (View view : footerList) {
                    // 将footer view添加到itemView中
                    footerViewParent.addView(view);
                }
            }
        } else {
            ImageModel imageModel = data.get(position - headerList.size());

            RecyclerView.LayoutManager manager = mRecyclerView.getLayoutManager();
            if (manager instanceof StaggeredGridLayoutManager) {
                int screenWidth = getScreenWidth(holder.itemView.getContext());
                ViewGroup.LayoutParams lp = holder.showIv.getLayoutParams();
                lp.width = screenWidth / 2;
                // 固定设置每个ItemView的高度,防止滑动的复用ItemView的时候重新分配itemView的高度
                lp.height = (int) (screenWidth * imageModel.getImageHeight());
                holder.showIv.setLayoutParams(lp);
            } else if (manager instanceof GridLayoutManager) {
                int screenWidth = getScreenWidth(holder.itemView.getContext());
                ViewGroup.LayoutParams lp = holder.showIv.getLayoutParams();
                lp.width = screenWidth / 2;
                // 固定设置每个ItemView的高度,防止滑动的复用ItemView的时候重新分配itemView的高度
                lp.height = (int) (screenWidth * 2 / 3);
                holder.showIv.setLayoutParams(lp);
            } else {
                int screenWidth = getScreenWidth(holder.itemView.getContext());
                ViewGroup.LayoutParams lp = holder.showIv.getLayoutParams();
                lp.width = screenWidth / 2;
                // 固定设置每个ItemView的高度,防止滑动的复用ItemView的时候重新分配itemView的高度
                lp.height = (int) (screenWidth / 3);
                holder.showIv.setLayoutParams(lp);
            }
            holder.descTv.setText(imageModel.getDesc());
            holder.showIv.setImageResource(imageModel.getImageRes());
        }


    }

    private RecyclerView mRecyclerView;

    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        this.mRecyclerView = recyclerView;
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    // 指定每个item占用几个网格坑位
                    if (position < headerList.size() || position >= (headerList.size()) + data.size()) {
                        // header 或者 footer
                        return gridLayoutManager.getSpanCount();
                    }
                    return 1;
                }
            });
        }
    }

    @Override
    public void onViewAttachedToWindow(@NonNull MyHolder holder) {
        super.onViewAttachedToWindow(holder);
        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
            // 瀑布流的布局参数
            StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) lp;
            if (holder.getItemViewType() == ITEM_HEADER || holder.getItemViewType() == ITEM_FOOTER) {
                layoutParams.setFullSpan(true);
                holder.itemView.setLayoutParams(layoutParams);
            } else {
                layoutParams.setFullSpan(false);
                holder.itemView.setLayoutParams(layoutParams);
            }
        }
    }

    @Override
    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onDetachedFromRecyclerView(recyclerView);
    }

    @Override
    public void onViewDetachedFromWindow(@NonNull MyHolder holder) {
        super.onViewDetachedFromWindow(holder);
    }

    @Override
    public int getItemCount() {
        return data.size() + headerList.size() + footerList.size();
    }

    static class MyHolder extends RecyclerView.ViewHolder {

        private final ImageView showIv;
        private final TextView descTv;

        public MyHolder(@NonNull View itemView) {
            super(itemView);
            showIv = itemView.findViewById(R.id.show_iv);
            descTv = itemView.findViewById(R.id.desc_tv);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(itemView.getContext(), "position:" + getLayoutPosition(), Toast.LENGTH_SHORT).show();
                }
            });

        }
    }

    public int getScreenWidth(Context context) {
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        return displayMetrics.widthPixels;
    }

    public int getScreenHeight(Context context) {
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
        return displayMetrics.heightPixels;
    }


}

每个itemView的分割线:

package com.ww.myapplication.adapter;

import android.graphics.Rect;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

public class SpaceItemDecoration extends RecyclerView.ItemDecoration {
    private int space;

    public SpaceItemDecoration(int space) {
        this.space = space;
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.left = space;
        outRect.top = space;
        outRect.right = space;
        outRect.bottom = space;
    }
}

recyclerView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容

  • 瀑布流也是个常用的显示控件了,但是在使用时经常遇到一些问题,比如滑动回顶部后出现空隙、item在滑动时乱跳等问题。...
    程序员张晴天阅读 14,708评论 2 7
  • 谨以文章记录学习历程,如有错误还请指明。 RecyclerView 简介 首先,可以理解 RecyclerView...
    whd_Alive阅读 5,443评论 3 62
  • RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替...
    Jason_andy阅读 1,134评论 0 0
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 123,687评论 2 7
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,026评论 0 4