前言:因公司項目重構(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