AlertDialog如何铺满,有个默认的padding

默认的Dialog

布局,就一个简单的textview,弄个背景,好区分

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:background="#88888888"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

默认的显示成这样,可以看到宽度默认是有限制的,我们的match是无效的,


image.png
修改宽度大家应该知道的,下边的代码
        override fun onStart() {
            super.onStart()
            val window = dialog.window
            val param = window!!.attributes
            param.width = resources.displayMetrics.widthPixels
            window.attributes = param
        }

现在长这样,可以看到,两边还是有边距的,宽度设置和屏幕一样没用的,其实吧,我个人觉得这样挺好的,你是个dialog,留个边距才是合理的,铺满了咋还能看出是dialog。
可实际中,ui可能就需要你不准留间距,那就只好想办法解决了


image.png

我去看看源码有没有设置这个的地方

看AlertDialog里边可以发现它是用AlertController来生成的,这个类跳不过去,自己去源码下边找下,然后打开看了下,view的添加过程

构造方法

可以看到里边加载了好多布局,看名字大概就知道干啥的了,我们普通的布局就是这个了布局alert_dialog

    public AlertController(Context context, DialogInterface di, Window window) {
        mContext = context;
        mDialogInterface = di;
        mWindow = window;
        mHandler = new ButtonHandler(di);

        final TypedArray a = context.obtainStyledAttributes(null,
                R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);

        mAlertDialogLayout = a.getResourceId(
                R.styleable.AlertDialog_layout, R.layout.alert_dialog);
        mButtonPanelSideLayout = a.getResourceId(
                R.styleable.AlertDialog_buttonPanelSideLayout, 0);
        mListLayout = a.getResourceId(
                R.styleable.AlertDialog_listLayout, R.layout.select_dialog);

        mMultiChoiceItemLayout = a.getResourceId(
                R.styleable.AlertDialog_multiChoiceItemLayout,
                R.layout.select_dialog_multichoice);
        mSingleChoiceItemLayout = a.getResourceId(
                R.styleable.AlertDialog_singleChoiceItemLayout,
                R.layout.select_dialog_singlechoice);
        mListItemLayout = a.getResourceId(
                R.styleable.AlertDialog_listItemLayout,
                R.layout.select_dialog_item);

        a.recycle();
    }
alert_dialog.xml

这个布局到源码platforms里对应的版本里可以找到,自己随便找个版本吧

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/parentPanel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:paddingTop="9dip"
    android:paddingBottom="3dip"
    android:paddingStart="3dip"
    android:paddingEnd="1dip">
//省略,下边就是标题,内容,以及3个button按钮了,就不帖了
AlertController里布局的添加

mWindow.setContentView(contentView); 可以看到就是把我们dialog默认的布局id传进去了,就是上边那个alert_dialog.xml

    public void installContent() {
        /* We use a custom title so never request a window title */
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        int contentView = selectContentView();
        mWindow.setContentView(contentView);
        setupView();
        setupDecor();
    }

    private int selectContentView() {
        if (mButtonPanelSideLayout == 0) {
            return mAlertDialogLayout;
        }
        if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
            return mButtonPanelSideLayout;
        }
        // TODO: use layout hint side for long messages/lists
        return mAlertDialogLayout;
    }

这个dialog的里边的布局逻辑就不管了,我们知道我们这个view最后是通过windown方法添加到页面上了。

打印下dialog的页面构成

既然知道和window有关,那就打印下

            val mWindow = dialog.window
            val decor = mWindow.getDecorView()
            checkView(decor)

        private fun checkView(decor:View){
            if(decor!=null&&decor is ViewGroup){
                val vg=decor as ViewGroup
                val count=vg.childCount
                repeat(count){
                    val child=vg.getChildAt(it)
                    println("parent=${vg}======child${count}====${it}====$child")
                    if(child is ViewGroup){
                        checkView(child)
                    }
                }
            }
        }

打印完可以看到,他的页面结构和activity差不多,或者说是一样的。
这里有一张activity页面的布局图,可以参考https://www.jianshu.com/p/1e222b3ac7a0 后边有分析。
我们这里的dialog,对应的就是黑色字体的部分。
也就是dialog里我们自定义的布局,是放在一个id是content的Framelayout下边的

view.png

我们打印出的view,其实可以看到left,top,right,bottom这几个值的
从最里边到最外边,parentPanel这个id上边的布局文件有

android.widget.LinearLayout{6a24d84 V.E...... ........ 0,0-531,150 #10202f0 android:id/parentPanel}

android.widget.FrameLayout{40c6797 V.E...... ........ 0,0-531,150 #1020002 android:id/content}

android.widget.FrameLayout{6932216 V.E...... ........ 16,16-547,166}

com.android.internal.policy.PhoneWindow$DecorView{ab44b31 V.E...... R....... 0,0-563,182}

我们看到decorview下边的那个FrameLayout,他的left,top,right,bottom值,可以猜到它设置了一个margin,或者也可能是decorview设置的padding。

打印下的结果是decorview设置了padding,4个padding都是16.

找到原因,那就改呗

        override fun onStart() {
            super.onStart()
            val window = dialog.window
            val param = window!!.attributes
            param.width = resources.displayMetrics.widthPixels
            window.attributes = param
            window.decorView.setPadding(0,0,0,0)
        }

改完成这样了,文字部分不见了,而且打印结果来看,padding确实没了,我们的view宽度也确实和屏幕一样了。对比上边的图,应该知道,我们的文字就是默认的textView.


image.png

从打印结果,我们的view宽度和屏幕一样宽了,从图上来看,Textview明显也就是在最左边了,只是左边那部分看不到了。感觉是布局没刷新?
后来想到弄个背景颜色看看,结果发现弄完背景就正常了,甚至那paddding都不需要,如下

        override fun onStart() {
            super.onStart()
            val window = dialog.window
            val param = window!!.attributes
            param.width = resources.displayMetrics.widthPixels
            window.attributes = param
//            window.decorView.setPadding(0,0,0,0)
//            window.decorView.setBackgroundColor(Color.RED)
            window.setBackgroundDrawable(ColorDrawable(Color.RED))
        }
image.png

我们看下源码,sources目录下随便找个版本
D:\sdk\sources\android-23\com\android\internal\policy\PhoneWindow.java

    @Override
    public final void setBackgroundDrawable(Drawable drawable) {
        if (drawable != mBackgroundDrawable || mBackgroundResource != 0) {
            mBackgroundResource = 0;
            mBackgroundDrawable = drawable;
            if (mDecor != null) {
                mDecor.setWindowBackground(drawable);
            }
            if (mBackgroundFallbackResource != 0) {
                mDecor.setBackgroundFallback(drawable != null ? 0 : mBackgroundFallbackResource);
            }
        }
    }

//DecorView 也在这个类里边
        public void setWindowBackground(Drawable drawable) {
            if (getBackground() != drawable) {
                setBackgroundDrawable(drawable);
                if (drawable != null) {
                    drawable.getPadding(mBackgroundPadding);
                } else {
                    mBackgroundPadding.setEmpty();
                }
                drawableChanged();
            }
        }

可以看到window.setBackgroundDrawable(ColorDrawable(Color.RED))最终也是给decorview设置背景的
那么这样写可以不
window.decorView.setBackgroundColor(Color.RED)
背景没问题,不过这种padding还在,就 需要加上
window.decorView.setPadding(0,0,0,0)
也就是,下边这两句的效果和window.setBackgroundDrawable(ColorDrawable(Color.RED))一样

window.decorView.setBackgroundColor(Color.RED)
window.decorView.setPadding(0,0,0,0)
image.png

那么仔细看下就可以发现window.setBackgroundDrawable(ColorDrawable(Color.RED))这个方法里最后多了一行代码drawable.getPadding(mBackgroundPadding);
看下这个方法,可以看到把Rect的值弄成0了.

    public boolean getPadding(@NonNull Rect padding) {
        padding.set(0, 0, 0, 0);
        return false;
    }

而后边还调用了一个方法drawableChanged();
下图可以看到,用到了这个padding,而这个padding被弄成0了都,仔细看看了,感觉他好像本来就是0
也就是初始化,然后上边setWindowBackground()方法里用到,然后就下图了。
另外一个mFramePadding也差不多


image.png

分析下布局的加载过程

decorview-framlayout-framlayout-dialog的布局

dialog的show方法如下,其他代码省略,就留了2行,

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);

    public void show() {
        onStart();
        if (!mCreated) {
            dispatchOnCreate(null);//这个会调用dialog的onCreate方法的
        }
        mDecor = mWindow.getDecorView();//如果 为空,里边会创建DecorView的

        mWindowManager.addView(mDecor, l);

    }

看下AlertDialog的布局是咋加进去的

//AlertDialog方法
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAlert.installContent();
    }
//
    public void installContent() {
        int contentView = selectContentView();
        mWindow.setContentView(contentView);
        setupView();
    }

看下phoneWindow

    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);//这里添加的,mContentParent是decorview里加的那个布局
        }
        mContentParentExplicitlySet = true;
    }

看下phonewindow,这里会分析DecorView的创建,以及上边mContentParent的添加过程。

    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

下边会省略无关的代码,只保留view的创建相关的代码

    private DecorView mDecor;
    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
 private void installDecor() {
 if (mDecor == null) {
            mDecor = generateDecor(-1);
        }
if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);



}

继续

protected ViewGroup generateLayout(DecorView decor) {

 WindowManager.LayoutParams params = getAttributes();
        // The rest are only done if this window is not embedded; otherwise,
        // the values are inherited from our container.
        if (getContainer() == null) {
            if (mBackgroundDrawable == null) {
                if (mBackgroundResource == 0) {
                    mBackgroundResource = a.getResourceId(
                            R.styleable.Window_windowBackground, 0);
                }
                if (mFrameResource == 0) {
                    mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
                }
            }
        }
//
int layoutResource;
//根据条件不同获取到layoutresource
layoutResource = R.layout.screen_swipe_dismiss;
layoutResource = R.layout.screen_title_icons;
layoutResource = R.layout.screen_progress;
layoutResource = R.layout.screen_custom_title;

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
}

下边DecorView的方法就是把上边的布局加载到DecorView里

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;//新加的这个布局成了容器,后边的东西都是加到这里的
        initializeElevation();
    }

查找这个padding到底是哪里弄的

我打印了相关的几个rect,前边分析过decorview在setbackground以后会调用一个drawableChanged()的方法,这个方法里调用了setpadding方法,如下

    private void drawableChanged() {
        if (mChanging) {
            return;
        }

        setPadding(mFramePadding.left + mBackgroundPadding.left,
                mFramePadding.top + mBackgroundPadding.top,
                mFramePadding.right + mBackgroundPadding.right,
                mFramePadding.bottom + mBackgroundPadding.bottom);
        requestLayout();
        invalidate();

打印下这4个的值

    private final Rect mDrawingBounds = new Rect();

    private final Rect mBackgroundPadding = new Rect();

    private final Rect mFramePadding = new Rect();

    private final Rect mFrameOffsets = new Rect();

使用反射获取

val decor =  dialog.window.getDecorView()
            reflectValue(decor,"mDrawingBounds")
            reflectValue(decor,"mBackgroundPadding")
            reflectValue(decor,"mFramePadding")
            reflectValue(decor,"mFrameOffsets")

        private fun reflectValue(decor: View,filedName:String){
            try {
                var filed=decor.javaClass.getDeclaredField(filedName)
                filed.isAccessible=true
                val rect= filed.get(decor) as Rect
                println("$filedName==========${rect.toShortString()}")
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }

结果,这种我修改过宽度为屏幕宽1024,

mDrawingBounds==========[0,0][1024,204]
mBackgroundPadding==========[16,16][16,16]
mFramePadding==========[0,0][0,0]
mFrameOffsets==========[0,0][0,0]

可以看到mBackgroundPadding是有值的,那看下这个值设置的,可以查到是在给DecorView设置背景的时候
drawable.getPadding(mBackgroundPadding); 获取到的,好像我就知道InsetDrawable有padding,其他Drawable还真没研究过,抽空看看还有哪个带padding的

        public void setWindowBackground(Drawable drawable) {
            if (getBackground() != drawable) {
                setBackgroundDrawable(drawable);
                if (drawable != null) {
                    drawable.getPadding(mBackgroundPadding);
                } else {
                    mBackgroundPadding.setEmpty();
                }
                drawableChanged();
            }
        }

找到哪里设置的,那么就简单了,现在查下系统啥时候给它设置的背景,背景哪里来的,可以看到用的就是主题里的windowBackground资源,那去找下这个资源文件啥样

protected ViewGroup generateLayout(DecorView decor){

                if (mBackgroundResource == 0) {
                    mBackgroundResource = a.getResourceId(
                            R.styleable.Window_windowBackground, 0);
                }
                if (mFrameResource == 0) {
                    mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
                }
}

去主题里找下
因为现在用的都是Theme.AppCompat 主题,所以就去appcompat-v7下去找

<style name="Base.ThemeOverlay.AppCompat.Dark" parent="Platform.ThemeOverlay.AppCompat.Dark">
        <item name="android:windowBackground">@color/background_material_dark</item>

<style name="Base.ThemeOverlay.AppCompat.Light" parent="Platform.ThemeOverlay.AppCompat.Light">
        <item name="android:windowBackground">@color/background_material_light</item>

 <style name="Base.V7.Theme.AppCompat.Dialog" parent="Base.Theme.AppCompat">
        <item name="android:windowBackground">@drawable/abc_dialog_material_background</item>

    <style name="Base.V7.Theme.AppCompat.Light.Dialog" parent="Base.Theme.AppCompat.Light">
        <item name="android:windowBackground">@drawable/abc_dialog_material_background</item>

    <style name="Base.V7.ThemeOverlay.AppCompat.Dialog" parent="Base.ThemeOverlay.AppCompat">
<item name="android:windowBackground">@drawable/abc_dialog_material_background</item>

    <style name="Platform.AppCompat" parent="android:Theme.Holo">
        <item name="android:windowBackground">@color/background_material_dark</item>

<style name="Platform.AppCompat.Light" parent="android:Theme.Holo.Light">
<item name="android:windowBackground">@color/background_material_light</item>

我们这里是dialog,那么肯定找dialog那个图片了abc_dialog_material_background
果然和猜想的一样,是个insetDrawable

<inset xmlns:android="http://schemas.android.com/apk/res/android"
       android:insetLeft="16dp"
       android:insetTop="16dp"
       android:insetRight="16dp"
       android:insetBottom="16dp">
    <shape android:shape="rectangle">
        <corners android:radius="2dp" />
        <solid android:color="@android:color/white" />
    </shape>
</inset>

总结

终于找到这个padding哪里设置的了。

中间碰到的问题

我studio打开了一份api27的DecorView源码,然后看到那个mResizingBackgroundDrawable【保存DecorView的背景的】是个全局变量,然后想通过反射获取来着,结果在我6.0的本子上一直提示反射失败,找不到这个字段。可我明明看到有,最后想着去api23的源码看了下,发现6.0的还真没这个字段。
哎。看来以后反射失败找不到字段的时候得注意下,是不是手机版本和自己看的api源码不一致。

解释下最上边解决办法的逻辑

有两种

  1. 这种分析过,这个方法最终里边调用了DecorView的setWindowBackground,而这个方法里会根据drawable获取padding,因为我们这里传的是ColorDrawable,所以padding就成0了。
 window.setBackgroundDrawable(ColorDrawable(Color.RED))
  1. 下边这种,因为系统默认图片就是带padding的,而且也setpadding不为空,所以我们即需要修改padding为0,还得修改默认的背景图
window.decorView.setPadding(0,0,0,0)
window.decorView.setBackgroundColor(Color.RED)
//或者这样写也行,反正就是设置背景的3种方法,当然可别设置insetDrawable额
//window.decorView.background=ColorDrawable(Color.RED)
//window.decorView.setBackgroundResource()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容