AlertDialog添加自定义view流程详解
AlertDialog自定义view添加流程
有错误请指正,谢谢。
重要类
- Dialog: 与dialog相关的类的基类
- AlertDialog:继承自Dialog类。
- AlertDialog.Builder: AlertDialog内置的Builder类,用于构建alertDialog。
- AlertController:AlertDialog对象构建和显示的实际操作者。
- AlertController.AlertParams:AlertController与AlertDialog类之间的桥梁。
AlertDialog会持有一个AlertController和一个AlertParams,AlertDialog通过Builder模式
创建,通过AlertParams调用AlertController来设置dialog的view以及显示控制等。
todo: 类关系图
调用方法
todo:调用流程图
AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.my_style);
builder.setView(view);
AlertDialog dialog = builder.create();
dialog.show();
拆分
构造AlertDialog.Builder()
public static class Builder {
@UnsupportedAppUsage
private final AlertController.AlertParams P;
...
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
}
...
}
builder是AlertDialog的内部构造类,其功能只有一个,维护了一个AlertParams对象,将所有
需要设置的项保存在params中。
注意此时初始化P时传入了一个ContextThemeWrapper,它爸爸是ContextWrapper,爷爷就是熟悉的
Context,所以在P内的mContext是一个额外设置了theme的,其余与传进来的context一样的context。
这个东西会在后面用于设置dialog的background,后面会讲到。
// A context wrapper that allows you to modify or replace the theme of the wrapped context.
public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
super(base);
mThemeResource = themeResId;
}
Builder.setView
public Builder setView(View view) {
P.mView = view;
P.mViewLayoutResId = 0;
P.mViewSpacingSpecified = false;
return this;
}
可以看到即把自定义的view同样保存在了params对象内。
Builder.create
public AlertDialog create() {
// Context has already been wrapped with the appropriate theme.
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;
}
#### 构造AlertDialog和其内部的AlertController
这里create了dialog对象,传进去的是前面设置在P里的context。这里重点关注P.apply(dialog.mAlert),
后面一些都是设置的cancel和dismiss的事件监听。
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构造时,创建了mAlert,即AlertController,mWindow在调用super构造后就会有,
即系统服务代理WindowManager,这里不多展开。
AlertParams.apply
public void apply(AlertController dialog) {
...
if (mView != null) {
if (mViewSpacingSpecified) {
dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
mViewSpacingBottom);
} else {
dialog.setView(mView);
}
} else if (mViewLayoutResId != 0) {
dialog.setView(mViewLayoutResId);
}
...
}
apply函数中省略了很多设置dialog的title,message等的函数,因为这次重点关注自定义view设置,
过程也是比较相似。
可以看到此时mView就是刚刚setView进来的自定义view,且当时设置了mViewSpacingSpecified
为false,故走else,即dialog.setView(mView)。
这里的dialog不是AlertDialog对象,而是传来AlertDialog对象中的AlertControl对象,因为一切
实际操作由它完成。
AlertController.setView
public void setView(View view) {
mView = view;
mViewLayoutResId = 0;
mViewSpacingSpecified = false;
}
这里view就被存进了AlertController中。到这里create就完成了,真正的setView也完成了(
其实刚刚的setView只是表面的),然后一个可以用的alertDialog对象也被返回。
AlertDialog.show()
现在就开始真正的处理自定义view和参数了。其实之前全部都是准备工作,但看上去好像干了很多事一样。
public void show() {
if (mShowing) {
...
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();
...
... // 省略softinput相关
mWindowManager.addView(mDecor, l);
... // 省略softinput相关
mShowing = true;
sendShowMessage();
}
AlertDialog.dispatchOnCreate
// internal method to make sure mCreated is set properly without requiring
// users to call through to super in onCreate
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
onCreate(savedInstanceState);
mCreated = true;
}
}
重点就在onCreate中,AlertDialog重写了爸爸的onCreate方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
到这里AlertDialog的show方法,最终由mAlert去做。
AlertController.installContent
public void installContent(AlertParams params) {
params.apply(this);
installContent();
}
@UnsupportedAppUsage
public void installContent() {
int contentView = selectContentView();
mWindow.setContentView(contentView);
setupView();
}
installContent有两个多态方法,一个app可用,手动传入AlertParams并apply。这次就先不管了,
直接看无参方法,只供系统调用。
AlertController.selectContentView
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;
}
常规情况下就会返回mAlertDialogLayout。这个东西是啥呢?其实就是构造controller的时候
会缓存一些系统默认的dialog的layout布局id,用作容器。
@UnsupportedAppUsage
protected AlertController(Context context, DialogInterface di, Window window) {
...
mAlertDialogLayout = a.getResourceId(
R.styleable.AlertDialog_layout, R.layout.alert_dialog);
... // 省略了一些其他的默认layout id缓存,如button,各种title,item等。
a.recycle();
}
回到installContent方法中,调用了mWindow的setContentView方法,其实也就是将这个预置的容器
加到了DecorView里面,这里有一个frameLayout,就是用来放我们自定义view的。然后走到setupView
AlertController.setupView
private void setupView() {
...
setupCustomContent(customPanel);
...
final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
...
final TypedArray a = mContext.obtainStyledAttributes(
null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
hasTopPanel, hasCustomPanel, hasButtonPanel);
a.recycle();
}
private void setupCustomContent(ViewGroup customPanel) {
final View customView;
if (mView != null) {
customView = mView;
}
...
final boolean hasCustomView = customView != null;
...
if (hasCustomView) {
final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
...
} else {
customPanel.setVisibility(View.GONE);
}
}
这里存在两个关键点,设置view的参数和背景。
setupView里面主要调了setupCustomContent来设自定义view,这里面其实就是把传进去的view
放在了id是custom的frameLayout里,也就是就zidingyiview的父view就是上面提及的预置的layout,
此前已经被add到DecorView里了。
这里重要的是,不管你在layout里是怎么定义的,都会new LayoutParams(MATCH_PARENT, MATCH_PARENT)
作为view的params,所以有时候设置了但没生效,问题可能就在这里。
还有一点是,当dialog背景出现问题,比如view设置了圆角,但是圆角背后还是出现了直角的背景的
时候,解决点在这里。
可以看到在setupView最后,对customPanel设置了背景。而这个背景资源,
就是前面通过ContextThemeWrapper设置的theme中,对背景设置来决定的。
这里setBackground的代码暂时省略。
<style name="name" parent="Theme.AppCompat.Dialog">
<item name="android:windowBackground">@color/customColor</item>
</style>
总结
- 实际上就是一系列的将view添加到window的DecorView中的过程。
- 实际执行者是AlertController;
- Builder是构建者,用于将参数保存,而AlertParams用于帮助controller处理参数。
- AlertDialog用于对dialog的实际操作,显示或者去除等。
完
有错误请指正,谢谢。
附录
预置alertDialog layout定义,platform/frameworks/core/res/res/layout/alert_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/layout/alert_dialog.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<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">
<LinearLayout android:id="@+id/topPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="54dip"
android:orientation="vertical">
<LinearLayout android:id="@+id/title_template"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="6dip"
android:layout_marginBottom="9dip"
android:layout_marginStart="10dip"
android:layout_marginEnd="10dip">
<ImageView android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:paddingTop="6dip"
android:paddingEnd="10dip"
android:src="@drawable/ic_dialog_info" />
<com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
style="?android:attr/textAppearanceLarge"
android:singleLine="true"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart" />
</LinearLayout>
<ImageView android:id="@+id/titleDivider"
android:layout_width="match_parent"
android:layout_height="1dip"
android:visibility="gone"
android:scaleType="fitXY"
android:gravity="fill_horizontal"
android:src="@android:drawable/divider_horizontal_dark" />
<!-- If the client uses a customTitle, it will be added here. -->
</LinearLayout>
<LinearLayout android:id="@+id/contentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ScrollView android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="2dip"
android:paddingBottom="12dip"
android:paddingStart="14dip"
android:paddingEnd="10dip"
android:overScrollMode="ifContentScrolls">
<TextView android:id="@+id/message"
style="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dip" />
</ScrollView>
</LinearLayout>
<FrameLayout android:id="@+id/customPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
<FrameLayout android:id="@+android:id/custom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="5dip"
android:paddingBottom="5dip" />
</FrameLayout>
<LinearLayout android:id="@+id/buttonPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="54dip"
android:orientation="vertical" >
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="4dip"
android:paddingStart="2dip"
android:paddingEnd="2dip"
android:measureWithLargestChild="true">
<LinearLayout android:id="@+id/leftSpacer"
android:layout_weight="0.25"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone" />
<Button android:id="@+id/button1"
android:layout_width="0dip"
android:layout_gravity="start"
android:layout_weight="1"
style="?android:attr/buttonBarPositiveButtonStyle"
android:maxLines="2"
android:layout_height="wrap_content" />
<Button android:id="@+id/button3"
android:layout_width="0dip"
android:layout_gravity="center_horizontal"
android:layout_weight="1"
style="?android:attr/buttonBarNeutralButtonStyle"
android:maxLines="2"
android:layout_height="wrap_content" />
<Button android:id="@+id/button2"
android:layout_width="0dip"
android:layout_gravity="end"
android:layout_weight="1"
style="?android:attr/buttonBarNegativeButtonStyle"
android:maxLines="2"
android:layout_height="wrap_content" />
<LinearLayout android:id="@+id/rightSpacer"
android:layout_width="0dip"
android:layout_weight="0.25"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

1325

被折叠的 条评论
为什么被折叠?



