底部PopupWindow+流布局的实现

本文介绍如何在Android项目中使用PopupWindow实现底部流式布局,对比了PopupWindow与AlterDialog的区别,并提供了具体的代码实现示例。
摘要由CSDN通过智能技术生成


PopupWindow简介 

1.在我门日常项目实现中,对话框一般有两种,一种是AlterDialog还有一种就是PopupWindow,那么 他俩的区别主要在于:

(1)AlterDialog的位置是固定的,而PopupWindow的位置是随意的。

(2)AlterDialog不会阻塞线程,而PopupWindow会阻塞线程。

2.那么如何使用 我们在下面会给出,就不过多介绍了,直接在项目中看:

图所示 这就是我们要实现的功能,这功能我们经常会在淘宝上看到。

代码实现

1.主界面的XML文件:

<Button
        android:id="@+id/show_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="弹出PopupWindow"/>
2.popwupWindow的XML文件:

<strong><span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#000000ff">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:background="#ffffff"
            android:layout_marginTop="20dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:text="商品名称"
                android:layout_marginTop="20dp"
                android:layout_marginLeft="200dp"
                android:textColor="#000000"/>

        </RelativeLayout>

        <ImageView
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:src="@mipmap/ic_launcher"
            android:scaleType="fitXY"
            android:layout_marginLeft="20dp"/>

    </RelativeLayout>

    <LinearLayout
        android:id="@+id/layout_spc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:background="#ffffff"></LinearLayout>

    <TextView
        android:id="@+id/ok_button"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="#ff0000"
        android:textColor="#ffffff"
        android:textSize="18sp"
        android:text="确定"
        android:gravity="center"/>

</LinearLayout></span></strong>
3.我讲效果图中的数据写在了一个assets文件中,那么 读取时 我们就需要通过assetsManager来进行读取:

<strong>public class Utils {

    /**
     * 读取assets文件
     * @param context
     * @param fileName
     * @return
     */
    public static String getStringFromAssets(Context context, String fileName) {
        AssetManager manager = context.getResources().getAssets();
        BufferedInputStream bis = null;
        try {
            InputStream is = manager.open(fileName);
            bis = new BufferedInputStream(is);
            byte[] data = new byte[1024];
            int len=0;
            StringBuffer buffer = new StringBuffer();
            while ((len = bis.read(data)) != -1) {
                buffer.append(new String(data, 0, len));
            }
            return buffer.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}</strong>
<strong>
</strong>
<span style="white-space:pre">	</span>4.我讲数据进行了封装
<strong><span style="font-size:18px;">public class DataInfo {

    private List<Specification> specification;


    public void setSpecification(List<Specification> specification) {
        this.specification = specification;
    }
    public List<Specification> getSpecification() {
        return specification;
    }

}</span></strong>
<strong><span style="font-size:18px;">public class Specification {


    /**
     * attr_type : 1
     * name : 包装
     * value : [{"label":"500g","price":"10","format_price":"¥10.00","id":"1195"},{"label":"250g","price":"5","format_price":"¥5.00","id":"1198"}]
     */

    private String attr_type;
    private String name;

    private List<Value> value;

    public String getAttr_type() {
        return attr_type;
    }

    public void setAttr_type(String attr_type) {
        this.attr_type = attr_type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Value> getValue() {
        return value;
    }

    public void setValue(List<Value> value) {
        this.value = value;
    }

}</span></strong>
<strong><span style="font-size:18px;">public class  Value {


    /**
     * label : 250g
     * price : 5
     * format_price : ¥5.00
     * id : 1198
     */

    private String label;
    private String price;
    private String format_price;
    private String id;

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    public String getFormat_price() {
        return format_price;
    }

    public void setFormat_price(String format_price) {
        this.format_price = format_price;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}</span></strong>

5.接下来是我自定义的流布局实现,在代码中已经给出了详细的注解,所以在这里我就不过多的重复了:

<strong><span style="font-size:18px;">public class FlowLayout extends ViewGroup {

    // 存储所有行的View,按行记录。每行存在一个List集合中。
    private List<List<View>> mAllViews = new ArrayList<List<View>>();

    // 记录每一行的最大高度
    private List<Integer> mLineHeight = new ArrayList<Integer>();

    public FlowLayout(Context context) {
        super(context);
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * @param
     * @return
     */
    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

    /**
     * @param attrs
     * @return
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    /**
     * @return
     */
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    /**
     * @param widthMeasureSpec   宽度的尺寸说明
     * @param heightMeasureSpec 高度的尺寸说明
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // 定义一个不断累加的总高度,当布局高度设置为wrap_content时使用该高度。一般都是将高度设置为wrap_content。
        int totalHeight = 0;
        // 定义一个不断累加的变量。存放当前行控件的宽度总和
        int lineWidth = 0;
        // 获取当前行控件中最高的那个控件的高度
        int lineHeight = 0;
        // 遍历每个子控件
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            // 获取每个子控件的宽度和高度
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            // 得到每个子控件的布局参数
            MarginLayoutParams lp = (MarginLayoutParams) childView
                    .getLayoutParams();
            // 获取当前子控件的实际宽度和高度
            int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

            if (lineWidth + childWidth > widthSize) {
                lineWidth = childWidth; // 重新开启新行,开始记录
                // 叠加当前高度,
                totalHeight += lineHeight;
                // 开启记录下一行的高度
                lineHeight = childHeight;
            } else {
                // 累加lineWidth,lineHeight取最大高度
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }
            // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较
            if (i == childCount - 1) {
                totalHeight += lineHeight;
            }
            setMeasuredDimension(widthSize,
                    (heightMode == MeasureSpec.EXACTLY) ? heightSize : totalHeight);
        }
    }

    /**
     * @param changed
     *            该参数指出当前ViewGroup的尺寸或者位置是否发生了改变
     * @param left
     *            top right bottom 当前ViewGroup相对于其父控件的坐标位置
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        mAllViews.clear();
        mLineHeight.clear();
        // 获取当前View的宽度
        int layoutWidth = getWidth();

        // 定义每行的宽度和高度
        int lineWidth = 0;
        int lineHeight = 0;
        // 存储每一行所有的childView
        List<View> lineViews = new ArrayList<View>();
        // 遍历所有的子控件
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
            int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int childHeight = childView.getMeasuredHeight() + lp.leftMargin + lp.rightMargin;

            // 如果已经需要换行
            if (lineWidth + childWidth > layoutWidth) {
                // 记录这一行所有的View以及最大高度
                mLineHeight.add(lineHeight);
                // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
                mAllViews.add(lineViews);
                lineWidth = 0;// 重置行宽
                lineViews = new ArrayList<View>();
            }
            // 如果不需要换行,则累加
            lineWidth += childWidth;
            lineHeight = Math.max(lineHeight, childHeight);
            lineViews.add(childView);
        }
        // 记录最后一行
        mLineHeight.add(lineHeight);
        mAllViews.add(lineViews);

        int mLeft = 0;
        int mTop = 0;
        // 得到总行数
        int lineNums = mAllViews.size();
        for (int i = 0; i < lineNums; i++) {
            // 每一行的所有的views
            lineViews = mAllViews.get(i);
            // 当前行的最大高度
            lineHeight = mLineHeight.get(i);

            // 遍历当前行所有的View
            for (int j = 0; j < lineViews.size(); j++) {
                View childView = lineViews.get(j);
                if (childView.getVisibility() == View.GONE) {
                    continue;
                }
                MarginLayoutParams lp = (MarginLayoutParams) childView
                        .getLayoutParams();

                // 计算childView的left,top,right,bottom
                int left_child = mLeft + lp.leftMargin;
                int top_child = mTop + lp.topMargin;
                int right_child = left_child + childView.getMeasuredWidth();
                int bottom_child = top_child + childView.getMeasuredHeight();
                // 给每个子控件指定位置
                childView.layout(left_child, top_child, right_child, bottom_child);
                // 指定每个子控件的起始位置
                mLeft += childView.getMeasuredWidth() + lp.rightMargin + lp.leftMargin;
            }
            mLeft = 0;
            mTop += lineHeight;
        }
    }
}</span></strong>

6.在PopView中声明我们之前自定义的流布局,并加载数据:
<strong><span style="font-size:18px;">public abstract class PopupView extends RelativeLayout {

    private Context mContext;

    //动态流布局的父布局
    private LinearLayout mSpecLayout;

    //每行选中的TextView
    private List<View> mSelectedViews;

    //选中的Button
    private TextView mOkButton;

    public PopupView(Context context) {
        this(context, null);
    }

    public PopupView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;

        LayoutInflater.from(context).inflate(R.layout.view_specification, this, true);
        mSpecLayout = (LinearLayout) findViewById(R.id.layout_spc);
        mOkButton = (TextView) findViewById(R.id.ok_button);
        mOkButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                List<String> selectIndex = new ArrayList<String>();
                for (View view : mSelectedViews) {
                    selectIndex.add((String) view.getTag());
                }
                OKSelect(selectIndex);
            }
        });
    }

    //加载数据
    public void setData(DataInfo data){

        mSelectedViews = new ArrayList<>();

        for (int i=0; i<data.getSpecification().size(); i++) {
            Specification scc = data.getSpecification().get(i);
            //横线
            View lineView = new View(mContext);
            lineView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 1));
            lineView.setBackgroundColor(Color.GRAY);
            mSpecLayout.addView(lineView);

            //标题
            TextView nameTv = new TextView(mContext);
            nameTv.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            nameTv.setTextColor(Color.RED);
            nameTv.setText(scc.getName());
            nameTv.setTextSize(20);
            nameTv.setPadding(10,10,10,10);
            mSpecLayout.addView(nameTv);

            //流布局
            FlowLayout flowLayout = new FlowLayout(mContext);
            for (int j=0; j<scc.getValue().size(); j++) {
                Value value = scc.getValue().get(j);
                TextView tv = new TextView(mContext);
                LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
                params.setMargins(10,10,10,10);
                tv.setLayoutParams(params);
                tv.setText(value.getLabel());
                tv.setTextSize(16);
                tv.setPadding(10,10,10,10);
                tv.setBackgroundResource(R.drawable.flag_04);
                if (j == 0) {
                    tv.setBackgroundResource(R.drawable.flag_04_p);
                    tv.setTag(i + "#" + j); //保存位置,行和列
                    //初始化都放入0
                    mSelectedViews.add(tv);
                }
                flowLayout.addView(tv);

                tv.setTag(i + "#" + j); //保存位置,行和列
                tv.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        String indexStr = (String) v.getTag();
                        String[] index = indexStr.split("#");
                        int row = Integer.valueOf(index[0]);
                        //改变背景
                        View oldView = mSelectedViews.get(row);
                        oldView.setBackgroundResource(R.drawable.flag_04);
                        v.setBackgroundResource(R.drawable.flag_04_p);
                        //将选中位置保存
                        mSelectedViews.set(row, v);
                    }
                });
            }
            mSpecLayout.addView(flowLayout);
        }
    }

    public abstract void OKSelect(List<String> selectIndex);
}</span></strong>

7.最后 在主界面的实现过程:
<strong><span style="font-size:18px;">public class MainActivity extends Activity { 

    private Button mShowButton;

    private DataInfo mData;

    private PopupWindow mPopupWindow;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //从Assets获得数据并解析
        getData();

        mShowButton = (Button) findViewById(R.id.show_button);
        mShowButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                initPopupWindow();
                mPopupWindow.showAtLocation(v, Gravity.BOTTOM, 0, 0);
            }
        });


    }

    private void getData() {
        //从Assets获得数据
        String txt = Utils.getStringFromAssets(getApplicationContext(), "data.json");
        if (!TextUtils.isEmpty(txt)) {
            //解析数据
            Gson gson = new Gson();
            mData = gson.fromJson(txt, DataInfo.class);
        }
    }

    private void initPopupWindow() {

        if (mPopupWindow == null) {
            PopupView pv = new PopupView(this) {
                @Override
                public void OKSelect(List<String> selectIndex) { //点击确定的处理
                    //关闭PopupWindow
                    mPopupWindow.dismiss();
                    mPopupWindow = null;
                    String select = "";
                    for (String s : selectIndex) {
                        select += s;
                        select += "\n";
                    }
                    Toast.makeText(MainActivity.this, select, Toast.LENGTH_SHORT).show();
                }
            };
            pv.setData(mData);
            mPopupWindow = new PopupWindow(pv, ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            // 设置popWindow的显示和消失动画
            mPopupWindow.setAnimationStyle(R.style.mypopwindow_anim_style);
            // 设置触摸外面时消失
            mPopupWindow.setBackgroundDrawable(new ColorDrawable());
            mPopupWindow.setOutsideTouchable(true);
        }
    }

}</span></strong>
还有一些小细节就是popup的出现及隐藏的效果:
<strong><span style="font-size:18px;"><set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate
        android:duration="1000"
        android:fromYDelta="0"
        android:toYDelta="50%p" />

    <alpha
        android:duration="1000"
        android:fromAlpha="1.0"
        android:toAlpha="0.0" />
</set></span></strong>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="1000"
        android:fromYDelta="100%p"
        android:toYDelta="0" />

    <alpha
        android:duration="1000"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
</set>

自定义点击效果:

<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="#E7E7E7" />
    <corners android:radius="30dp" />

    <padding
        android:bottom="2dp"
        android:left="10dp"
        android:right="10dp"
        android:top="2dp" />
</shape>

<strong><span style="font-size:18px;"><shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="#FAA35C" />
    <corners android:radius="30dp" />

    <padding
        android:bottom="2dp"
        android:left="10dp"
        android:right="10dp"
        android:top="2dp" />
</shape>
</span></strong>

整个功能的效果就全部实现了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值