专栏名称: 非非白
目录
相关文章推荐
禽报网  ·  行业\\2024年末家禽市场大揭秘:白羽鸡与 ... ·  11 小时前  
禽报网  ·  行业\\担心对中国征收进口关税 ... ·  昨天  
科幻世界SFW  ·  蛇年说蛇:人类文化中“神魔难辨”的蛇形象 ·  3 天前  
51好读  ›  专栏  ›  非非白

所见即所得 dialog

非非白  · 掘金  ·  · 2018-07-08 23:23

正文

阅读 29

所见即所得 dialog

我们平时在做普通页面的时候,当 app 运行起来时,所看到的界面,往往就是我们预览 xml 布局文件所看到的那样,即所见即所得。可是如果这些布局文件是放在 dialog 里展示的,情况就不一样了,往往要煞费苦心,才能得到我们想要的效果。

本文分享如何定义一个 BaseDialogFragment 来实现所见即所得的效果。文末还附有处理 dialog 中嵌套 Fragment,status bar 相关问题实践方案。

首先我们创建一个 DialogFragment

public class MyDialogFragment extends DialogFragment {
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_dialog, container, false);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<!--fragment_dialog.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:gravity="center"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Hello Dialog"
        android:textSize="32dp" />
</LinearLayout>

我们期待的结果是 dialog 充满整个屏幕,并且 Hello Dialog 这几个字居中显示,但实际的结果是:

我们在根布局设置的 layout 是 match_parent , 显示出来的结果却是 wrap_content

我们知道,一个 dialog 对应着一个 window, 而 window 有一个神奇的属性: isFloating 。当 isFloating 为 true 时,dialog contentView 的 宽高被重置为 wrap_content ,否者重置为 match_parent

让我们为 dialog 自定义主题,来改变这个值:

<!-- styles.xml -->
<resources>
    <style name="FullScreenDialog" parent="Theme.AppCompat.Dialog">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsFloating">false</item>
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>
</resources>

在 MyDialogFragment 中应用这个主题

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setStyle(DialogFragment.STYLE_NORMAL, R.style.FullScreenDialog);
}

跑起来看看:

果然实现全屏了,但是有两个问题,第一,状态栏变黑色了,第二,'Hello Dialog' 不见了。

第一个问题我们延后解决,先让我们来解决第二个问题。

目前,支持库中存在一个错误,导致样式无法正常显示。 可以通过使用 Activity 的 inflater 来解决这个问题,更改 onCreateView 方法:

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    return getActivity().getLayoutInflater().inflate(R.layout.fragment_dialog, container, false);
}

现在,Dialog 的样式能正常显示了,具体细节请参看 stackoverflow 这篇文章

现在让我们更改根布局的 margin, 留出一些空间来显示遮罩:

<?xml version="1.0" encoding="utf-8"?>
<!--fragment_dialog.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:layout_marginLeft="32dp"
    android:layout_marginRight="32dp"
    android:layout_gravity="center"
    android:background="#FFFFFF"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Hello Dialog"
        android:textSize="32dp" />

</LinearLayout>

跑起来看看,结果是令人失望的:

layout_height 不是 200dp, 而是 match_parent , 这是和 isFloating 这个属性密切相关的。

现在我们想到的一个解决方案是,在 LinearLayout 外再套一层 FrameLayout

<?xml version="1.0" encoding="utf-8"?>
<!--fragment_dialog.xml-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:layout_marginLeft="32dp"
        android:layout_marginRight="32dp"
        android:background="#FFFFFF"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="Hello Dialog"
            android:textSize="32dp" />

    </LinearLayout>
</FrameLayout>

现在,我们得到了预期效果:

但是点击遮罩,dialog 并没有消失,因为这个 dialog 实际上是全屏的,并没有 outside 可以点击。

现在开始封装我们的 BaseDialogFragment, 来解决以下问题:

  1. 不需要在正常的布局外再套一层 FrameLayout
  2. 点击遮罩,Dialog 可以消失
  3. 解决黑色状态栏的问题

定义 DialogFrameLayout,用来处理点击遮罩的问题

public class DialogFrameLayout extends FrameLayout {

    interface OnTouchOutsideListener {
        void onTouchOutside();
    }

    GestureDetector gestureDetector = null;

    OnTouchOutsideListener onTouchOutsideListener;

    public void setOnTouchOutsideListener(OnTouchOutsideListener onTouchOutsideListener) {
        this.onTouchOutsideListener = onTouchOutsideListener;
    }

    public DialogFrameLayout(@NonNull Context context) {
        super(context);
        commonInit(context);
    }

    private void commonInit(@NonNull Context context) {
        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDown(MotionEvent e) {
                return true;
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                Rect rect = new Rect();
                getHitRect(rect);
                int count = getChildCount();
                for (int i = count - 1; i > -1; i--) {
                    View child = getChildAt(i);
                    Rect outRect = new Rect();
                    child.getHitRect(outRect);
                    if (outRect.contains((int) e.getX(), (int) e.getY())) {
                        return false;
                    }
                }
                if (onTouchOutsideListener != null) {
                    onTouchOutsideListener.onTouchOutside();
                }
                return true;
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
    }
}

定义 DialogLayoutInflater, 让我们可以不再需要额外的 FrameLayout

public class DialogLayoutInflater extends LayoutInflater {

    private LayoutInflater layoutInflater;

    private DialogFrameLayout.OnTouchOutsideListener listener;

    public DialogLayoutInflater(Context context, LayoutInflater layoutInflater, DialogFrameLayout.OnTouchOutsideListener listener) {
        super(context);
        this.layoutInflater = layoutInflater;
        this.listener = listener;
    }

    @Override
    public LayoutInflater cloneInContext(Context context) {
        return layoutInflater.cloneInContext(context);
    }

    @Override
    public View inflate(int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        DialogFrameLayout dialogFrameLayout = new DialogFrameLayout(getContext());
        dialogFrameLayout.setOnTouchOutsideListener(listener);
        dialogFrameLayout.setLayoutParams(new ViewGroup.LayoutParams(-1, -1));
        layoutInflater.inflate(resource, dialogFrameLayout, true);
        return dialogFrameLayout;
    }
}

编写 BaseDialogFragment, 把一切连接起来:







请到「今天看啥」查看全文