美文网首页Android UIAndroid控件自定义控件
Android自定义PopupWindow实现流式布局筛选控件(

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

作者: ruancw | 来源:发表于2018-06-01 17:47 被阅读388次

    前言:因公司项目重构需要,添加了二级菜单筛选及类似商品分类筛选的功能。上一篇文章介绍了带二级菜单的筛选控件,今天介绍类似流式布局的筛选控件,该控件继承自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);</pre>
                        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方法解决高版本显示的问题。

    注:Android自定义PopupWindow实现流式布局筛选控件(二)对本篇文章代码做了部分修改,修复了返回数据的bug,有兴趣的请移步链接地址文章。

    如有任何疑问,欢迎评论留言,谢谢!!!

    Android自定义带popupWindow的二级菜单筛选控件:
    https://blog.csdn.net/ruancw/article/details/80522881

    参考链接:https://www.2cto.com/kf/201804/735958.html

    相关文章

      网友评论

      • Jetkoal:作者大大,有demo吗,初级菜鸟看不太懂,目前也在做类似的功能,蟹蟹作者大大了
        ruancw:https://github.com/ruancw/FiltersPopup,可以先看看我的这个demo

      本文标题:Android自定义PopupWindow实现流式布局筛选控件(

      本文链接:https://www.haomeiwen.com/subject/mbuksftx.html