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/tech/app/5521.html

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

相关推荐

发表回复

登录后才能评论