DialogFragment使用的不便之处
1.布局文件的属性失效 。不管是warp还是match 对DialogFragment并不生效。
2.最好还可以像popwindow那样依附某个view。
那让我们来解决上边的几个问题吧 --------本文并不不涉及源码解析
1.布局属性失效
首先随便写一个布局 ,然后加载显示
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="300dp"
android:background="@color/white"
android:layout_height="200dp">
<TextView
android:id="@+id/tv_dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="dialog text"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
效果如下
![](https://img.haomeiwen.com/i7806742/4a752366d1a1ade2.png)
打开layout inspector查看一下
![](https://img.haomeiwen.com/i7806742/3b5db09f6f88cd56.png)
可以看到是依附于一个DectorView中的
那么如果我把布局view自己加载然后添加到到DecorView中 ,岂不是就能解决这个问题。
这里就要先大致的说一下DialogFragment的加载了
如果onCreateView方法不为空, 那么就会调用dialog的setContentView 将其设置到dialog中,
而dialog则通过onCreateDialog方法来创建。
那么我的思路就是先重写onCreateDialog方法,然后将onCreateView返回空值,
再将dialog的Window和DecorView的背景设为透明。
最后获取我们的布局属性,并设置给我们的window。
上代码
public class Test1FreeDialog extends DialogFragment {
private Dialog dialog;
private View rootView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return null;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
dialog=new Dialog(getActivity());
rootView= LayoutInflater.from(getActivity()).inflate(getLayoutId(), (ViewGroup) dialog.getWindow().getDecorView(),false);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);//设置背景透明
dialog.getWindow().getDecorView().setBackgroundResource(android.R.color.transparent); //设置背景
ViewGroup view= (ViewGroup) dialog.getWindow().getDecorView();
view.removeAllViews();//不要其附属的子FrameLayout
dialog.getWindow().setGravity(Gravity.CENTER);//设置居中
FrameLayout.LayoutParams params= (FrameLayout.LayoutParams) rootView.getLayoutParams();
//设置window布局的 如果为固定值的需要加上margin数值
dialog.getWindow().setLayout(params.width>0 //如果大于0代表xml文件中是固定值
?params.width+params.leftMargin+params.rightMargin
:params.width
,params.height>0
?params.height+params.bottomMargin+params.topMargin
:params.height);
DisplayMetrics screen=getWindowSize();//屏幕宽高
int withSpec=0, heightSpec=0;
switch (params.width){
case ViewGroup.LayoutParams.MATCH_PARENT:
withSpec = View.MeasureSpec.makeMeasureSpec(screen.widthPixels, View.MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
withSpec = View.MeasureSpec.makeMeasureSpec(screen.widthPixels, View.MeasureSpec.AT_MOST);
break;
default: //固定值
withSpec = View.MeasureSpec.makeMeasureSpec(params.width, View.MeasureSpec.EXACTLY);
break;
}
switch (params.height){
case ViewGroup.LayoutParams.MATCH_PARENT:
heightSpec = View.MeasureSpec.makeMeasureSpec(screen.heightPixels, View.MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
heightSpec = View.MeasureSpec.makeMeasureSpec(screen.heightPixels, View.MeasureSpec.AT_MOST);
break;
default: //固定值
heightSpec = View.MeasureSpec.makeMeasureSpec(params.height, View.MeasureSpec.EXACTLY);
break;
}
//手动measure获取view大小 用于后续位置调整
rootView.measure(withSpec, heightSpec);
view.addView(rootView);//添加到DecorView
return dialog;
}
int getLayoutId(){
return R.layout.dialog_test; //随便测试一下
}
/**
* 获取屏幕宽高
* @return
*/
private DisplayMetrics getWindowSize(){
DisplayMetrics outMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
return outMetrics;
}
}
测试一下实际效果
![](https://img.haomeiwen.com/i7806742/fe17c15a4f7a85af.png)
再改一下布局属性试试
android:layout_width="match_parent"
![](https://img.haomeiwen.com/i7806742/9265ade68135cd58.png)
那么新的问题来了 如果我的dialog view需要更改呢?是一个list列表呢?
解决方法也很简单
我们需要在测量rootview之前设置好view里边的各项属性
上测试代码 首先是一个很简单的布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:background="@color/white">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
item布局
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_name"
android:gravity="center"
android:text="sss"
android:padding="10dp"
android:background="@color/white"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
adapter
public class TestAdapter extends ListAdapter<String, TestAdapter.ViewHolder> {
static DiffUtil.ItemCallback<String> diffCallback=new DiffUtil.ItemCallback<String>(){
@Override
public boolean areItemsTheSame(@NonNull String oldItem, @NonNull String newItem) {
return oldItem.equals(newItem);
}
@Override
public boolean areContentsTheSame(@NonNull String oldItem, @NonNull String newItem) {
return oldItem.equals(newItem);
}
};
public TestAdapter() {
super(diffCallback);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.dialog_item_text,parent,false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.textView.setText(getItem(position));
}
class ViewHolder extends RecyclerView.ViewHolder{
TextView textView;
public ViewHolder (View view)
{
super(view);
textView = (TextView) view.findViewById(R.id.tv_name);
}
}
}
在rootview inflate之后增加一个onCreateView方法
void onCreateView(Bundle savedInstanceState){
RecyclerView recyclerView=rootView.findViewById(R.id.rv_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
TestAdapter testAdapter=new TestAdapter();
List<String> list=new ArrayList<>();
for(int i=0;i<5;i++){
list.add("第"+i+"条数据");
}
testAdapter.submitList(list);
recyclerView.setAdapter(testAdapter);
}
完成再看一下效果
![](https://img.haomeiwen.com/i7806742/5bc706f90a491721.png)
ojbk 完全符合预期 接下来解决第二个问题
2 依附于某个view
我们经常会有这样的需求: 在某个view下显示一个列表或者其他的东西
![](https://img.haomeiwen.com/i7806742/57dcba0541a952ce.png)
PopupWindow确实可以解决这个问题。那DialogFragment 怎么解决呢?
先思考一下
首先我们需要依附的view来计算在屏幕上的坐标,然后再根据我们要显示的view宽高计算出坐标并显示。
然后去掉dialog的背景,这样就可以完成类似于PopupWindow的效果了。
这里提一下gravity是一个8位数的二进制 。 前四位为Y轴的gravity, 后四位为X轴的gravity
参考的android的Gravity 具体的可以自己查询
那么上一部分代码 非完整
private void setAnchorView( View anchorView,int pxElevation) {
int pxYOffset=dip2px(yOffset);
int pxXOffset=dip2px(xOffset);
dialog.getWindow().setGravity(Gravity.TOP | Gravity.LEFT);//必须设置 把坐标原点放到左上角
anchorView.getLocationOnScreen(location);
//获取window的attributes用于设置位置
WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
// 获取rootView的高宽
final int rHeight = rootView.getMeasuredHeight();
final int rWidth = rootView.getMeasuredWidth();
int yGravity = gravity & 0xf0;//获取前4位 得到y轴
int xGravity = gravity & 0x0f;//获取后4位 得到x轴
int x = 0, y = 0;
//处理y轴
switch (yGravity) {
case TOP:
y = location[1] + pxYOffset - getStatusBarHeight() - rHeight;
break;
case CENTER_VERTICAL:
y = location[1] + pxYOffset - getStatusBarHeight() - (rHeight - anchorView.getHeight()) / 2;
break;
default://BOTTOM
y = location[1] + pxYOffset - getStatusBarHeight() + otherView.getHeight();
break;
}
//处理x轴
switch (xGravity) {
case LEFT:
x = location[0] + pxXOffset - rWidth;
break;
case RIGHT:
x = location[0] + pxXOffset + anchorView.getWidth();
break;
default: //center_horizontal
x = location[0] + pxXOffset - (rWidth - anchorView.getWidth()) / 2;
break;
}
//设置位置
params.x=x;
params.y=y;
}
/**
* 获取状态栏高度
* @return
*/
private int getStatusBarHeight() {
//如果是全屏了 则返回0
if ( (getActivity().getWindow().getAttributes().flags & FLAG_FULLSCREEN)
== FLAG_FULLSCREEN) {
return 0;
}
//获取状态栏高度
Resources resources = getActivity().getResources();
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
int height = resources.getDimensionPixelSize(resourceId);
return height;
}
写到这其实核心问题已经解决了 那么我们接下来就封装一下
例如是否可以取消,dialog是否带阴影,dialog遮罩层透明度,dialog使用建造者模式等等
完整代码移步https://github.com/lujing5873/FreeDialog
如何使用
首先导入
1.去github拷贝源码
2.项目gradle下 添加jitpack仓库
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
module.gradle下添加
dependencies {
implementation 'com.github.lujing5873:FreeDialog:V1.0.0'
}
使用
1.继承FreeCusDialog 重写getLayoutId 和 createView方法即可
public class ListDialog extends FreeCusDialog {
@Override
public int getLayoutId() {
return R.layout.dialog_list;
}
@Override
protected void createView(Bundle savedInstanceState) {
RecyclerView recyclerView=findViewById(R.id.rv_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
TestAdapter testAdapter=new TestAdapter();
List<String> list=new ArrayList<>();
for(int i=0;i<5;i++){
list.add("第"+i+"条数据");
}
testAdapter.submitList(list);
recyclerView.setAdapter(testAdapter);
}
}
activity中直接调用
findViewById(R.id.tv_list).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new ListDialog()
.setCancel(false) //不可取消 还有其他属性自行查看
.show(getSupportFragmentManager(),"list");
}
});
2.直接在activity中使用FreeDialog
findViewById(R.id.tv_list_free).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new FreeDialog.Builder(R.layout.dialog_list) {
@Override
public void onCreateView(Bundle savedInstanceState) {
RecyclerView recyclerView=getViewById(R.id.rv_list);
recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
TestAdapter testAdapter=new TestAdapter();
List<String> list=new ArrayList<>();
for(int i=0;i<5;i++){
list.add("第"+i+"条数据");
}
testAdapter.submitList(list);
recyclerView.setAdapter(testAdapter);
}
}.setAnchor(findViewById(R.id.tv_list_free),0,0).setGravity(Gravity.LEFT)
.show(getSupportFragmentManager(),"list_free");
}
});
使用注意
layout文件必须加backgroud 因为上层view是透明的
待解决问题
1.如果在dialog上再弹出一个dialog, 那么上一层dialog遮罩层的设置莫名的会影响到下层dialog 查阅了很多资料搞不明白orz 应该和window有关
2.目前没有写任何动画啥的 以后再添加
结尾
如果有bug欢迎反馈 项目基于androidX by 浓厚纯正500
网友评论