这篇文章主要的功能是利用 PopupWindow 和 RecyclerView 实现条件筛选包括二级联动筛选,主要是仿小红书里的筛选功能而写的一个 Demo 效果如下,代码通俗易懂,保姆级教程
api 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46'
api 'com.google.code.gson:gson:2.8.6'
api 'com.alibaba:fastjson:1.2.61'
这里我模拟实际接口返回的数据而准备的数据源,在工程目录下新建 assets 资源文件,在新建一个JsonData.json 文件,复制数据源到此文件里
{
"code": "200",
"data": {
"result": [
{
"id": 1,
"list": [
{
"name": "全部分类"
}
],
"name": "全部分类"
},
{
"id": 2,
"list": [
{
"name": "天安门"
},
{
"name": "故宫"
},
{
"name": "颐和园"
},
{
"name": "圆明园"
},
{
"name": "北海公园"
},
{
"name": "雍和宫"
}
],
"name": "景点"
},
{
"id": 2,
"list": [
{
"name": "海鲜"
},
{
"name": "烧烤"
},
{
"name": "火锅"
},
{
"name": "烤肉"
},
{
"name": "西餐"
},
{
"name": "日料"
}
],
"name": "餐饮"
},
{
"id": 2,
"list": [
{
"name": "西单"
},
{
"name": "SKP"
},
{
"name": "荟聚"
},
{
"name": "凯德茂"
},
{
"name": "奥特莱斯"
},
{
"name": "老佛爷"
}
],
"name": "购物地"
},
{
"id": 2,
"list": [
{
"name": "电影"
},
{
"name": "SPA"
},
{
"name": "密室逃脱"
},
{
"name": "酒吧"
},
{
"name": "KTV"
},
{
"name": "足疗"
},
{
"name": "汗蒸/洗浴"
}
],
"name": "休闲娱乐"
},
{
"id": 2,
"list": [
{
"name": "希岸"
},
{
"name": "云朵"
},
{
"name": "橘子"
},
{
"name": "兰朵"
},
{
"name": "汉庭"
},
{
"name": "如家"
},
{
"name": "维也纳"
}
],
"name": "酒店"
}
]
},
"message": "success"
}
主页面 activity_main.xml
一级 AcolumnAdapter 适配器 item 文件
a_column_layout.xml
二级 BcolumnAdapter 适配器 item 文件
b_column_layout.xml
单选 GeneAdapter 适配器 item 文件
item_listview_popwin.xml
这里是 popupWindow 实现二级联动 布局文件,
popwin_supplier_list.xml
在 popupWindow 布局里自定义了 MyScrollView,重写 onMeasure() 方法可以实现自定义测量规则。其中,使用 getMeasuredHeight() 方法和 getMeasuredWidth() 方法可以获取 ScrollView 的测量高度和测量宽度。通过 setMaxHeight 来设置 ScrollView 的最大高度
public class MyScrollView extends ScrollView {
public static final String TAG = "MyScrollView";
private int maxHeight = -1;
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = getMeasuredHeight();
int width = getMeasuredWidth();
if (maxHeight > 0 && height > maxHeight) {
setMeasuredDimension(width, maxHeight);
}
}
public void setMaxHeight(int height) {
this.maxHeight = height;
}
}
public class ToolsUtils {
/**
* 读取本地资源文件
*
* @param context 上下文
* @param fileName 本地数据文件名
* @return
*/
public static String getFromAssets(Context context, String fileName) {
InputStreamReader inputReader = null;
BufferedReader bufReader = null;
try {
inputReader = new InputStreamReader(context.getResources().getAssets().open(fileName));
bufReader = new BufferedReader(inputReader);
String line = "";
StringBuilder result = new StringBuilder();
while ((line = bufReader.readLine()) != null)
result.append(line);
return result.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputReader != null) {
inputReader.close();
}
if (bufReader != null) {
bufReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return "";
}
}
public class JsonBean {
private String code;
private String message;
private DataBean data;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public DataBean getData() {
return data;
}
public void setData(DataBean data) {
this.data = data;
}
public static class DataBean {
private List result;
public List getResult() {
return result;
}
public void setResult(List result) {
this.result = result;
}
public static class ResultBean {
private int id;
private List list;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static class ListBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
}
}
public class AcolumnAdapter extends BaseQuickAdapter {
private int position;
public AcolumnAdapter(int layoutResId, @Nullable List resultList) {
super(layoutResId, resultList);
}
@Override
protected void convert(BaseViewHolder helper, JsonBean.DataBean.ResultBean item) {
helper.setTextColor(R.id.tv_class_name, helper.getLayoutPosition() == position ? Color.parseColor("#00d8a0") : Color.parseColor("#363636"));
helper.setText(R.id.tv_class_name, item.getName());
}
public void setSelection(int pos) {
this.position = pos;
notifyDataSetChanged();
}
}
public class BcolumnAdapter extends BaseQuickAdapter {
private BcolumnAdapter.OnCallBackData onCallBackData;
public BcolumnAdapter(int layoutResId, @Nullable List resultList) {
super(layoutResId, resultList);
}
@Override
protected void convert(BaseViewHolder helper, JsonBean.DataBean.ResultBean.ListBean item) {
helper.addOnClickListener(R.id.item_layout);
helper.setText(R.id.tv_name, item.getName() + "");
onCallBackData.convertView(helper, item);
}
public void setOnCallBackData(BcolumnAdapter.OnCallBackData onCallBackData) {
this.onCallBackData = onCallBackData;
}
public interface OnCallBackData {
void convertView(BaseViewHolder holder, T item);
}
}
public class GeneAdapter extends BaseQuickAdapter {
public GeneAdapter(@LayoutRes int layoutResId, @Nullable List data) {
super(layoutResId, data);
}
public GeneAdapter(@Nullable List data) {
super(data);
}
public GeneAdapter(@LayoutRes int layoutResId) {
super(layoutResId);
}
private OnCallBackData onCallBackData;
@Override
protected void convert(BaseViewHolder holder, T item) {
if (onCallBackData != null) {
onCallBackData.convertView(holder, item);
}
}
public void setOnCallBackData(OnCallBackData onCallBackData) {
this.onCallBackData = onCallBackData;
}
public interface OnCallBackData {
void convertView(BaseViewHolder holder, T item);
}
}
这里在 adapter 中 定义了一个 OnCallBackData 接口,它是一个泛型接口,表示在回调中传递数据的类型是 T。该接口包含一个 convertView() 方法,用于将数据绑定到 ViewHolder 中。同二级适配器 BcolumnAdapter 中一样 类型可以自定,其中,BaseViewHolder 是一个通用的 ViewHolder 类,用于存储 ItemView 中的 View 控件,从而避免在每次滚动列表时都重新查找控件。OnCallBackData 接口将数据绑定到 ViewHolder 中,可以通过 ViewHolder 中的控件来更新 ItemView 的内容。该接口的作用是将数据和视图分离,避免在 Activity 或 Fragment 中写过多的 UI 逻辑,同时也方便数据的管理和更新。通过实现该接口,可以将数据的获取和数据的展示分为两个部分,提高代码的可读性和可维护性。在 MainActivity 实现该接口,用于展示 选中效果
主要是读取本地数据添加到数组,以及 popupWindow 的创建 跟点击交互
public class MainActivity extends AppCompatActivity {
private LinearLayout ll_list_default;
private TextView ll_list_default_txt;
private ImageView ll_list_default_icon;
private LinearLayout ll_list_brand;
private TextView list_brand_txt;
private ImageView ll_list_brand_icon;
private LinearLayout list_list_type;
private TextView list_list_type_txt;
private ImageView list_list_type_icon;
private RecyclerView recyclerView;
private GeneAdapter popAdapter, popAdapter2;
private List popList, popList1, popList2;
private PopupWindow popupWindow;
private String currentBrand, currentType;
private MyScrollView ui_sl_container;
private RecyclerView assRecyclerView;
private String jsonData;
private AcolumnAdapter acolumnAdapter;
private BcolumnAdapter bcolumnAdapter;
private List resultList;
private List result1List;
private int flag = 0;
private boolean clickFlag = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InitView();
String fromAssets = ToolsUtils.getFromAssets(this, "JsonData.json");
JsonBean jsonBean = new Gson().fromJson(fromAssets, JsonBean.class);
resultList = jsonBean.getData().getResult();
String fromAssets1 = ToolsUtils.getFromAssets(this, "JsonData.json");
JsonBean jsonBean1 = new Gson().fromJson(fromAssets1, JsonBean.class);
result1List = jsonBean1.getData().getResult().get(0).getList();
ll_list_default = findViewById(R.id.ll_list_default);
ll_list_default_txt = findViewById(R.id.ll_list_default_txt);
ll_list_default_icon = findViewById(R.id.ll_list_default_icon);
ll_list_brand = findViewById(R.id.ll_list_brand);
list_brand_txt = findViewById(R.id.list_brand_txt);
ll_list_brand_icon = findViewById(R.id.ll_list_brand_icon);
list_list_type = findViewById(R.id.list_list_type);
list_list_type_txt = findViewById(R.id.list_list_type_txt);
list_list_type_icon = findViewById(R.id.list_list_type_icon);
View contentView = getLayoutInflater().inflate(R.layout.popwin_supplier_list, null);
ui_sl_container = contentView.findViewById(R.id.pop_common_sl_container);
ui_sl_container.setMaxHeight(860);
//二级列表
assRecyclerView = contentView.findViewById(R.id.supplier_list_lv);
//一级列表
recyclerView = contentView.findViewById(R.id.popwin_supplier_list_lv);
popupWindow = new PopupWindow(contentView, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT, false);
popupWindow.setOutsideTouchable(true);
popupWindow.setBackgroundDrawable(new BitmapDrawable());
popupWindow.setFocusable(true);
popupWindow.setAnimationStyle(R.style.popwin_anim_style);
//监听popupWindow关闭事件
popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
ll_list_default_txt.setTextColor(Color.parseColor("#333333"));
ll_list_default_icon.setImageResource(R.mipmap.screen_icon_normal);
list_brand_txt.setTextColor(Color.parseColor("#333333"));
ll_list_brand_icon.setImageResource(R.mipmap.screen_icon_normal);
list_list_type_txt.setTextColor(Color.parseColor("#333333"));
list_list_type_icon.setImageResource(R.mipmap.screen_icon_normal);
clickFlag = true;
}
});
ll_list_default.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (clickFlag) {
ll_list_default_txt.setTextColor(Color.parseColor("#00d8a0"));
ll_list_default_icon.setImageResource(R.mipmap.screen_icon_selected);
clickFlag = false;
} else {
ll_list_default_txt.setTextColor(Color.parseColor("#333333"));
ll_list_default_icon.setImageResource(R.mipmap.screen_icon_normal);
clickFlag = true;
}
assRecyclerView.setVisibility(View.GONE);
showPopupWindow0(v);
}
});
ll_list_brand.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (clickFlag) {
list_brand_txt.setTextColor(Color.parseColor("#00d8a0"));
ll_list_brand_icon.setImageResource(R.mipmap.screen_icon_selected);
clickFlag = false;
} else {
list_brand_txt.setTextColor(Color.parseColor("#333333"));
ll_list_brand_icon.setImageResource(R.mipmap.screen_icon_normal);
clickFlag = true;
}
assRecyclerView.setVisibility(View.VISIBLE);
showPopupWindow1(v);
}
});
list_list_type.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (clickFlag) {
list_list_type_txt.setTextColor(Color.parseColor("#00d8a0"));
list_list_type_icon.setImageResource(R.mipmap.screen_icon_selected);
clickFlag = false;
} else {
list_list_type_txt.setTextColor(Color.parseColor("#333333"));
list_list_type_icon.setImageResource(R.mipmap.screen_icon_normal);
clickFlag = true;
}
assRecyclerView.setVisibility(View.GONE);
showPopupWindow2(v);
}
});
}
private void showPopupWindow0(View v) {
popupWindow.showAsDropDown(v);
popAdapter = new GeneAdapter<>(R.layout.item_listview_popwin, popList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(popAdapter);
popAdapter.setOnCallBackData(new GeneAdapter.OnCallBackData() {
@Override
public void convertView(BaseViewHolder holder, String item) {
((TextView) holder.getView(R.id.listview_popwind_tv)).setText(item);
String list_default_trim = ll_list_default_txt.getText().toString().trim();
if (list_default_trim.equals(item)) {
((TextView) holder.getView(R.id.listview_popwind_tv)).setTextColor(Color.parseColor("#00d8a0"));
holder.getView(R.id.iv_select_icon).setVisibility(View.VISIBLE);
} else {
((TextView) holder.getView(R.id.listview_popwind_tv)).setTextColor(Color.parseColor("#333333"));
holder.getView(R.id.iv_select_icon).setVisibility(View.GONE);
}
}
});
popAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
popupWindow.dismiss();
currentBrand = popList.get(position);
ll_list_default_txt.setText(currentBrand);
ll_list_default_txt.setTextColor(Color.parseColor("#333333"));
ll_list_default_icon.setImageResource(R.mipmap.screen_icon_normal);
clickFlag = true;
}
});
}
private void showPopupWindow1(View v) {
popupWindow.showAsDropDown(v);
acolumnAdapter = new AcolumnAdapter(R.layout.a_column_layout, resultList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(acolumnAdapter);
//设置默认的选取状态
acolumnAdapter.setSelection(flag);
bcolumnAdapter = new BcolumnAdapter(R.layout.b_column_layout, result1List);
assRecyclerView.setLayoutManager(new LinearLayoutManager(this));
assRecyclerView.setAdapter(bcolumnAdapter);
result1List.clear();
result1List.addAll(resultList.get(flag).getList());
bcolumnAdapter.notifyDataSetChanged();
bcolumnAdapter.setOnCallBackData(new BcolumnAdapter.OnCallBackData() {
@Override
public void convertView(BaseViewHolder holder, JsonBean.DataBean.ResultBean.ListBean item) {
((TextView) holder.getView(R.id.tv_name)).setText(item.getName());
String list_brand_trim = list_brand_txt.getText().toString().trim();
if (list_brand_trim.equals(item.getName())) {
((TextView) holder.getView(R.id.tv_name)).setTextColor(Color.parseColor("#00d8a0"));
holder.getView(R.id.iv_select_icon).setVisibility(View.VISIBLE);
} else {
((TextView) holder.getView(R.id.tv_name)).setTextColor(Color.parseColor("#333333"));
holder.getView(R.id.iv_select_icon).setVisibility(View.GONE);
}
}
});
//左侧列表的事件处理
acolumnAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
acolumnAdapter.setSelection(position);
flag = position;
result1List.clear();
result1List.addAll(resultList.get(position).getList());
bcolumnAdapter.notifyDataSetChanged();
}
});
//右侧列表的事件处理
bcolumnAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
String name = result1List.get(position).getName();
list_brand_txt.setText(name);
acolumnAdapter.setSelection(flag);
list_brand_txt.setTextColor(Color.parseColor("#333333"));
ll_list_brand_icon.setImageResource(R.mipmap.screen_icon_normal);
clickFlag = true;
result1List.clear();
popupWindow.dismiss();
}
});
}
private void showPopupWindow2(View v) {
popupWindow.showAsDropDown(v);
popAdapter2 = new GeneAdapter<>(R.layout.item_listview_popwin, popList2);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(popAdapter2);
popAdapter2.setOnCallBackData(new GeneAdapter.OnCallBackData() {
@Override
public void convertView(BaseViewHolder holder, String item) {
((TextView) holder.getView(R.id.listview_popwind_tv)).setText(item);
String list_list_trim = list_list_type_txt.getText().toString().trim();
if (list_list_trim.equals(item)) {
((TextView) holder.getView(R.id.listview_popwind_tv)).setTextColor(Color.parseColor("#00d8a0"));
holder.getView(R.id.iv_select_icon).setVisibility(View.VISIBLE);
} else {
((TextView) holder.getView(R.id.listview_popwind_tv)).setTextColor(Color.parseColor("#333333"));
holder.getView(R.id.iv_select_icon).setVisibility(View.GONE);
}
}
});
popAdapter2.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
popupWindow.dismiss();
currentType = popList2.get(position);
list_list_type_txt.setText(currentType);
list_list_type_txt.setTextColor(Color.parseColor("#333333"));
list_list_type_icon.setImageResource(R.mipmap.screen_icon_normal);
clickFlag = true;
}
});
}
private void InitView() {
popList = new ArrayList<>();
popList.add("综合排序");
popList.add("距离优先");
popList.add("人气优先");
jsonData = JSON.toJSONString(popList);
Log.e("tag", jsonData);
popList1 = new ArrayList<>();
popList1.add("全部分类");
popList1.add("景点");
popList1.add("餐饮");
popList1.add("购物地");
popList1.add("休闲娱乐");
popList1.add("酒店");
jsonData = JSON.toJSONString(popList1);
Log.e("tag", jsonData);
popList2 = new ArrayList<>();
popList2.add("全部商区");
popList2.add("热门商区");
popList2.add("朝阳区");
popList2.add("东城区");
popList2.add("海淀区");
popList2.add("西城区");
popList2.add("丰台区");
popList2.add("延庆区");
popList2.add("顺义区");
popList2.add("昌平区");
popList2.add("密云区");
popList2.add("怀柔区");
popList2.add("石景山区");
popList2.add("房山区");
popList2.add("大兴区");
popList2.add("通州区");
jsonData = JSON.toJSONString(popList2);
Log.e("tag", jsonData);
}
}
以上就是全部代码,利用 PopupWindow + RecyclerView 实现筛选二级联动功能,代码很简单,需要注意一下 PopupWindow 关闭事件 跟 控件点击事件 之间的焦点冲突问题,
如果对你有所帮助的话,不妨 Star 或 Fork,青山不改,绿水长流 有缘江湖再见 ~
源码地址:PopLinkage