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