【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>

总结

  1. 实际上就是一系列的将view添加到window的DecorView中的过程。
  2. 实际执行者是AlertController;
  3. Builder是构建者,用于将参数保存,而AlertParams用于帮助controller处理参数。
  4. 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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值