Android自定义PopupWindow实现流式布局筛选控件

时间:2022-12-04 05:26:59

前言:因公司项目重构需要,添加了二级菜单筛选及类似商品分类筛选的功能。上一篇文章介绍了带二级菜单的筛选控件,今天介绍类似流式布局的筛选控件,该控件继承自PopupWindow,并解决了高版本的显示问题。

先上效果图:

Android自定义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