从源码看 AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE) 为什么是 null

2023-10-27

我们在使用 AlertDialog 的时候,如果想改变 POSITIVE_BUTTON 或者 NEGATIVE_BUTTON 的字体颜色、大小时,可能会注意到 AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE) 这个方法,但是当我们在创建了AlertDialog,想要拿到这 Button 的时候会发现返回的结果是个null
尝试之后会发现,在AlertDialogshow()执行过之后,这个Button返回的就不再是null了,此时就可以对Button的属性进行设置:

 final AlertDialog alertDialog = new AlertDialog.Builder(this).setTitle("Title")
                .setMessage("meaasge")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                }).create();
        alertDialog.show();
        Button button = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
        button.setTextColor(Color.parseColor("#00c8aa"));
        button.setTextSize(10);

或者在onShow(DialogInterface dialog)的回调中设置:

alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                Button button = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
                button.setTextColor(Color.parseColor("#00c8aa"));
                button.setTextSize(10);
            }
        });
alertDialog.show();

接下来我们就看一下为什么AlertDialog.Builder.create()已经构建了一个AlertDialog对象,再去getButton(int which) 会是null

有了AlertDialog实体而getButton(int which)null,那我们就从getButton(int which) 入手看一下源码的处理。点进去getButton(int which) 我们会发现返回的是AlertController类中的成员变量 Button mButtonPositive

//AlertDialog中的getButton(int which)
public Button getButton(int whichButton) {
    return mAlert.getButton(whichButton);
}

//AlertController类中的getButton(int which)
public Button getButton(int whichButton) {
    switch (whichButton) {
        case DialogInterface.BUTTON_POSITIVE:
            return mButtonPositive;
        case DialogInterface.BUTTON_NEGATIVE:
            return mButtonNegative;
        case DialogInterface.BUTTON_NEUTRAL:
            return mButtonNeutral;
        default:
            return null;
        }
}

AlertController,这个类几乎包含了AlertDialog所有的属性。返回的buttonnull,说明这个button还没有初始化,我们来看一下这个button是在哪里被初始化的:

private void setupButtons(ViewGroup buttonPanel) {
        int BIT_BUTTON_POSITIVE = 1;
        int BIT_BUTTON_NEGATIVE = 2;
        int BIT_BUTTON_NEUTRAL = 4;
        int whichButtons = 0;
        mButtonPositive = (Button) buttonPanel.findViewById(android.R.id.button1);
        mButtonPositive.setOnClickListener(mButtonHandler);

        if (TextUtils.isEmpty(mButtonPositiveText)) {
            mButtonPositive.setVisibility(View.GONE);
        } else {
            mButtonPositive.setText(mButtonPositiveText);
            mButtonPositive.setVisibility(View.VISIBLE);
            whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
        }

        mButtonNegative = (Button) buttonPanel.findViewById(android.R.id.button2);
        mButtonNegative.setOnClickListener(mButtonHandler);
    }

buttonAlertController类的setupButtons(ViewGroup buttonPanel)方法中被初始化,通过追踪,setupButtons(ViewGroup buttonPanel)方法在setupView()中被执行,setupView()installContent()中被执行:

public void installContent() {
        final int contentView = selectContentView();
        mDialog.setContentView(contentView);
        setupView();
    }


private void setupView() {
        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
        final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
        final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
        final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
        final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
        setupCustomContent(customPanel);

        final View customTopPanel = customPanel.findViewById(R.id.topPanel);
        final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
        final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);

        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);

        setupContent(contentPanel);
        setupButtons(buttonPanel);
        setupTitle(topPanel);
}

这个installContent()方法是公有的且在AlertController中没有被执行,那必定是在其它类中被AlertController的实例执行了,AlertController类是AlertDialog的一个辅助类且AlertDialog也持有一个AlertController类的实例,所以我们在AlertController类中找这个installContent()方法:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mAlert.installContent();
}

通过查找,在AlertDialog中的onCreate(Bundle savedInstanceState)方法中找到了这个方法。onCreate(Bundle savedInstanceState)方法是重写了父类Dialog的方法,那具体的执行过程就要去Dialog类中找了。
Dialog类中能看到onCreate(Bundle savedInstanceState)dispatchOnCreate(Bundle savedInstanceState)中执行:

void dispatchOnCreate(Bundle savedInstanceState) {
    if (!mCreated) {
        onCreate(savedInstanceState);
        mCreated = true;
    }
}

dispatchOnCreate(Bundle savedInstanceState)方法在Dialog类中被多次执行,而与AlertDialog直接相关的则是show()方法:

public void show() {
        if (!mCreated) {
            dispatchOnCreate(null);
        } else {
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }

        onStart();
        mDecor = mWindow.getDecorView();
        WindowManager.LayoutParams l = mWindow.getAttributes();

        mWindowManager.addView(mDecor, l);
        mShowing = true;
        sendShowMessage();
    }

简化之后的show()方法,mCreated是一个布尔变量,标记是否被创建,首次调用show()时,mCreatedfalse,会执行dispatchOnCreate(null),就会执行onCreate(Bundle savedInstanceState)。到此,为什么AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE)null,就很清楚了。AlertDialog的逻辑是show()方法中初始化了AlertDialog上面的控件,然后展示出来,所以在show()方法执行之后才能得到button

那我们也来看一下AlertDialog.Builder.create()中都干了什么:

public AlertDialog create() {
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }

这个方法里面做了实例化一个AlertDialog 和一些参数的传递,在AlertDialog的构造方法中初始化了AlertController mAlert

protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        mAlert = new AlertController(getContext(), this, getWindow());
}

AlertController 的构造方法中也只是传参和初始化布局id,并没有做初始化控件的操作。

   public AlertController(Context context, AppCompatDialog di, Window window) {
        mContext = context;
        mDialog = 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_android_layout, 0);
        mButtonPanelSideLayout = a.getResourceId(R.styleable.AlertDialog_buttonPanelSideLayout, 0);

        mListLayout = a.getResourceId(R.styleable.AlertDialog_listLayout, 0);
        mMultiChoiceItemLayout = a.getResourceId(R.styleable.AlertDialog_multiChoiceItemLayout, 0);
        mSingleChoiceItemLayout = a
                .getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0);
        mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0);
        mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);

        a.recycle();
        di.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
    }

所以在AlertDialog.Builder.create()之后是得不到具体的控件实例的。

show()之后我们能得到Button,那我们能不能得到dialog上面其他的控件呢?
我们看AlertController的源码会发现,有多处使用findViewById()这个方法,使用的是mWindow,比如:

private void setupView() {
        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
        final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
        final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
        final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);

        final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
        setupCustomContent(customPanel);

        final View customTopPanel = customPanel.findViewById(R.id.topPanel);
        final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
        final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);

        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);

        setupContent(contentPanel);
        setupButtons(buttonPanel);
        setupTitle(topPanel);
    }

这个mWindow变量是通过构造方法传进来的,我们在看下AlertDialog的构造方法:

protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        mAlert = new AlertController(getContext(), this, getWindow());
}

mWindowDialoggetWindow()返回的对象,查看Dialog的构造方法,会发现这个mWindow是一个PhoneWindow实例:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }



    public @Nullable Window getWindow() {
        return mWindow;
    }

源码使用Window.findViewById(int id),那我们应该也能通过获取DialogWindow来获取其他控件。利用这个方法得到AlertDialog上面的控件来设置各种属性,包括titlemessage都可以设置颜色、大小等属性,可以看下我的上篇博客 Builder模式设置AlertDialog字体大小、颜色等属性

回顾一下AlertDialog的创建流程:

首先,通过AlertDialog的内部类Builder,设置了各种属性,然后通过create()方法实例化了AlertDialog,在这个过程中创建了DialogPhoneWindow,再然后是执行show()方法,在这个方法中,执行了onCreate(Bundle savedInstanceState)方法,接着是执行AlertController.installContent(),在这个方法中初始化了各种控件,并设置属性。最后才是show()方法中的WindowManager.addView(PhoneWindow.getDecorView, WindowManager.LayoutParams)展示出来。

看一下AlertController.installContent()这个方法:

public void installContent() {
        final int contentView = selectContentView();
        mDialog.setContentView(contentView);
        setupView();
    }

Dialog类中的setContentView(contentView)方法:

public void setContentView(@LayoutRes int layoutResID) {
    mWindow.setContentView(layoutResID);
}

所以在mDialog.setContentView(contentView)这句话的执行之后才能在后面使用mWindow.findViewById(int id),也就是这样我们才能通过Dialog.getWindow().findViewById(int id)得到具体的控件。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从源码看 AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE) 为什么是 null 的相关文章

随机推荐

  • 微信小程序云开发源码(垃圾分类源码)

    目录 微信小程序云开发源码 垃圾分类源码 小程序云数据库介绍 小程序界面 可搜索名称 大众垃圾分类 小程序体验 微信小程序源码 源码地址 https pan baidu com s 1U19Suzs3nZnMt5OHNGUahQ 提取码 z
  • Mysql表关系 连接查询

    表关联查询 文章目录 表关联查询 内连接 左连接 右连接 如果多个表存在一定关联关系 可以多表在一起进行查询操作 其实表的关联整理与外键约束之间并没有必然联系 但是基于外键约束设计的具有关联性的表往往会更多使用关联查询查找数据 简单多表查询
  • C++ default constructor 讨论

    豆瓣是个好地方 可以找到很多好书 最近翻到了Lippman的inside the c object model 今天看了关于默然构造函数部分 对这个东西有了新的理解 又找出c standard对照着看了看 首先看c standard 12
  • webview加载完成监听

    最近由于产品需要 一个页面上部分是一个WebView 下面是一些文字介绍 但是在赋值时 HTML网页加载会消耗一定时间 在其加载过程中 文字已经展示出来 给用户的体验很不好 所以我就想在webview加载成功结束后再给文字赋值 于是在网上搜
  • 云服务器Docker安装ElasticSearch却启动不了怎么办?

    下载镜像 docker pull elasticsearch 启动容器 docker run d name es p 9200 9200 p 9300 9300 e discovery type single node elasticsea
  • 用WinDbg断点调试FFmpeg

    本文主要讲解 WinDbg 调试器的使用 WinDbg 在 Windows 里面的地位 就跟 GDB 在 Linux 的地位一样 可以通过 微软的官方网站 下载 安装 WinDbg WinDbg 是比较轻量级的调试工具 在一些场景下比较实用
  • 联想服务器esxi虚拟化,企业服务器管理必备——VMware ESXI虚拟化服务器搭建

    现在企业虚拟化服务器使用越来越多 这是一篇VMware虚拟化服务器搭建教程 让我们开始吧 制作U盘安装ESXI镜像 1 使用Linux系统制作U盘启动镜像 UltraISO制作的镜像不能用 必须依赖Linux系统 安装镜像制作工具 yum
  • Hadoop3.x 之 MapReduce 框架原理(月薪过万 第九章下)

    Hadoop3 x 之 MapReduce 框架原理 一 MapTask工作机制 二 ReduceTask 工作机制 三 ReduceTask 并行度决定机制 四 MapTask 源码解析流程 五 ReduceTask 源码解析流程 六 R
  • c++在多个源文件中定义同一个类

    在自己写代码练习时 创建好多个源文件 然后有些文件又定义了相同的类 比如源文件1 class B public B B 源文件2 class B public B new A B 然后在运行源文件2的时候一直都没有进入到构造函数里面去 最后
  • matlab函数之reshape()

    reshape 重构数组 功能 B reshape A sz 按矢量sz定义的维度 包括行数 列数 维数 重构矩阵A来得到矩阵B 实现原理 先将矩阵A先排列成一列 结果感受就是按列优先排列 再按照矢量sz定义大小的行数切割 结构及实例 A
  • 区间图着色问题

    这是算法导论贪心算法一章的一个习题 题目描述 假定有一组活动 我们需要将它们安排到一些教室 任意活动都可以在任意教室进行 我们希望使用最少的教室完成所有的活动 设计一个高效的贪心算法求每个活动应该在哪个教室进行 这个问题称为区间图着色问题
  • 在Linux应用程序中打印函数调用栈

    在Linux中打印函数调用栈 要求 在Linux系统的应用程序中写一个函数print stackframe 用于获取当前位置的函数调用栈信息 方法 execinfo h库下的函数backtrace可以得到当前线程的函数调用栈指针和调用栈深度
  • ODOO15固定资产管理系统解决方案(原创)

    有些公司固定资产众多 而且涉及到在建工程的费用归集及在建工程结转固定资产等复杂情况 使用ODOO系统如何来解决这个客户需要解决的问题呢 我们根据自身的实施经验 分享ODOO固定资产的管理解决方案 1 资产分类设置 资产众多 需要进行类别设置
  • 谷歌云GCP

    感谢公司赞助了Google Cloud Platform GCP Coursera课程 https www coursera org 包括云基础设施 应用开发 数据湖和数据仓库相关知识 其中谷歌云的实验操作平台是 https www qwi
  • 数据库系统丨关系代数运算总结

    文章目录 1 需要记忆的符号 2 集合运算 1 并运算 2 差运算 3 交运算 4 广义笛卡尔积 3 关系运算 1 选择 Selection 2 投影 Projection 3 连接 Join 4 除 Division 1 需要记忆的符号
  • VOT 数据集 groundtruth 8个维度 转成 4个维度的方法

    VOT数据集由于加入了带旋转角度的boundingbox 使得其groundtruth的维度达到了8个 如下 8个维度就代表boundingbox的4个点 比如VOT16中 bag数据序列的groundtruth第一行 334 02 128
  • Casual inference 综述框架

    A survey on causal inference 因果推理综述 A Survey on Causal Inference 一文的总结和梳理 因果推断理论解读 Rubin因果模型的三个假设 基础理论 理论框架 名词解释 individ
  • 如何使用IDEA正确的创建一个Web项目

    我是学习java的新人休元 第一次使用CSDN写博客请大家多多关照 写的第一篇博客就是如何使用IDEA正确的创建一个Web项目 刚刚使用IDEA不到两个星期 有很多地方不熟练 如果有错误请大家指出来 操作系统 win10 编译环境 IDEA
  • 2023年第1季社区Task挑战赛开启,等你来战!

    社区Task挑战赛是面向社区开发者开展的代码或教程征集活动 该挑战赛为社区中热爱FISCO BCOS及周边组件的开发者提供了探索区块链技术 挑战技术难题的舞台 该挑战赛去年在社区成功举办了3季 共吸引了数百名开发者报名 前3季都有哪些有趣的
  • 从源码看 AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE) 为什么是 null

    我们在使用 AlertDialog 的时候 如果想改变 POSITIVE BUTTON 或者 NEGATIVE BUTTON 的字体颜色 大小时 可能会注意到 AlertDialog getButton DialogInterface BU