Android 多级弹窗实现详解手机开发

昨天去一个公司面试,要求实现一个弹窗并实现多选功能,其效果和京东的多级筛选类似。效果如下:
这里写图片描述
其实现的思路也比较简单,使用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<List<View>> mAllViews = new ArrayList<>(); 
protected List<Integer> mLineHeight = new ArrayList<>(); 
protected List<Integer> mLineWidth = new ArrayList<>(); 
private int mGravity; 
private List<View> 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> children; 
public static class Children { 
public String value; 
public int id; 
public boolean isSelected; 
} 
}

然后是Adapter的内容。

public class PopListViewAdapter extends BasicAdapter<FilterBean>  { 
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<FilterBean.Children> 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<FilterBean.Children> 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实现,代码为:

<?xml version="1.0" encoding="utf-8"?> 
<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> 

选择器的代码如下:

<?xml version="1.0" encoding="utf-8"?> 
<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<FilterBean> dictList; 
private PopListViewAdapter adapter; 
private OnConfirmClickListener onConfirmClickListener; 
public FilterPopWindow(Activity context, List<FilterBean> 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<FilterBean.Children> 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(); 
} 
}

该类涉及到的布局文件为:

<?xml version="1.0" encoding="utf-8"?> 
<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<FilterBean> 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<Children> 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<Children> 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); 
} 
}

好了就到这里。。。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/5521.html

(0)
上一篇 2021年7月16日
下一篇 2021年7月16日

相关推荐

发表回复

登录后才能评论