RecyclerView使用StaggeredGridLayoutManager實現(xiàn)瀑布流

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

header是一個寬度占滿全屏的item,下邊是一個瀑布流。

要點:
  • 實現(xiàn)瀑布流的要點:設(shè)置itemView的高度不一致
  • 防止滑動過程中item錯位的要點:來回滑動在復(fù)用itemView的時候固定itemView的高度,策略是在給itemView設(shè)置高度的Model數(shù)據(jù)中固定高度數(shù)據(jù)。
  • 更新數(shù)據(jù)要點:瀑布流添加數(shù)據(jù)更新的時候,需要使用notifyItemInserted(data.size()),data.size()是添加數(shù)據(jù)后集合的大小

下邊展示代碼:
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="添加數(shù)據(jù)"
            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的數(shù)據(jù),封裝成了一個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中調(diào)用的代碼:

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;

/**
 * 練習(xí)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 = {
            "春風(fēng)又綠江南岸",
            "十步殺一人,千里不留行",
            "明月何時照我還",
            "君不見,黃河之水天上來",
            "蜀道難,難于上青天",
            "落霞與孤鶩齊飛",
            "關(guān)山難越,誰悲失路之人",
            "兩岸猿聲啼不住",
            "海上生明月,天涯共此時",
            "乘舟側(cè)畔千帆過",
            "青青子衿,依依我心"
    };
    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);

       // 設(shè)置網(wǎng)格布局
       //  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();
        // 初始數(shù)據(jù)
        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;
                // 固定設(shè)置每個ItemView的高度,防止滑動的復(fù)用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;
                // 固定設(shè)置每個ItemView的高度,防止滑動的復(fù)用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;
                // 固定設(shè)置每個ItemView的高度,防止滑動的復(fù)用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占用幾個網(wǎng)格坑位
                    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) {
            // 瀑布流的布局參數(shù)
            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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 瀑布流也是個常用的顯示控件了,但是在使用時經(jīng)常遇到一些問題,比如滑動回頂部后出現(xiàn)空隙、item在滑動時亂跳等問題。...
    程序員張晴天閱讀 14,998評論 2 7
  • 謹(jǐn)以文章記錄學(xué)習(xí)歷程,如有錯誤還請指明。 RecyclerView 簡介 首先,可以理解 RecyclerView...
    whd_Alive閱讀 7,879評論 3 62
  • RecyclerView 是Android L版本中新添加的一個用來取代ListView的SDK,它的靈活性與可替...
    Jason_andy閱讀 1,188評論 0 0
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來的情緒。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,511評論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險厭惡者,不喜歡去冒險,但是人生放棄了冒險,也就放棄了無數(shù)的可能。 ...
    yichen大刀閱讀 7,647評論 0 4

友情鏈接更多精彩內(nèi)容