前言:因公司项目重构需要,添加了二级菜单筛选及类似商品分类筛选的功能。上一篇文章介绍了带二级菜单的筛选控件,今天介绍类似流式布局的筛选控件,该控件继承自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
网友评论