Android中DialogFragment的位置

70
我有一个DialogFragment来展示一个像弹出屏幕一样的View。 窗口总是出现在屏幕的中间。 是否有一种方法可以设置DialogFragment窗口的位置? 我已经查看了源代码,但尚未找到任何内容。
8个回答

112

尝试像这样:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    getDialog().getWindow().setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
    WindowManager.LayoutParams p = getDialog().getWindow().getAttributes();
    p.width = ViewGroup.LayoutParams.MATCH_PARENT;
    p.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
    p.x = 200;
    ...
    getDialog().getWindow().setAttributes(p);
    ...

或者使用getDialog().getWindow()的其他方法。

调用set-content后,请务必设置位置。


谢谢Steelight,我已经为你的答案点赞了。不过我有一个问题。你为什么要更改WindowManager.LayoutParam p?使用setGravity()似乎可以将窗口设置在顶部。当我测试或阅读有关setGravity的文档时,LayoutParams似乎没有任何负面影响。 - flobacca
问题是“如何设置位置”,我没有假设他的意思是“顶部”,所以我给出了设置多个属性以获得任何所需效果的示例。 - Steelight
1
+1 是因为这向我指出了正确的方向。最终对我有用的是:https://dev59.com/Dmkw5IYBdhLWcg3ws873#20419231 - Jonik
注意:在Android 22及以下版本中,对话框将浮动在系统导航按钮后面,这是由@Kyle R指出的,但他缺乏足够的声望来发表评论。 - Xenolion
谢谢。P.y对我来说可以改变对话框的垂直定位。我在onStart()中使用了这段代码,我认为这是一个完美的位置。 - Khizar Hayat

104

没错,在这个问题上我花了一两个小时的时间撞墙,最后终于把DialogFragment放在了我想要的位置。

我正在借鉴Steelight的回答。这是我找到的最简单、最可靠的方法。

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle b) {
    Window window = getDialog().getWindow();

    // set "origin" to top left corner, so to speak
    window.setGravity(Gravity.TOP|Gravity.LEFT);

    // after that, setting values for x and y works "naturally"
    WindowManager.LayoutParams params = window.getAttributes();
    params.x = 300;
    params.y = 100;
    window.setAttributes(params);

    Log.d(TAG, String.format("Positioning DialogFragment to: x %d; y %d", params.x, params.y));
} 

注意,params.widthparams.softInputMode(在Steelight的答案中使用)与此无关。


下面是一个更完整的示例。我真正需要的是将“确认框”DialogFragment与“源”或“父”视图对齐,在我的情况下是一个ImageButton。

我选择使用DialogFragment而不是任何自定义Fragment,因为它免费提供“对话框”功能(当用户在其外部单击时关闭对话框等)。

Example ConfirmBox
上面是“源”ImageButton(垃圾桶)的示例ConfirmBox

/**
 * A custom DialogFragment that is positioned above given "source" component.
 *
 * @author Jonik, https://dev59.com/Dmkw5IYBdhLWcg3ws873#20419231
 */
public class ConfirmBox extends DialogFragment {
    private View source;

    public ConfirmBox() {
    }

    public ConfirmBox(View source) {
        this.source = source;            
    }

    public static ConfirmBox newInstance(View source) {
        return new ConfirmBox(source);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setStyle(STYLE_NO_FRAME, R.style.Dialog);
    }


    @Override
    public void onStart() {
        super.onStart();

        // Less dimmed background; see https://dev59.com/cWYr5IYBdhLWcg3wQYFa
        Window window = getDialog().getWindow();
        WindowManager.LayoutParams params = window.getAttributes();
        params.dimAmount = 0.2f; // dim only a little bit
        window.setAttributes(params);

        // Transparent background; see https://dev59.com/ZWUp5IYBdhLWcg3w1qMi
        // (Needed to make dialog's alpha shadow look good)
        window.setBackgroundDrawableResource(android.R.color.transparent);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Put your dialog layout in R.layout.view_confirm_box
        View view = inflater.inflate(R.layout.view_confirm_box, container, false);

        // Initialise what you need; set e.g. button texts and listeners, etc.

        // ...

        setDialogPosition();

        return view;
    }

    /**
     * Try to position this dialog next to "source" view
     */
    private void setDialogPosition() {
        if (source == null) {
            return; // Leave the dialog in default position
        }

        // Find out location of source component on screen
        // see https://dev59.com/uHA65IYBdhLWcg3wzyH8#6798093
        int[] location = new int[2];
        source.getLocationOnScreen(location);
        int sourceX = location[0];
        int sourceY = location[1];

        Window window = getDialog().getWindow();

        // set "origin" to top left corner
        window.setGravity(Gravity.TOP|Gravity.LEFT);

        WindowManager.LayoutParams params = window.getAttributes();

        // Just an example; edit to suit your needs.
        params.x = sourceX - dpToPx(110); // about half of confirm button size left of source view
        params.y = sourceY - dpToPx(80); // above source view

        window.setAttributes(params);
    }

    public int dpToPx(float valueInDp) {
        DisplayMetrics metrics = getActivity().getResources().getDisplayMetrics();
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
    }
}

通过添加构造函数参数或必要的setter方法,可以很容易地使上述更普适化。(我的最终ConfirmBox有一个样式化的按钮(在一些边框内等),其文本和View.OnClickListener可以在代码中自定义。)


1
我找到了最棒的答案。 - Aiden Fry
2
你们太棒了--谢谢--我很好奇,为什么我不能在片段的XML中直接设置它?作为一个无知的iOS开发者,我不明白其中的问题是什么,感谢大家! :) - Fattie
非常好的回答!!在我的场景中,source按钮(下面是要显示对话框片段的位置)在屏幕方向改变时位置会发生变化,因此在更改方向时,对话框片段不会根据source的位置更新位置。您能否给我一些提示如何解决这个问题? - Dory
@alfacan 这个应该是被采纳的答案... 使用替代方案并不总是可接受的。 - Evren Ozturk
不错的解决方案,但问题是:如果我想让对话框填满视图下面的所有空间,我该怎么办? - An-droid
1
@Jonik,你太棒了。你的回答、个人资料和声望……都太棒了! - Edijae Crusar

6
我使用android.support.v7.app.AppCompatDialogFragment中的AppCompatDialogFragment,我想将对话框片段与屏幕底部对齐,并删除所有边框,特别是我需要设置内容宽度以匹配父级。
因此,我想从这个(黄色背景来自对话框片段的rootLayout):

src_img_1

获取这个:

src_img_2

以上的解决方案都没有起作用。所以,我做了这个:

fun AppCompatDialogFragment.alignToBottom() {
    dialog.window.apply {
        setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
        decorView.apply {

            // Get screen width
            val displayMetrics = DisplayMetrics().apply {
                windowManager.defaultDisplay.getMetrics(this)
            }

            setBackgroundColor(Color.WHITE) // I don't know why it is required, without it background of rootView is ignored (is transparent even if set in xml/runtime)
            minimumWidth = displayMetrics.widthPixels
            setPadding(0, 0, 0, 0)
            layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
            invalidate()
        }
    }
}

在我的情况下,为了创建全宽度对话框,需要使用 dialog?.window?.setLayout(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT) - Mikhail Sharin

5
您需要在DialogFragment中重写onResume()方法,如下所示:
@Override
public void onResume() {
    final Window dialogWindow = getDialog().getWindow();
    WindowManager.LayoutParams lp = dialogWindow.getAttributes();
    lp.x = 100;        // set your X position here
    lp.y = 200;        // set your Y position here
    dialogWindow.setAttributes(lp);

    super.onResume();
}

2
使这个工作的关键是设置一个起点。例如: Window window = getDialog().getWindow(); // 将“原点”设置为左上角 window.setGravity(Gravity.TOP|Gravity.LEFT);如果您不设置原点,则x和y位置将相对于屏幕中心。 - Kenneth Argo

4
getDialog().getWindow()在使用DialogFragment时不起作用,因为如果托管的活动不可见,getWindow()将返回null,而在基于片段的应用程序中,它是不可见的。尝试调用getAttributes()时,您将收到一个NullPointerException
我建议采用Mobistry的答案。如果您已经有了DialogFragment类,那么切换不会太难。只需用构造并返回PopupWindow的方法替换onCreateDialog方法即可。然后,您应该能够重用供给AlertDialog.builder.setView()的视图,并调用(PopupWindow object).showAtLocation()

PopupWindow 在旋转时会发生内存泄漏,而操作系统会为我们处理 DialogFragment - zaitsman

4

如果有人想要将对话框移到屏幕底部并使其全宽,这是我的解决方案:

    @Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    // the content
    final RelativeLayout root = new RelativeLayout(getActivity());
    root.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

    // creating the fullscreen dialog
    final Dialog dialog = new Dialog(getActivity());
    Window dialogWindow = dialog.getWindow();

    dialog.setContentView(root);
    dialogWindow.setBackgroundDrawable(new ColorDrawable(Color.WHITE));

    WindowManager.LayoutParams params = dialogWindow.getAttributes();

    params.width = ViewGroup.LayoutParams.MATCH_PARENT;
    params.height = ViewGroup.LayoutParams.WRAP_CONTENT;

    dialogWindow.setAttributes(params);
    dialogWindow.setGravity(Gravity.BOTTOM);


    return dialog;
}

2
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    Dialog dialog = new Dialog(mActivity, R.style.BottomDialog);
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // 
    dialog.setContentView(R.layout.layout_video_live);
    dialog.setCanceledOnTouchOutside(true);

    Window window = dialog.getWindow();
    assert window != null;
    WindowManager.LayoutParams lp = window.getAttributes();
    lp.gravity = Gravity.BOTTOM; //psotion
    lp.width = WindowManager.LayoutParams.MATCH_PARENT; // fuill screen
    lp.height = 280;
    window.setAttributes(lp);
    return dialog;
}

1
请指明通过执行此代码您实现了什么。它似乎并不是对所提出问题的完整回答。 - Juan José Melero Gómez
1
@JuanJoséMeleroGómez 对话框将位于窗口底部。 - Ven Ren

2
我正在使用 PopupWindow 类来实现此目的,因为您可以指定视图的位置和大小。

18
PopupWindow 不是一个 Fragment,所以这相当棘手。 - Glenn Maynard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接