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> 整个功能的效果就全部实现了!