一、 效果演示
二、实现思路
流式布局如何实现?
在onMeasure方法里,循环子view。具体思路是判断同一行的子View宽度是否大于父View的宽度(注意父View要减去padding),如果大于就换行,如果不大于就不换行。其中处理最后一个子View,需要重新赋值宽和高。这里具体实现参考demo
单选列表怎么做?
最简单的方法是用RecyclerView,每个选项对应一个item。这里因为是单选可以有两种实现方式,第一种是用CheckBox,第二种是在bean里用一个参数标记,点击之后重新渲染设置选中效果
数据bean怎么设计最好?
数据bean,最简单的设计是typeName + List< child >,其中typeName是模块名字,List< child >是集合。child里可以包含id(child的id)、value(child的文本值)、isSelected(child是否选择)
三、关键代码
适配器FlowPopRecyclerViewAdapter
/**
* Created by zhangyan 2021/01/29
*/
public class FlowPopRecyclerViewAdapter extends RecyclerView.Adapter<FlowPopRecyclerViewAdapter.FlowPopViewHolder> {
private Context mContext;
private List<FiltrateBean> mData;
public FlowPopRecyclerViewAdapter(Context context) {
mData = new ArrayList<>();
this.mContext = context;
}
@NonNull
@Override
public FlowPopViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
FlowPopViewHolder viewHolder = new FlowPopViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_listview_property, viewGroup, false));
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull FlowPopRecyclerViewAdapter.FlowPopViewHolder viewHolder, int i) {
FlowPopViewHolder vh = (FlowPopViewHolder) viewHolder;
FiltrateBean item = mData.get(i);
vh.tvTypeName.setText(item.getTypeName());
setFlowLayoutData(item.getChildren(), vh.layoutProperty);
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public static class FlowPopViewHolder extends RecyclerView.ViewHolder {
private TextView tvTypeName;
private SkuFlowLayout layoutProperty;
public FlowPopViewHolder(@NonNull View itemView) {
super(itemView);
tvTypeName = itemView.findViewById(R.id.tv_type_name);
layoutProperty = itemView.findViewById(R.id.layout_property);
}
}
private void setFlowLayoutData(final List<FiltrateBean.Children> childrenList, final SkuFlowLayout flowLayout) {
flowLayout.removeAllViews();
for (int x = 0; x < childrenList.size(); x++) {
CheckBox checkBox = (CheckBox) View.inflate(mContext, R.layout.item_flowlayout_bill, null);
checkBox.setText(childrenList.get(x).getValue());
if (childrenList.get(x).isSelected()) {
checkBox.setChecked(true);
childrenList.get(x).setSelected(true);
} else {
checkBox.setChecked(false);
childrenList.get(x).setSelected(false);
}
final int finalX = x;
checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
refreshCheckBox(flowLayout, finalX, childrenList);
}
});
flowLayout.addView(checkBox);
}
}
private void refreshCheckBox(SkuFlowLayout flowLayout, int finalX, List<FiltrateBean.Children> propBeenList) {
for (int y = 0; y < flowLayout.getChildCount(); y++) {
CheckBox radio = (CheckBox) flowLayout.getChildAt(y);
radio.setChecked(false);
propBeenList.get(y).setSelected(false);
if (finalX == y) {
radio.setChecked(true);
propBeenList.get(y).setSelected(true);
}
}
}
/**
* 设置数据
* @param data
*/
public void setData(List<FiltrateBean> data){
mData.clear();
mData.addAll(data);
notifyDataSetChanged();
}
/**
* 清空数据
*/
public void clearData(){
mData.clear();
notifyDataSetChanged();
}
}
流式布局的测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int width = 0;
int height = 0;
int lineWidth = 0;
int lineHeight = 0;
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
if (i == cCount - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
continue;
}
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
lp.leftMargin = 10;
lp.rightMargin = 10;
lp.topMargin = 10;
lp.bottomMargin = 10;
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
width = Math.max(width, lineWidth);
lineWidth = childWidth;
height += lineHeight;
lineHeight = childHeight;
} else {
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
if (i == cCount - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
}
setMeasuredDimension(
modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//
);
}
四、完整代码
https://github.com/YanInfo/demo03
注意:
这个demo可以有很多的优化空间以适应更加复杂的需求。比如新增EditText文本框类型,新增地区联动类型等等,如果这样做就需要在bean里新增一个参数来区分类型,然后在适配器里根据不同的参数来加载不同的布局。另外demo可后续做一些提升,比如记住筛选历史,自定义弹框动画等等。其中记住历史可以用本地数据库去实现,这里点到为止,开发中可以用此为原版本进行定制。