序言
最近项目中要实现瀑布流的混排,之前还实现过GridLayoutManger 的混排,于是写了一些工具类来实现。使用了这个工具类,可以处理混排,可以处理间距。都集成在一个接
GridLayoutManger 混排
效果
可以实现混排,实现布局自适应,宽高比锁定,实现了边距
使用到的类
GridItemUI
下面的这个类是用来实现在GridLayouManger中混排的。
package com.haiwai.winttor.mine.other_home.product_list;
import android.graphics.Rect;
import android.util.Pair;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.zhuguohui.app.lib.action.TRSFunction;
/**
* <pre>
* Created by zhuguohui
* Date: 2024/8/23
* Time: 10:21
* Desc:用来实现GridItem混排的接口。
*
* 用法如下:
* <code>
* int spanCount = 3;
* GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), spanCount);
* GridItemUI.setSpanSizeLookup(gridLayoutManager,position->adapter.getItems().get(position));
* GridItemUI.addItemDecoration(recyclerView, spanCount, position -> adapter.getItems().get(position));
* </code>
* </pre>
*/
public interface GridItemUI {
/**
* 获取item之间的水平间隔 horizontal
*
* @return
*/
int getItemHorizontalSpaceSize();
/**
* 获取Item 之间垂直方向的间隔
*
* @return
*/
int getItemVerticalSpaceSize();
/**
* 用来查找adapter中对应position数据的接口
*/
interface FindAdapterContentFunction{
Object call(int position);
}
static void setSpanSizeLookup(GridLayoutManager gridLayoutManager, FindAdapterContentFunction findAdapterContentFunction) {
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
Object o = findAdapterContentFunction.call(position);
if (o instanceof GridItemUI) {
return 1;
}
return gridLayoutManager.getSpanCount();
}
});
}
class PositionInfo {
boolean firstLine;//处于第一行
boolean lineFirstItem;//处于第一行的第一列
int indexOfLine;//处于第几列,从0开始
}
static void addItemDecoration(RecyclerView recyclerView, int spanCount, FindAdapterContentFunction findAdapterContentFunction) {
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
Object o = findAdapterContentFunction.call(position);
if (o instanceof GridItemUI) {
PositionInfo info = getIsFirstLineAndIsLineFirstItem(position);
GridItemUI itemUI = (GridItemUI) o;
int hs = itemUI.getItemHorizontalSpaceSize();
int vs = itemUI.getItemVerticalSpaceSize();
outRect.left = getLeftSpace(info, spanCount, hs);
outRect.right = getRightSpace(info, spanCount, hs);
outRect.top = info.firstLine ? vs : 0;
outRect.bottom = vs;
} else {
super.getItemOffsets(outRect, view, parent, state);
}
}
private PositionInfo getIsFirstLineAndIsLineFirstItem(int position) {
int beforeItemCount = 0;
int checkPosition = position;
do {
checkPosition--;
if (checkPosition < 0) {
break;
}
Object o = findAdapterContentFunction.call(checkPosition);
if (!(o instanceof GridItemUI)) {
break;
}
beforeItemCount++;
} while (true);
boolean lineFirst = beforeItemCount % spanCount == 0;
boolean firstLine = beforeItemCount < spanCount;
PositionInfo info = new PositionInfo();
info.lineFirstItem = lineFirst;
info.firstLine = firstLine;
info.indexOfLine = beforeItemCount % spanCount;
return info;
}
});
}
static int getRightSpace(PositionInfo info, int spanCount, int vs) {
float ip = vs * 1.0f * (spanCount + 1) / spanCount;
float p = vs;
int i = info.indexOfLine;
return (int) ((i + 1) * (ip - p));
}
static int getLeftSpace(PositionInfo info, int spanCount, int vs) {
float ip = vs * 1.0f * (spanCount + 1) / spanCount;
float p = vs;
int i = info.indexOfLine;
return (int) ((i + 1) * p - i * ip);
}
}
AspectRatioLayout (支持按比例显示的FrameLayout)
package com.haiwai.winttor.mine.home.collect;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.haiwai.winttor.R;
/**
* <pre>
* Created by zhuguohui
* Date: 2024/8/30
* Time: 10:09
* Desc:支持按比例显示的View
* </pre>
*/
public class AspectRatioLayout extends FrameLayout {
private static final float UN_SET = -1;
float ratio = UN_SET;//宽高比
public AspectRatioLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AspectRatioLayout);
ratio = typedArray.getFloat(R.styleable.AspectRatioLayout_ratio, UN_SET);
typedArray.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (ratio != UN_SET) {
int heightSize = (int) (widthSize / ratio);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
配套的属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AspectRatioLayout">
<!--宽高比,宽度除以高度-->
<attr name="ratio" format="float"/>
</declare-styleable>
</resources>
用法
1.让你的UI元素实现GridItemUI
定义List为你要显示的数据,让这些数据中需要显示未GridLayout的类实现 GridItemUI接口。在接口中可以定义水平和垂直的边距。
类似这样。
布局锁定宽高比
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="10dp">
<com.haiwai.winttor.mine.home.collect.AspectRatioLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#fff"
app:ratio="0.885">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/ic_default_img" />
<ImageView
android:id="@+id/iv_collect_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:padding="6dp"
android:src="@mipmap/icon_shoucang_false" />
</com.haiwai.winttor.mine.home.collect.AspectRatioLayout>
</androidx.cardview.widget.CardView>
配置RecycleView和添加间隙
int spanCount = 3;
GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), spanCount);
//以下代码实现混排
GridItemUI. setSpanSizeLookup(gridLayoutManager,position->adapter. getItems().get(position));
//以下代码实现间距
GridItemUI. addItemDecoration(recyclerView, spanCount, position -> adapter. getItems().get(position));
就能实现上面的样式了
StaggeredGridLayoutManager 混排
效果
使用到的类
StaggeredUI
package com.trs.baseapp.news.list.ui.staggered;
import android.graphics.Rect;
import android.util.Pair;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.trs.library.callback.action.TRSFunction;
/**
* <pre>
* Created by zhuguohui
* Date: 2024/8/28
* Time: 11:24
* Desc:实现了该接口,表示当前UI是为了瀑布流设计的
* </pre>
*/
public interface StaggeredUI {
/**
* 用来查找adapter中对应position数据的接口
*/
interface FindAdapterContentFunction{
Object call(int position);
}
static void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder,FindAdapterContentFunction findAdapterContentFunction) {
int adapterPosition = holder.getAdapterPosition();
Object o = findAdapterContentFunction.call(adapterPosition);
StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
if (o instanceof StaggeredUI) {
params.setFullSpan(false);
} else {
params.setFullSpan(true);
}
}
static void addItemDecoration(RecyclerView recyclerView, int spanCount, FindAdapterContentFunction findAdapterContentFunction) {
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
Object o = findAdapterContentFunction.call(position);
if (o instanceof StaggeredUI itemUI) {
StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
int hs = itemUI.getItemHorizontalSpaceSize();
int vs = itemUI.getItemVerticalSpaceSize();
outRect.left = lp.getSpanIndex() == 0 ? hs : 0;
outRect.right = hs;
outRect.top = isFirstLine(position) ? vs : 0;
outRect.bottom = vs;
} else {
super.getItemOffsets(outRect, view, parent, state);
}
}
private boolean isFirstLine(int position) {
int beforeItemCount = 0;
int checkPosition = position;
do {
checkPosition--;
if (checkPosition < 0) {
break;
}
Object o = findAdapterContentFunction.call(checkPosition);
if (!(o instanceof StaggeredUI)) {
break;
}
beforeItemCount++;
} while (true);
boolean firstLine = beforeItemCount < spanCount;
return firstLine;
}
});
}
/**
* 返回垂直间距
* @return
*/
int getItemVerticalSpaceSize();
/**
* 返回水平间距
* @return
*/
int getItemHorizontalSpaceSize();
}
使用
1.在混排的数据中,把要瀑布流显示的数据实现接口
默认所有数据都不是瀑布流,只有实现了StaggeredUI 接口的数据才是瀑布流
2.使用接口的静态方法设置间距
3.重写adapter的 onViewAttachedToWindow 方法
直接调用StaggeredUI的同名静态方法就行了