AlertDialog源码理解

首先看一下AlertDialog简单的用法。

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);

	AlertDialog.Builder builder = new AlertDialog.Builder(this);
	builder.setMessage("hello")
			.setTitle("AlertDialog")
			.create()
			.show();
}

一步一步来看看到底在源码中做了什么。
首先调用AlertDialog.Builder()创建了一个Builder类的对象,它是一个AlertDialog的内部类。

public class AlertDialog {
	...
	public static class Builder {
	
		...
		
		public Builder(@NonNull Context context) {
		    this(context, resolveDialogTheme(context, 0));
		}
		
		...
		
	}
	
	...
	
}

通过this调用另一个构造方法。

public Builder(@NonNull Context context, @StyleRes int themeResId) {
    P = new AlertController.AlertParams(new ContextThemeWrapper(
            context, resolveDialogTheme(context, themeResId)));
    mTheme = themeResId;
}

创建了一个AlertParams类的对象P。

class AlertController {
	...
	public static class AlertParams {
		...
		public AlertParams(Context context) {
			mContext = context;
			mCancelable = true;
			mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		}
		...
	}
	...
}

AlertParams是AlertController的静态内部类。用户在调用Builder方法后会创建一个对应的AlertParams实例P。
接着调用了一系列Builder的set方法。我们看其中一个setTitle方法。

public Builder setTitle(@Nullable CharSequence title) {
    P.mTitle = title;
    return this;
}

为P中的对应属性赋值。setMessage和其他的一些set方法也是类似的

public Builder setMessage(@Nullable CharSequence message) {
    P.mMessage = message;
    return this;
}

通过我们调用一系列的set方法,P中的各个属性已经被填充为我们传入的参数。
接着调用了Builder的create()方法。

public AlertDialog create() {
    // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
    // so we always have to re-set the theme
    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。

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

在这个过程中还创建了一个AlertController实例。接着调用了P的apply方法。

public void apply(AlertController dialog) {
	if (mCustomTitleView != null) {
		dialog.setCustomTitle(mCustomTitleView);
	} else {
		if (mTitle != null) {
			dialog.setTitle(mTitle);
		}
		if (mIcon != null) {
			dialog.setIcon(mIcon);
		}
		if (mIconId != 0) {
			dialog.setIcon(mIconId);
		}
		if (mIconAttrId != 0) {
			dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
		}
	}
	if (mMessage != null) {
		dialog.setMessage(mMessage);
	}
	
	...
	
}

这里的dialog是上一步创建的AlertController对象。调用了一系列AlertController的set方法。mTitle、mIcon、mMessage这些就是P中的那些属性。我们在前面已经通过builder的set方法为其中的一部分赋了值。就是说,我们如果设置了这些参数,会继续调用AlertController中对应的set方法。

if (mTitle != null) {
    dialog.setTitle(mTitle);
}
看其中的一个,其他都是类似的。
```javascript
public void setTitle(CharSequence title) {
    mTitle = title;
    if (mTitleView != null) {
        mTitleView.setText(title);
    }
}

将这些P中设置的值传给了AlertController。回到create方法。最后返回了创建的AlertDialog实例。到这里我们知道了,现在我们拥有了一个AlertDialog,在这个AlertDialog中还包含了一个AlertController,而AlertController中保存着我们传进去的参数。接着还有最后一步,调用builder的show方法。
因为前面调用create方法时返回的是一个AlertDialog,调用的show方法其实是AlertDialog的show方法。但是AlertDialog是继承自AppCompatDialog,而AppCompatDialog又是继承自Dialog,所以这里的show()方法是Dialog的show方法。

public void show() {
	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;

	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();

	...
}

调用了dispatchOnCreate()。

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

调用onCreate()

protected void onCreate(Bundle savedInstanceState) {
}

这个方法是在AlertDialog中实现的。

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

调用了mAlert的installContent方法。mAlert就是前面创建的AlertController。

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

调用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);

	// Install custom content before setting up the title or buttons so
	// that we can handle panel overrides.
	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);

	// Resolve the correct panels and remove the defaults, if needed.
	final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
	final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
	final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);

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

	final boolean hasCustomPanel = customPanel != null
			&& customPanel.getVisibility() != View.GONE;
	final boolean hasTopPanel = topPanel != null
			&& topPanel.getVisibility() != View.GONE;
	final boolean hasButtonPanel = buttonPanel != null
			&& buttonPanel.getVisibility() != View.GONE;

	// Only display the text spacer if we don't have buttons.
	if (!hasButtonPanel) {
		if (contentPanel != null) {
			final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
			if (spacer != null) {
				spacer.setVisibility(View.VISIBLE);
			}
		}
	}

	...
}

看一下setupTitle方法

private void setupTitle(ViewGroup topPanel) {
	if (mCustomTitleView != null) {
		// Add the custom title view directly to the topPanel layout
		LayoutParams lp = new LayoutParams(
				LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);

		topPanel.addView(mCustomTitleView, 0, lp);

		// Hide the title template
		View titleTemplate = mWindow.findViewById(R.id.title_template);
		titleTemplate.setVisibility(View.GONE);
	} else {
		mIconView = (ImageView) mWindow.findViewById(android.R.id.icon);

		final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
		if (hasTextTitle && mShowTitle) {
			// Display the title if a title is supplied, else hide it.
			mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
			mTitleView.setText(mTitle);

			// Do this last so that if the user has supplied any icons we
			// use them instead of the default ones. If the user has
			// specified 0 then make it disappear.
			if (mIconId != 0) {
				mIconView.setImageResource(mIconId);
			} else if (mIcon != null) {
				mIconView.setImageDrawable(mIcon);
			} else {
				// Apply the padding from the icon to ensure the title is
				// aligned correctly.
				mTitleView.setPadding(mIconView.getPaddingLeft(),
						mIconView.getPaddingTop(),
						mIconView.getPaddingRight(),
						mIconView.getPaddingBottom());
				mIconView.setVisibility(View.GONE);
			}
		} else {
			// Hide the title template
			final View titleTemplate = mWindow.findViewById(R.id.title_template);
			titleTemplate.setVisibility(View.GONE);
			mIconView.setVisibility(View.GONE);
			topPanel.setVisibility(View.GONE);
		}
	}
}

在这个方法中通过findViewById找到了textview,然后调用setText。这段代码看着就很熟悉了,和我们自己向TextView中设置文本是一样的。其他的一些属性设置流程都是相似的。
以上就是整个AlertDialog创建的大致流程,它是通过一个建造者模式来实现的。总结一下这个AlertDialog建造的流程。

  1. 首先通过AlertDialog.Builder创建builder,在内部创建了AlertParams。
  2. 用户调用一系列builder的set方法填充AlertParams中对应的属性值.
  3. 调用builder的create方法创建AlertDialog和自己的AlertController,接着apply方法将AlertParams中填充的属性继续填充到AlertController中。
  4. 调用show方法实际会调用AlertController的installContent方法,然后通过一系列内部调用,将AlertController中填充的值设置到界面上。
发布了10 篇原创文章 · 获赞 19 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/songkai0825/article/details/103620447