Android自定義PopupWindow實現(xiàn)流式布局篩選控件(一)

前言:因公司項目重構(gòu)需要,添加了二級菜單篩選及類似商品分類篩選的功能。上一篇文章介紹了帶二級菜單的篩選控件,今天介紹類似流式布局的篩選控件,該控件繼承自PopupWindow,并解決了高版本的顯示問題。本篇文章的控件只能實現(xiàn)單選效果,《Android實現(xiàn)類似京東篩選的流式布局標簽(可單選/多選)》通過自定義GridLayout實現(xiàn)可設置單選/多選的流式布局篩選效果,有興趣的可以移步到此文章查看。

先上效果圖:

效果圖

實現(xiàn)方式:

1.繼承自PopupWindow

2.linearLayout+GridLayout顯示數(shù)據(jù)

3.接口回調(diào),更新UI

1.定義PopupWindow內(nèi)部類Builder

注:Builder類用于設置PopupWindow的參數(shù)設置、popupWindow布局文件初始化、GridLayout布局的數(shù)據(jù)展示等

(1)定義參數(shù)設置方法

private Context context;//上下文對象 
private List<FilterModel> listData;//要顯示的數(shù)據(jù)集合 
private int columnCount;//列數(shù) 
private GridLayout mGridLayout;//用于顯示流式布局 
private LinearLayout llContent;//popupWindow的內(nèi)容顯示 
//背景顏色 
private int colorBg = Color.parseColor("#F8F8F8"); 
//默認的標題和標簽的大?。╯p) 
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; 
//當前加載的行數(shù) 
private int row = -1; 
private FlowPopupWindow mFlowPopupWindow; 
private List<String> labelLists=new ArrayList<>();
//保存選中的標簽數(shù)據(jù)   
public Builder(Context context) {
    this.context = context; 
}

/**  
* 設置數(shù)據(jù)集合 * 
*/ 
public void setValues(List<FilterBean> listData) {
    this.listData = listData; 
}

/**  
* 設置gridLayout的列數(shù) 
* @param columnCount 列數(shù)  
*/ 
public void setColumnCount(int columnCount){
    this.columnCount = columnCount; 
}

/**  
* 設置內(nèi)容區(qū)域的背景色 
* @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的數(shù)據(jù)
    setGridData(); 
}

A、初始化PopupWindow布局:initPopup方法

/**  
* 初始化PopupWindow的布局 
* @param rowCount 行數(shù)  
* @param columnCount 列數(shù)  
*/ 
private void initPopup(int rowCount,int columnCount){
    //初始化popupWindow的布局文件
    View view = LayoutInflater.from(context).inflate(R.layout.flow_popup,null);
    //主要用于設置數(shù)據(jù)顯示區(qū)域的背景色
    LinearLayout ll=view.findViewById(R.id.ll);
    //流布局數(shù)據(jù)展示控件
    mGridLayout=view.findViewById(R.id.grid_layout);
    //確定按鈕
    Button btnConfirm=view.findViewById(R.id.btn_confirm);
    //設置數(shù)據(jù)展示區(qū)域的背景色
    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) {
            //監(jiān)聽接口的數(shù)據(jù)回調(diào)方法
            flowPopupMonitor.setFlowPopupResult(labelLists);
            //隱藏popupWindow
            hidePopup();
        }
   });
  //設置mGridLayout的屬性參數(shù)
  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的數(shù)據(jù)展示

/**  
* 將數(shù)據(jù)設置給GridLayout 
*/ 
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void setGridData() {
    for (int i = 0; i < listData.size(); i++){
        //行數(shù)++
        ++row;
        //顯示每個條目類型的控件
        TextView tvType = new TextView(context);
        tvType.setText(listData.get(i).getTypeName());
        tvType.setTextColor(titleTextColor);
        tvType.setTextSize(titleTextSize);
        //配置列 第一個參數(shù)是起始列標 第二個參數(shù)是占幾列 title(篩選類型)應該占滿整行,so -> 總列數(shù)
        GridLayout.Spec columnSpec = GridLayout.spec(0,columnCount);
        //配置行 第一個參數(shù)是起始行標  起始行+起始列就是一個確定的位置
        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 數(shù)據(jù)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標簽的參數(shù)
        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));
                    //清空上次選中的狀態(tài)
                    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的屬性參數(shù)的方法

@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++){
        //計算當前類型之前的元素所占的個數(shù) title算一個
        index += listData.get(i).getTabs().size() + 1;
  }
    //加上當前 title下的索引
    FilterModel.TableMode tableModel = bean.getTab();
    index += bean.getTabs().indexOf(tableModel) + 1;
     return index; 
}

/**  
* 獲取內(nèi)容行數(shù) 
* @return 行數(shù)  
*/ 
private int getRowCount(){
    int row = 0;
    for (FilterBean bean : listData){
        //計算當前類型之前的元素所占的個數(shù) 標題欄也算一行
        row ++;
        int size = bean.getTabs().size();
        row += (size / columnCount) + (size % columnCount > 0 ? 1 : 0) ;
    }
    return row; 
}

(3)定義創(chuàng)建PopupWindow的方法

/**  
* 創(chuàng)建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; 
}

注:以上定義的方法均是在內(nèi)部類Builder中實現(xiàn)的

2.重寫構(gòu)造方法及showAsDropDown

(1)構(gòu)造方法

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的位置不會出現(xiàn)在相應控件的下方,而是在系統(tǒng)狀態(tài)欄的地方,有興趣的可以注掉重寫的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.定義接口回調(diào)方法

private static FlowPopupMonitor flowPopupMonitor;   
public interface FlowPopupMonitor{
    void setFlowPopupResult(List<String> filterResult); 
}

public void setFlowPopupMonitor(FlowPopupMonitor flowPopupMonitor){
    this.flowPopupMonitor=flowPopupMonitor; 
}

注:FlowPopupMonitor接口的實現(xiàn)方法setFlowPopupResult是在確定按鈕點擊事件中調(diào)用的。

4.控件使用

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void initFlowPopup() {
    FlowPopupWindow.Builder builder=new FlowPopupWindow.Builder(context);
    //設置數(shù)據(jù)
    builder.setValues(lists);
    //設置標簽字體的顏色,這里的color不是values目錄下的color,而是res文件夾下的color
    builder.setLabelColor(R.color.color_popup);
    //設置標簽的背景色
    builder.setLabelBg(R.drawable.flow_popup);
    //設置GridLayout的列數(shù)
    builder.setColumnCount(4);
    //初始化popupWindow的相關布局及數(shù)據(jù)展示
    builder.build();
    //創(chuàng)建popup
    mFixPopupWindow=builder.createPopup();
    //設置數(shù)據(jù)監(jiān)聽接口
    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

附上數(shù)據(jù)類的Bean:

/**  
* Created by ruancw on 2018/5/31. 
* 用于篩選的數(shù)據(jù)類 
*/   
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;
        }
    }
}

總結(jié):通過自定義PopupWindow的方式,使用LinearLayout+GridLayout的布局,實現(xiàn)了類似流布局的篩選控件,并通過重寫showAsDropDown方法解決高版本顯示的問題。

注:Android自定義PopupWindow實現(xiàn)流式布局篩選控件(二)對本篇文章代碼做了部分修改,修復了返回數(shù)據(jù)的bug,有興趣的請移步鏈接地址文章。

如有任何疑問,歡迎評論留言,謝謝?。?!

Android自定義帶popupWindow的二級菜單篩選控件:
https://blog.csdn.net/ruancw/article/details/80522881

參考鏈接:https://www.2cto.com/kf/201804/735958.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容