美文网首页
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