前一段时间接到一个需求,点击弹出一个底部弹窗,弹窗的内容以列表的方式来呈现,同时包含选择条目和自适应以及固定高度,需求不难,就是一些细节的东西绕了一些弯路,现在做下汇总。
国际惯例,先看下效果图。
下面说下实现方法。
1.首先新建一个dialog的style。
<!--底部弹窗-->
<style name="DialogStyle" parent="android:style/Theme.Dialog">
<!--背景透明-->
<item name="android:windowBackground">@android:color/white</item>
<item name="android:windowContentOverlay">@null</item>
<!--浮于Activity之上-->
<item name="android:windowIsFloating">true</item>
<!--边框-->
<item name="android:windowFrame">@null</item>
<!--Dialog以外的区域模糊效果-->
<item name="android:backgroundDimEnabled">true</item>
<!--无标题-->
<item name="android:windowNoTitle">true</item>
<!--半透明-->
<item name="android:windowIsTranslucent">true</item>
</style>
1.首先新建一个继承Dialog的dialog文件,生成一个构造函数,把上面新建dialog的style引进去。
public ListViewDialog(Context context) {
super(context, R.style.AppTheme);
}
2.在构造方法中通过initViews()配置组件,主要是引入含listview的xml文件,并引用列表控件设置adapter适配器。
/**
* 配置组件
*/
private void initViews() {
View view= LayoutInflater.from(context).inflate(R.layout.dialog_main, null);
listview= view.findViewById(R.id.main_listview);
adapter= new MainAdapter(context, new ArrayList<MainBean>());
listview.setAdapter(adapter);
}
adapter适配器不复杂,item条目只有一个显示title的TextView,完整代码如下
/**
* 列表的adapter
*/
private class MainAdapter extends BaseAdapter {
private final Context context;
private List<MainBean> list;
private TextView titleTv;
public MainAdapter(Context context, List<MainBean> list) {
this.context= context;
this.list= list;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
View contentView= LayoutInflater.from(context).inflate(R.layout.item_main, null);
titleTv= contentView.findViewById(R.id.main_titleTv);
titleTv.setText(list.get(i).getTitle());
return contentView;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int i) {
return list.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
/**
* 设置新数据
* @param list
*/
public void setNewList(List<MainBean> list){
this.list= list;
notifyDataSetChanged();
}
}
3.在视图Activity里启动dialog,为了节省资源,我们做一个非空判断,不用每次都new一个dialog。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (dialog== null){
dialog= new ListViewDialog(MainActivity.this);
}
dialog.show();
}
4.启动app,数据应该出来了。
根据需求,此时我们把弹窗移到屏幕底部,并且调整一下宽度充满屏幕。
在构造函数调用一下如下方法。
/**
* 设置宽高度
*/
private void setHeight() {
Window window= getWindow();
DisplayMetrics metrics= context.getResources().getDisplayMetrics();
WindowManager.LayoutParams params= window.getAttributes();
params.width= metrics.widthPixels;
window.setAttributes(params);
window.setGravity(Gravity.BOTTOM);
}
安装app看下效果:
此时我们添加更多的测试数据,就会发现:
屏幕被全占了,原来这是自适应高度,弹窗的高度会随着条目的数量增加而增加,这种显示效果自然不是我们想要的,所以我们要对它的高度也做下限制,比如说最多不能高于屏幕高度的百分之六十。
回到setHeight方法中,增加以下这行
params.height= (int) (metrics.heightPixels* 0.6);
运行,看效果:
很棒吧,高度已经限制屏幕的60%了,那么问题又来了,如果条目过少呢
看吧,如果条目过少,高度也已固定,弹窗下方就会空出一大片空白区域,因此,我们应该设定当高度不足以到达屏幕高度的百分之六十时,我们就设置其高度自适应。
回到setHeight方法,在设置高度时增加这样的判断条件
if (window.getDecorView().getHeight() >= (int) (metrics.heightPixels * 0.6)) {
params.height = (int) (metrics.heightPixels * 0.6);
}
运行。
条目过少时,高度自适应。
条目过多时,高度固定屏幕高度的60%。
蛤?没生效。。。经过度娘查找,因为高度动态改变,随之会发生弹窗焦点失去的状况,此时我们只需要将sehHeight()方法放在这个方法中即可。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
setHeight(); //设置宽高度
}
运行,正常了。
接下来,我们给列表加上选择功能,当选中某个条目时,其会变成蓝色,并把选中条目的值返回。
打开列表bean,增加一个自定义字段isChecked;
private boolean isChecked;
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean checked) {
isChecked = checked;
}
打开adapter适配器,在getView()方法中增加以下代码。
titleTv.setTextColor(list.get(i).isChecked()? Color.BLUE: Color.GRAY);
即当某个条目的iChecked为true即选中时,文字改为蓝色,反之则为黑色。
增加控件的点击方法。
titleTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
for (int i = 0; i < list.size(); i++) {
list.get(i).setChecked(false);
}
list.get(i).setChecked(true);
notifyDataSetChanged();
}
});
代码的意思就是先将所有条目都的isChecked都设为false,再把点击条目的isChecked设为true,从而实现单选效果。
运行起来瞅瞅。
成功!现在我们把选中的值返回到主界面。
这里我们通过接口回调的方式来完成。
先把adapter的数值传回dialog中。
打开adapter,新建:
public interface onItemViewClickListener{
void onItemViewClick(String title);
}
titleTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
.........
if (listener!= null){
listener.onItemViewClick(list.get(i).getTitle());
}
}
});
同理打开dialog
public interface onItemViewClickListener{
void onItemViewClick(String title);
}
adapter = new MainAdapter(context, new ArrayList<MainBean>(), new MainAdapter.onItemViewClickListener() {
@Override
public void onItemViewClick(String title) {
if (listener!= null){
listener.onItemViewClick(title);
dismiss();
}
}
});
接下来打开视图Activity。在xml文件中放置一个用来显示返回文字的TextView,然后修改dialog的构造方法,通过回调获取传递的值。
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (dialog== null){
dialog= new ListViewDialog(MainActivity.this, new ListViewDialog.onItemViewClickListener() {
@Override
public void onItemViewClick(String title) {
titleTv.setText(title);
}
});
}
dialog.show();
}
});
运行起来~
成功!
至此全部完成,demo附上!