修改AlertDialog的button的字体颜色

AlertDialog修改Button的字体颜色

去网上搜了一下,大部分是通过修改主题中的属性值。详见通过源码分析,修改AlertDialog按钮的颜色,看了之后,确实是一步一步查找,一步一步深入,找到了系统设置颜色的方法,也可实现需求。
如果用面向对象的思想 思考一下,应该可以.getButton()得到Button对象,然后,直接对这Button对象设置颜色属性值就好了。

这里写图片描述
这里写图片描述 

结果如上图所示,直接GG了。并且通过debug我们发现,dialog.getButton() 获取到的button对象竟然为null,这个确实让我们很费解。

    public Button getButton(int whichButton) {
        return mAlert.getButton(whichButton);
    }
我们看到getButton为null是mAlert.getButton()为null,这个mAlert是AlertController的实例。

ok,找到原因之后,我们分析下源码吧。

首先57行之前的代码应该不用多说,是用Builder模式,给AlertDialog设置各种值。直接看下dialog=builder.create()

        public AlertDialog create() {
            // Context has already been wrapped with the appropriate theme.
            //new 一个AlertDialog对象
            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
            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;
        }


大概做了两个事情,一个是new一个AlertDialog对象,然后,给dialog设置了各种事件的监听。我们开看下new AlertDialog的时候做了什么事情。
    protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
        this(context, 0);

        setCancelable(cancelable);
        setOnCancelListener(cancelListener);
    }
  protected AlertDialog(Context context, @StyleRes int themeResId) {
      this(context, themeResId, true);
  }
AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                createContextThemeWrapper);

        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = AlertController.create(getContext(), this, getWindow());
}

我们可以看到,AlertDialog的构造方法最终是调用了super.(xx,xx,xx)也就是他的父类Dialog的构造方法。我们接着看。

    public Dialog(@NonNull Context context, @StyleRes int themeResId) {
        this(context, themeResId, true);
    }

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == ResourceId.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }

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

        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

可以看到,重要的是先获取了一个WindowManager,然后new了一个phoneWindow。至此,dialog =builder.create()方法,重要的代码分析完毕。

接下来分析dialog.show()方法。

由于AlertDialog里面没有show()方法,我们直接去他的父类Dialog去看。

public void show() {
        //mShowing这个我们知道,就是当前dialog是否显示出来了。现在还没出来,所以false
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }

        mCanceled = false;
        //这个mCreated 默认是false。所以执行了if语句里面的代码。
        if (!mCreated) {
            dispatchOnCreate(null);
        } else {
            // Fill the DecorView in on any configuration changes that
            // may have occured while it was removed from the WindowManager.
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }

        onStart();
        mDecor = mWindow.getDecorView();

        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
            final ApplicationInfo info = mContext.getApplicationInfo();
            mWindow.setDefaultIcon(info.icon);
            mWindow.setDefaultLogo(info.logo);
            mActionBar = new WindowDecorActionBar(this);
        }

        WindowManager.LayoutParams l = mWindow.getAttributes();
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
            nl.copyFrom(l);
            nl.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            l = nl;
        }

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

        sendShowMessage();
    }

在源码的注释里面我们知道了,重点是dispatchOnCreate()方法。

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

在这个方法我们很清楚的看到,mCreate为false执行了onCreate()方法,然后设置mCreate为true。我们继续看onCreate()方法。发现Dialog的onCreate()方法是空方法。所以我们知道他肯定在AlertDialog里面重写了,我们接着看。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAlert.installContent();
    }
  public void installContent() {
      final int contentView = selectContentView();
      mDialog.setContentView(contentView);
      setupView();
  }
private int selectContentView() {
    if (mButtonPanelSideLayout == 0) {
        return mAlertDialogLayout;
    }
    if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
        return mButtonPanelSideLayout;
    }
    return mAlertDialogLayout;
}

这个onCreate()方法调用了mAlert.installContent()方法。而且这个mAlert是AlertController的实例。所以我们去AlertController再找到installContent()源码。

selectContentView()方法返回的是加载AlertDialog需要的布局问价的Id,然后是调用了Window的setContentView(id)方法。这个方法我相信大家一定很熟悉。因为我们最长用的Activity就是这样创建布局的。

setupView()方法很长,大概就是初始化各种控件,其中就是有关于 button初始化的代码。

private void setupView() {
    ...
    setupButtons(buttonPanel);
    ...
}
protected void setupButtons(ViewGroup buttonPanel) {
      ...
        //给AlertController的mBbuttonPositive赋值
        mButtonPositive = (Button) buttonPanel.findViewById(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;
        }
        
  //给AlertController的mButtonNegative 赋值
mButtonNegative = (Button) buttonPanel.findViewById(R.id.button2); mButtonNegative.setOnClickListener(mButtonHandler); if (TextUtils.isEmpty(mButtonNegativeText)) { mButtonNegative.setVisibility(View.GONE); } else { mButtonNegative.setText(mButtonNegativeText); mButtonNegative.setVisibility(View.VISIBLE); whichButtons = whichButtons | BIT_BUTTON_NEGATIVE; }         //给AlertController的mButtonNeutral赋值 mButtonNeutral = (Button) buttonPanel.findViewById(R.id.button3); mButtonNeutral.setOnClickListener(mButtonHandler); if (TextUtils.isEmpty(mButtonNeutralText)) { mButtonNeutral.setVisibility(View.GONE); } else { mButtonNeutral.setText(mButtonNeutralText); mButtonNeutral.setVisibility(View.VISIBLE); whichButtons = whichButtons | BIT_BUTTON_NEUTRAL; } ... }

至此,我们知道了,AlertController里面的button属性,是在AlertDialog的show()方法里面的dispatchOnCreate()方法里面完成的。所以,我们在show()方法之前通过getButton()获取的值必然是null。所以只需要这样修改我们的代码,就可以解决bug。

        dialog.show();
        dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.RED);
        dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.BLUE);

我们的源码也分析的差不多了,show()方法,通过dispatchOnCreate()方法初始化了各种控件和View只有,通过下面的代码把DecorView添加到WindowManager里面。

mWindowManager.addView(mDecor, l);
然后最后执行这行代码,把AlertDialog展示出来。这行代码实质也是通过Handler来发送一个消息,来展示AlertDialog。
sendShowMessage();

通过源码来慢慢分析一个bug,不紧能让我们深入了解其实现原理,还能从根本上解决这个bug。所以学会看源码对于我们开发者来说,还是很重要的。

如果错误,欢迎指正。











猜你喜欢

转载自blog.csdn.net/xy4_android/article/details/72288826
今日推荐