前言:因公司项目重构需要,添加了二级菜单筛选及类似商品分类筛选的功能。上一篇文章介绍了带二级菜单的筛选控件,今天介绍类似流式布局的筛选控件,该控件继承自PopupWindow,并解决了高版本的显示问题。
先上效果图:
实现方式:
1.继承自PopupWindow
2.linearLayout+GridLayout显示数据
3.接口回调,更新UI
1.定义PopupWindow内部类Builder
注:Builder类用于设置PopupWindow的参数设置、popupWindow布局文件初始化、GridLayout布局的数据展示等
(1)定义参数设置方法
private Context context;//上下文对象 private List<FilterModel> listData;//要显示的数据集合 private int columnCount;//列数 private GridLayout mGridLayout;//用于显示流式布局 private LinearLayout llContent;//popupWindow的内容显示 //背景颜色 private int colorBg = Color.parseColor("#F8F8F8"); //默认的标题和标签的大小(sp) private int titleTextSize = 16; private int tabTextSize = 16; //标题字体颜色 private int titleTextColor = Color.parseColor("#333333"); //tab标签字体颜色 private int labelTextColor = R.color.color_popup; //tab标签背景颜色 private int labelBg = R.drawable.shape_circle_bg; //当前加载的行数 private int row = -1; private FlowPopupWindow mFlowPopupWindow; private List<String> labelLists=new ArrayList<>();//保存选中的标签数据 public Builder(Context context) { this.context = context; } /** * 设置数据集合 * */ public void setValues(List<FilterBean> listData) { this.listData = listData; } /** * 设置gridLayout的列数 * @param columnCount 列数 */ public void setColumnCount(int columnCount){ this.columnCount = columnCount; } /** * 设置内容区域的背景色 * @param color 颜色 */ public void setBgColor(int color){ colorBg = context.getResources().getColor(color); } /** * 标题字体大小 * @param titleTextSize 字体大小 */ public void setTitleSize(int titleTextSize) { this.titleTextSize = titleTextSize; } /** * tab标签字体大小 * @param tabTextSize 标签字体大小 */ public void setLabelSize(int tabTextSize) { this.tabTextSize = tabTextSize; } /** * 标题字体颜色 * @param titleTextColor 颜色 */ public void setTitleColor(int titleTextColor) { this.titleTextColor = titleTextColor; } /** * tab标签字体颜色 * @param labelTextColor 颜色 */ public void setTabColor(int labelTextColor) { this.labelTextColor = labelTextColor; } /** * 设置标签的背景色 * @param labelBg 背景色(drawable) */ public void setLabelBg(int labelBg) { this.labelBg = labelBg; }(2)定义build类,初始化popupWindow布局及GridLayout布局
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void build(){ //初始化popupWindow的布局文件 initPopup(getRowCount(),columnCount); //设置gridLayout的数据 setGridData(); }
A、初始化PopupWindow布局:initPopup方法
/** * 初始化PopupWindow的布局 * @param rowCount 行数 * @param columnCount 列数 */ private void initPopup(int rowCount,int columnCount){ //初始化popupWindow的布局文件 View view = LayoutInflater.from(context).inflate(R.layout.flow_popup,null); //主要用于设置数据显示区域的背景色 LinearLayout ll=view.findViewById(R.id.ll); //流布局数据展示控件 mGridLayout=view.findViewById(R.id.grid_layout); //确定按钮 Button btnConfirm=view.findViewById(R.id.btn_confirm); //设置数据展示区域的背景色 ll.setBackgroundColor(colorBg); llContent = new LinearLayout(context); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); view.setLayoutParams(params); llContent.addView(view); //设置背景色,不设置的话在有些机型会不显示popupWindow llContent.setBackgroundColor(Color.argb(60, 0, 0, 0)); llContent.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { hidePopup(); } }); //确定按钮的点击事件 btnConfirm.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //监听接口的数据回调方法 flowPopupMonitor.setFlowPopupResult(labelLists); //隐藏popupWindow hidePopup(); } }); //设置mGridLayout的属性参数 mGridLayout.setOrientation(GridLayout.HORIZONTAL); mGridLayout.setRowCount(rowCount); mGridLayout.setColumnCount(columnCount); //设置gridLayout消费触摸事件 mGridLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } }); int padding = context.getResources().getDimensionPixelSize(R.dimen.dp_10); mGridLayout.setPadding(padding,padding,padding,padding); }隐藏popupWindow方法:hidePopup()
/** * 隐藏popupWindow */ private void hidePopup() { if (mFlowPopupWindow != null&&mFlowPopupWindow.isShowing()){ mFlowPopupWindow.dismiss(); } }B、设置GridLayout的数据展示
/** * 将数据设置给GridLayout */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void setGridData() { for (int i = 0; i < listData.size(); i++){ //行数++ ++row; //显示每个条目类型的控件 TextView tvType = new TextView(context); tvType.setText(listData.get(i).getTypeName()); tvType.setTextColor(titleTextColor); tvType.setTextSize(titleTextSize); //配置列 第一个参数是起始列标 第二个参数是占几列 title(筛选类型)应该占满整行,so -> 总列数 GridLayout.Spec columnSpec = GridLayout.spec(0,columnCount); //配置行 第一个参数是起始行标 起始行+起始列就是一个确定的位置 GridLayout.Spec rowSpec = GridLayout.spec(row); //将Spec传入GridLayout.LayoutParams并设置宽高为0或者WRAP_CONTENT,必须设置宽高,否则视图异常 GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(rowSpec, columnSpec); layoutParams.width = GridLayout.LayoutParams.WRAP_CONTENT; layoutParams.height = GridLayout.LayoutParams.WRAP_CONTENT; layoutParams.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); layoutParams.bottomMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_8); mGridLayout.addView(tvType,layoutParams); //添加tab标签 addTabs(listData.get(i),i); } }
添加tab标签的方法
/** * 添加tab标签 * @param model 数据bean * @param labelIndex 标签的标号 */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void addTabs(final FilterBean bean, final int labelIndex){ List<FilterBean.TableMode> tabs = bean.getTabs(); for (int i = 0; i < tabs.size(); i++){ if (i % columnCount == 0){ row ++; } final FilterBean.TableMode tab = tabs.get(i); //显示标签的控件 final TextView label = new TextView(context); //设置默认选中第一个 if (i==0) { //每个tab的第一个设置为选中 label.setSelected(true); //记录选中的tab值 bean.setTab(tab); } label.setTextSize(tabTextSize); label.setTextColor(context.getResources().getColorStateList(labelTextColor)); label.setBackgroundDrawable(context.getResources().getDrawable(labelBg)); label.setSingleLine(true); label.setGravity(Gravity.CENTER); label.setEllipsize(TextUtils.TruncateAt.MIDDLE); //上下padding值 int paddingT = context.getResources().getDimensionPixelSize(R.dimen.dp_5); //左右padding值 int paddingL = context.getResources().getDimensionPixelSize(R.dimen.dp_8); label.setPadding(paddingL,paddingT,paddingL,paddingT); //getItemLayoutParams用于设置label标签的参数 mGridLayout.addView(label,getItemLayoutParams(i,row)); label.setText(tab.name); if (tabs.get(i) == bean.getTab()){ label.setSelected(true); } //标签的点击事件 label.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (tab != bean.getTab()){ Log.e("rcw","index--->"+getIndex(bean,labelIndex)); //清空上次选中的状态 mGridLayout.getChildAt(getIndex(bean,labelIndex)).setSelected(false); //设置当前点击选中的tab
bean.setTab(tab);label.setSelected( true) ; String labelText= label.getText().toString() ; labelLists.add( bean.getTypeName()+ "-"+labelText) ; Log. e( "rcw" , "labelText--->"+ bean.getTypeName()+ "-" +labelText) ; } } }) ; }}
设置GridLayout的item的属性参数的方法
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private GridLayout.LayoutParams getItemLayoutParams(int i, int row){ //使用Spec定义子控件的位置和比重 GridLayout.Spec rowSpec = GridLayout.spec(row,1f); GridLayout.Spec columnSpec = GridLayout.spec(i%columnCount,1f); //将Spec传入GridLayout.LayoutParams并设置宽高为0,必须设置宽高,否则视图异常 GridLayout.LayoutParams lp = new GridLayout.LayoutParams(rowSpec, columnSpec); lp.width = 0; lp.height = GridLayout.LayoutParams.WRAP_CONTENT; lp.bottomMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_8); if(i % columnCount == 0) {//最左边 lp.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_10); lp.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_20); }else if((i + 1) % columnCount == 0){//最右边 lp.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_10); }else {//中间 lp.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_20); } return lp; }
其他相关方法
/** * 获取当前选中标签在整个GridLayout的索引 * @return 标签下标 */ private int getIndex(FilterBean bean, int labelIndex){ int index = 0; for (int i = 0; i < labelIndex; i++){ //计算当前类型之前的元素所占的个数 title算一个 index += listData.get(i).getTabs().size() + 1; } //加上当前 title下的索引 FilterModel.TableMode tableModel = bean.getTab(); index += bean.getTabs().indexOf(tableModel) + 1; return index; } /** * 获取内容行数 * @return 行数 */ private int getRowCount(){ int row = 0; for (FilterBean bean : listData){ //计算当前类型之前的元素所占的个数 标题栏也算一行 row ++; int size = bean.getTabs().size(); row += (size / columnCount) + (size % columnCount > 0 ? 1 : 0) ; } return row; }
(3)定义创建PopupWindow的方法
/** * 创建popupWindow * @return FlowPopupWindow实例 */ public FlowPopupWindow createPopup(){ if (listData == null || listData.size() == 0){ try { throw new Exception("没有筛选标签"); } catch (Exception e) { Toast.makeText(context,e.getMessage(),Toast.LENGTH_SHORT).show(); e.printStackTrace(); } return null; } mFlowPopupWindow = new FlowPopupWindow(context,llContent); return mFlowPopupWindow; }注:以上定义的方法均是在内部类Builder中实现的
2.重写构造方法及showAsDropDown
(1)构造方法
private FlowPopupWindow(Context context,View view){ //这里可以修改popupWindow的宽高 super(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); setContentView(view); //设置popupWindow弹出和消失的动画效果 //setAnimationStyle(R.style.popwin_anim_style); //设置有焦点 setFocusable(true); //设置点击外部可消失 setOutsideTouchable(true); }
(2)showAsDropDown方法
重写showAsDropDown方法的目的是为了解决高版本不兼容的问题,在高版本中,popupWindow的位置不会出现在相应控件的下方,而是在系统状态栏的地方,有兴趣的可以注掉重写的showAsDropDown方法在高版本手机中进行测试。
/** * 重写showAsDropDown方法,解决高版本不在控件下方显示的问题 * @param anchor popupWindow要显示在的控件 */ @Override public void showAsDropDown(View anchor) { if(Build.VERSION.SDK_INT >= 24) { Rect rect = new Rect(); anchor.getGlobalVisibleRect(rect); int h = anchor.getResources().getDisplayMetrics().heightPixels - rect.bottom; setHeight(h); } super.showAsDropDown(anchor); }
3.定义接口回调方法
private static FlowPopupMonitor flowPopupMonitor; public interface FlowPopupMonitor{ void setFlowPopupResult(List<String> filterResult); } public void setFlowPopupMonitor(FlowPopupMonitor flowPopupMonitor){ this.flowPopupMonitor=flowPopupMonitor; }
注:FlowPopupMonitor接口的实现方法setFlowPopupResult是在确定按钮点击事件中调用的。
4.控件使用
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void initFlowPopup() { FlowPopupWindow.Builder builder=new FlowPopupWindow.Builder(context); //设置数据 builder.setValues(lists); //设置标签字体的颜色,这里的color不是values目录下的color,而是res文件夹下的color builder.setLabelColor(R.color.color_popup); //设置标签的背景色 builder.setLabelBg(R.drawable.flow_popup); //设置GridLayout的列数 builder.setColumnCount(4); //初始化popupWindow的相关布局及数据展示 builder.build(); //创建popup mFixPopupWindow=builder.createPopup(); //设置数据监听接口 mFixPopupWindow.setFlowPopupMonitor(this); mFixPopupWindow.showAsDropDown(btn2); mFixPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { @Override public void onDismiss() { ivArrow.setImageResource(R.drawable.arrow_down); } }); }
注:setLabelColor中的color不是values目录下的color,是在res文件夹下color
附上数据类的Bean:
/** * Created by ruancw on 2018/5/31. * 用于筛选的数据类 */ public class FilterBean { private String typeName;//标题名字 private TableMode tab;//用于记录上次点击的位置 private List<TableMode> tabs; //标签集合 public FilterBean(String typeName, TableMode tab, List<TableMode> tabs) { this.typeName = typeName; this.tab = tab; this.tabs = tabs; } public String getTypeName() { return typeName; } public void setTypeName(String typeName) { this.typeName = typeName; } public TableMode getTab() { return tab; } public void setTab(TableMode tab) { this.tab = tab; } public List<TableMode> getTabs() { return tabs; } public void setTabs(List<TableMode> tabs) { this.tabs = tabs; } public static class TableMode{ String name; public TableMode(String name) { this.name = name; } } }
总结:通过自定义PopupWindow的方式,使用LinearLayout+GridLayout的布局,实现了类似流布局的筛选控件,并通过重写showAsDropDown方法解决高版本显示的问题。
如有任何疑问,欢迎评论留言,谢谢!!!
带二级菜单的筛选控件地址:https://blog.csdn.net/ruancw/article/details/80522881
参考链接:https://www.2cto.com/kf/201804/735958.html