美文网首页
FreeDialog:打造一个好用万能的DialogFragme

FreeDialog:打造一个好用万能的DialogFragme

作者: 果汁_de5e | 来源:发表于2021-01-25 11:38 被阅读0次

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>

效果如下


image.png

打开layout inspector查看一下


image.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;
    }
}

测试一下实际效果


image.png

再改一下布局属性试试
android:layout_width="match_parent"


image.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);
    }

完成再看一下效果


image.png

ojbk 完全符合预期 接下来解决第二个问题

2 依附于某个view

我们经常会有这样的需求: 在某个view下显示一个列表或者其他的东西


image.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

相关文章

网友评论

      本文标题:FreeDialog:打造一个好用万能的DialogFragme

      本文链接:https://www.haomeiwen.com/subject/yjvbzktx.html