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;
}
}