昨天去一个公司面试,要求实现一个弹窗并实现多选功能,其效果和京东的多级筛选类似。效果如下:
其实现的思路也比较简单,使用PopupWindow负责弹窗显示,PopupWindow主要由列表组成,而具体的子项可以使用GridView实现,我这里使用的的自定义流式布局,自定义FlowLayout的布局代码如下:
public class FlowLayout extends ViewGroup {
private static final int LEFT = -1;
private static final int CENTER = 0;
private static final int RIGHT = 1;
protected List> mAllViews = new ArrayList<>();
protected List mLineHeight = new ArrayList<>();
protected List mLineWidth = new ArrayList<>();
private int mGravity;
private List lineViews = new ArrayList<>();
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context) {
this(context, null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int width = 0;
int height = 0;
int lineWidth = 0;
int lineHeight = 0;
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
if (i == cCount - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
continue;
}
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
lp.leftMargin = 10;
lp.rightMargin = 10;
lp.topMargin = 10;
lp.bottomMargin = 10;
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
width = Math.max(width, lineWidth);
lineWidth = childWidth;
height += lineHeight;
lineHeight = childHeight;
} else {
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
if (i == cCount - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
}
setMeasuredDimension(
modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//
);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllViews.clear();
mLineHeight.clear();
mLineWidth.clear();
lineViews.clear();
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
int cCount = getChildCount();
for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE) continue;
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) {
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);
mLineWidth.add(lineWidth);
lineWidth = 0;
lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
lineViews = new ArrayList<>();
}
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin);
lineViews.add(child);
}
mLineHeight.add(lineHeight);
mLineWidth.add(lineWidth);
mAllViews.add(lineViews);
int left = getPaddingLeft();
int top = getPaddingTop();
int lineNum = mAllViews.size();
for (int i = 0; i < lineNum; i++) {
lineViews = mAllViews.get(i);
lineHeight = mLineHeight.get(i);
int currentLineWidth = this.mLineWidth.get(i);
switch (this.mGravity) {
case LEFT:
left = getPaddingLeft();
break;
case CENTER:
left = getPaddingLeft();
break;
case RIGHT:
left = width - currentLineWidth + getPaddingLeft();
break;
}
for (int j = 0; j < lineViews.size(); j++) {
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
}
top += lineHeight;
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
}
然后就是具体的代码实现,首先我们构建数据的结构:
public class FilterBean {
public String typeName;
public List children;
public static class Children {
public String value;
public int id;
public boolean isSelected;
}
}
然后是Adapter的内容。
public class PopListViewAdapter extends BasicAdapter {
private Activity context;
public PopListViewAdapter(Activity context) {
this.context = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = layoutInflater.inflate(R.layout.item_popwindows, null);
viewHolder = new ViewHolder(convertView);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
bindData(viewHolder, position);
return convertView;
}
private void bindData(ViewHolder viewHolder, int position) {
FilterBean bean = getItem(position);
if (bean==null)return;
viewHolder.tvTypeName.setText(bean.typeName);
setFlowLayoutData(bean.children, viewHolder.itemFlowLayout);
}
private void setFlowLayoutData(final List childrenList, final FlowLayout flowLayout) {
flowLayout.removeAllViews();
for (int x = 0; x < childrenList.size(); x++) {
CheckBox checkBox = (CheckBox) View.inflate(context, R.layout.item_checkbox, null);
checkBox.setText(childrenList.get(x).value);
//选中状态
if (childrenList.get(x).isSelected) {
checkBox.setChecked(true);
childrenList.get(x).isSelected=true;
} else {
checkBox.setChecked(false);
childrenList.get(x).isSelected=false;
}
// final int finalX = x;
// checkBox.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// refreshCheckBox(flowLayout, finalX, childrenList);
// }
// });
flowLayout.addView(checkBox);
}
}
/**
* 单选放开此代码
*/
private void refreshCheckBox(FlowLayout flowLayout, int finalX, List propBeenList) {
for (int y = 0; y < flowLayout.getChildCount(); y++) {
CheckBox radio = (CheckBox) flowLayout.getChildAt(y);
radio.setChecked(false);
propBeenList.get(y).isSelected=false;
if (finalX == y) {
radio.setChecked(true);
propBeenList.get(y).isSelected=true;
}
}
}
class ViewHolder {
@BindView(R.id.tv_type_name)
TextView tvTypeName;
@BindView(R.id.item_flowLayout)
FlowLayout itemFlowLayout;
ViewHolder(View view) {
ButterKnife.bind(this, view);
view.setTag(this);
}
}
}
对应的布局使用CheckBox实现,代码为:
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_selector"
android:button="@null"
android:gravity="center"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:textColor="@drawable/checkbox_text_color"
android:textSize="13sp">
CheckBox>
选择器的代码如下:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/btn_select" android:state_checked="true" />
<item android:drawable="@drawable/btn_select" android:state_selected="true" />
<item android:drawable="@drawable/btn_normal" />
selector>
btn_select.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#0079ff" />
<corners android:radius="2dp" />
shape>
btn_normal.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#dddddd" />
<corners android:radius="2dp" />
shape>
然后自定义一个PopupWindow
public class FilterPopWindow extends PopupWindow {
@BindView(R.id.listView)
MeasureListView listView;
@BindView(R.id.tv_reset)
TextView tvReset;
@BindView(R.id.tv_confirm)
TextView tvConfirm;
@BindView(R.id.view_null)
View viewNull;
private final Activity context;
private final List dictList;
private PopListViewAdapter adapter;
private OnConfirmClickListener onConfirmClickListener;
public FilterPopWindow(Activity context, List dictList) {
this.context = context;
this.dictList = dictList;
init();
}
private void init() {
initPop();
initView();
}
private void initPop() {
View popView = View.inflate(context, R.layout.layout_filter_pop, null);
ButterKnife.bind(this, popView);
setContentView(popView);
//LinearLayout.LayoutParams.MATCH_PARENT
setWidth(-1);
setHeight(-2);
setFocusable(true);
setOutsideTouchable(true);
setBackgroundDrawable(new ColorDrawable(0x33000000));
}
private void initView() {
tvReset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
for (int x = 0; x < dictList.size(); x++) {
List childrenBeen = dictList.get(x).children;
for (int y = 0; y < childrenBeen.size(); y++) {
if (childrenBeen.get(y).isSelected)
childrenBeen.get(y).isSelected = false;
}
}
adapter.notifyDataSetChanged();
}
});
tvConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onConfirmClickListener.onConfirmClick();
dismiss();
}
});
viewNull.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dismiss();
}
});
}
public void showAsDropDown(View view, int marginTop) {
adapter = new PopListViewAdapter(context);
listView.setAdapter(adapter);
adapter.setList(dictList);
showAsDropDown(view, 0, marginTop);
}
public void setOnConfirmClickListener(OnConfirmClickListener onConfirmClickListener) {
this.onConfirmClickListener = onConfirmClickListener;
}
public interface OnConfirmClickListener {
void onConfirmClick();
}
}
该类涉及到的布局文件为:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/gray_f3" />
<com.xzh.textureviewdemo.view.MeasureListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cacheColorHint="@android:color/transparent"
android:divider="@null"
android:scrollbars="none" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/gray_d8" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_reset"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/white"
android:gravity="center"
android:padding="5dp"
android:text="清空"
android:textColor="@color/black_33"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#3e9fed"
android:gravity="center"
android:padding="5dp"
android:text="确定"
android:textColor="#fefefe"
android:textSize="14sp" />
LinearLayout>
<View
android:id="@+id/view_null"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />
LinearLayout>
此处我们使用了MeasureListView,是重写ListView的OnMeasure函数,实现ListView高度的重新计算。
public class MeasureListView extends ListView {
private Context mContext;
public MeasureListView(Context context) {
this(context, null);
}
public MeasureListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MeasureListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mContext = context;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try {
Display display = ((Activity) mContext).getWindowManager().getDefaultDisplay();
DisplayMetrics d = new DisplayMetrics();
display.getMetrics(d);
//屏幕的一半
heightMeasureSpec = MeasureSpec.makeMeasureSpec(d.heightPixels /2 , MeasureSpec.AT_MOST);
} catch (Exception e) {
e.printStackTrace();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
最后是MainActivity的相关代码:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_title)
TextView tvTitle;
private List lists = new ArrayList<>();
private FilterPopWindow popWindow=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
init();
}
private void init() {
initData();
initView();
}
private void initData() {
String[] sexs = {"男", "女"};
String[] colors = {"红色", "浅黄色", "橙子色", "鲜绿色", "青色", "天蓝色", "紫色", "黑曜石色", "白色", "五颜六色"};
String[] company = {"阿里巴巴", "腾讯集团", "华为技术有限公司", "小米", "百度"};
FilterBean fb = new FilterBean();
fb.typeName="性别";
List childrenList = new ArrayList<>();
for (int x = 0; x < sexs.length; x++) {
Children cd = new Children();
cd.value=sexs[x];
childrenList.add(cd);
}
fb.children=childrenList;
lists.add(fb);
fb = new FilterBean();
fb.typeName="颜色";
childrenList = new ArrayList<>();
for (int x = 0; x < colors.length; x++) {
Children cd = new Children();
cd.value=colors[x];
childrenList.add(cd);
}
fb.children=childrenList;
lists.add(fb);
fb = new FilterBean();
fb.typeName="企业";
childrenList = new ArrayList<>();
for (int x = 0; x < company.length; x++) {
Children cd = new Children();
cd.value=company[x];
childrenList.add(cd);
}
fb.children=childrenList;
lists.add(fb);
}
private void initView() {
popWindow=new FilterPopWindow(this,lists);
popWindow.setOnConfirmClickListener(new FilterPopWindow.OnConfirmClickListener() {
@Override
public void onConfirmClick() {
StringBuilder sb = new StringBuilder();
for (FilterBean fb : lists) {
List cdList = fb.children;
for (int x = 0; x < cdList.size(); x++) {
Children children = cdList.get(x);
if (children.isSelected)
sb.append(fb.typeName + ":" + children.value + ";");
}
}
if (!TextUtils.isEmpty(sb.toString()))
Toast.makeText(MainActivity.this, sb.toString(), Toast.LENGTH_LONG).show();
}
});
}
@OnClick(R.id.tv_title)
public void onViewClicked() {
if (popWindow!=null)
popWindow.showAsDropDown(tvTitle,10);
}
}
好了就到这里。。。