美文网首页
Dialog源码学习笔记

Dialog源码学习笔记

作者: fancychendong | 来源:发表于2016-08-21 16:00 被阅读331次

    Dialog源码学习笔记

    [TOC] (简书这个不支持吗?)

    Dialog源码学习笔记Dialog中值得学习之-sendXXMessage
    AlertController代码分析记录
    Context.obtainStyledAttributes详解

    Dialog中值得学习之-sendXXMessage

    在Dialog源码中,dialog的显示与隐藏是通过mWindowManager.addView/removeViewImmediate来实现的,并且当dialog设置了

    dialog.setOnShowListener();
    dialog.setOnDismissListener();
    dialog.setOnCancelListener();
    

    的时候,当dialog显示隐藏的时候都会回调给相应的listener的,是如何回调过去的呢?
    本质上还是调用各自的方法:

    private static final class ListenersHandler extends Handler {
     private WeakReference<DialogInterface> mDialog;
    
     public ListenersHandler(Dialog dialog) {
         mDialog = new WeakReference<DialogInterface>(dialog);
     }
    
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
             case DISMISS:
                 ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                 break;
             case CANCEL:
                 ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                 break;
             case SHOW:
                 ((OnShowListener) msg.obj).onShow(mDialog.get());
                 break;
         }
     }
    }
    

    这里中间用Handler和Message去处理listener的回调,这种思路挺赞的。
    整体流程就是:
    1、setOnShowListener的时候会把OnShowListener对象赋值给Message,并且通过mListenersHandler返回给mShowMessage,这个时候mShowMessage的obj就是该listener了。

    public void setOnShowListener(OnShowListener listener) {
            if (listener != null) {
                mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
            } else {
                mShowMessage = null;
            }
        }
    

    2、当dialog调用show的时候,会执行sendShowMessage(),然后再让mDismissMessage.sendToTarget()传递给mListenersHandler的handleMessage去执行。

    public void show() {
            //.......省略代码
            try {
                mWindowManager.addView(mDecor, l);
                mShowing = true;
        
                sendShowMessage();
            } finally {
            }
        }
    
    private void sendDismissMessage() {
            if (mDismissMessage != null) {
                // Obtain a new message so this dialog can be re-used
                Message.obtain(mDismissMessage).sendToTarget();
            }
        }
    private void sendShowMessage() {
        if (mShowMessage != null) {
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mShowMessage).sendToTarget();
        }
    }
    

    这里再插一句:为什么一定要用Message.obtain(mShowMessage)呢?不直接mShowMessage.sendToTarget()呢?
    原因在内部调用了obtain,这个obtain里面实现了避免重复创建新的Message对象的机制。减少内存的消耗!这个是sdk的开发者思考过的问题,message在应用中使用的频率特别高,所以为了减少内存消耗出此策略,点赞!

    public static Message obtain(Message orig) {
            Message m = obtain();
            m.what = orig.what;
            m.arg1 = orig.arg1;
            m.arg2 = orig.arg2;
            m.obj = orig.obj;
            m.replyTo = orig.replyTo;
            m.sendingUid = orig.sendingUid;
            if (orig.data != null) {
                m.data = new Bundle(orig.data);
            }
            m.target = orig.target;
            m.callback = orig.callback;
    
            return m;
        }
        
        /**
         * Return a new Message instance from the global pool. Allows us to
         * avoid allocating new objects in many cases.
         */
        public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }
    

    总结一下:如果要我去实现一个listener的回调,大部分情况下是直接声明一个listener的变量,什么地方需要执行该listener的回调,什么地方就手动调用一下。
    这里用

        private Message mCancelMessage;
        private Message mDismissMessage;
        private Message mShowMessage;
    

    替代了,感觉来说会使代码更合理些。可能具体有什么好处,暂时参悟不出来,如果有感兴趣的人看到了这篇笔记,可以给我回复一起探讨下!

    AlertController代码分析记录

    AlertDialog extends Dialog implements DialogInterface {
        private AlertController mAlert;
        public static class Builder {
            private final AlertController.AlertParams P;
        }
    }
    

    首先AlertDialog的这个类的结构大家是知道的,使用了Builder设计模式。
    在Dialog的show函数中,

    if (!mCreated) {
        dispatchOnCreate(null);
    }
    mDecor = mWindow.getDecorView();
    mWindowManager.addView(mDecor, l);
    

    会执行onCreate函数创建view视图,然后通过windowManager添加视图,这样一个弹窗就显示在了界面中。

    AlertDialog中的oncreate中调用的是AlertController.installContent函数

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

    http://grepcode.com/中可以找到AlertController的源码

    public void More ...installContent() {
    232         /* We use a custom title so never request a window title */
    233         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
    234         int contentView = selectContentView();
    235         mWindow.setContentView(contentView);
    236         setupView();
    237         setupDecor();
    238     }
    239 
    240     private int More ...selectContentView() {
    241         if (mButtonPanelSideLayout == 0) {
    242             return mAlertDialogLayout;
    243         }
    244         if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
    245             return mButtonPanelSideLayout;
    246         }
    247         // TODO: use layout hint side for long messages/lists
    248         return mAlertDialogLayout;
    249     }
    

    如果没有给AlertDialog设置自定义view,则使用一个默认的mAlertDialogLayout,这个默认的

     TypedArray a = context.obtainStyledAttributes(null,  com.android.internal.R.styleable.AlertDialog, com.android.internal.R.attr.alertDialogStyle, 0);
     mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout,  com.android.internal.R.layout.alert_dialog);
    

    com.android.internal.R.layout.alert_dialog的文件可以在sdk中找到,太长了,大概略读了下,发现了里面有个

    <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" />
    

    Context.obtainStyledAttributes详解

    这个DialogTitle是个什么?在grepcode中可以找到

    public class More ...DialogTitle extends TextView {
    31
    32    public More ...DialogTitle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    33        super(context, attrs, defStyleAttr, defStyleRes);
    34    }
    47    //......省略构造函数
    48    @Override
    49    protected void More ...onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    50        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    51
    52        final Layout layout = getLayout();
    53        if (layout != null) {
    54            final int lineCount = layout.getLineCount();
    55            if (lineCount > 0) {
    56                final int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
    57                if (ellipsisCount > 0) {
    58                    setSingleLine(false);
    59                    setMaxLines(2);
    60
    61                    final TypedArray a = mContext.obtainStyledAttributes(null,
    62                            android.R.styleable.TextAppearance, android.R.attr.textAppearanceMedium,
    63                            android.R.style.TextAppearance_Medium);
    64                    final int textSize = a.getDimensionPixelSize(
    65                            android.R.styleable.TextAppearance_textSize, 0);
    66                    if (textSize != 0) {
    67                        // textSize is already expressed in pixels
    68                        setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
    69                    }
    70                    a.recycle();
    71
    72                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);      
    73                }
    74            }
    75        }
    76    }
    77}
    

    仔细分析这段代码,这段代码主要的用途就是让dialog的title最多显示两行,且如果是两行,字体大小由原来的大号变成中号。
    知识点:
    如果要计算textview的行数,可以自定义这个textview,并且在onMeasure方法中处理。因为onMeasure函数是在textview真正要开始布局的时候会执行,并且很多情况下可能会执行多次。

            final Layout layout = getLayout();
            if (layout != null) {
                final int lineCount = layout.getLineCount();
                if (lineCount > 0) {
    

    这里可以跟进getLayout的源码,这个layout必须判空,注释里说当这个textview的text或者width最近修改了,这个可能会为空的。
    之前在项目开发中通过textPaint的各种手段计算出textview的行数,今天又发现了另外一种方法。

    之前一直没有对mContext.obtainStyledAttributes做好好的分析,自己自定义view的时候一般用一些模板代码套用。

    final TypedArray a = mContext.obtainStyledAttributes(
     null,
     android.R.styleable.TextAppearance, 
     android.R.attr.textAppearanceMedium,
     android.R.style.TextAppearance_Medium);
    

    第一个参数是xml文件中的定义的属性集(理解为键值对),
    第二个参数是R.styleable.TextAppearance即为要取出typeArray的目标属性(理解为键),
    第三个是系统当前theme下默认的属性集(理解为建值对),
    第四个是备用的一个style(理解为键值对),当第三个属性 找不到或者为0, 可以直接指定某个style。

    第二个参数android.R.styleable.TextAppearance在源码的attrs.xml的

    <declare-styleable name="TextAppearance">
            <!-- Text color. -->
            <attr name="textColor" />
            <!-- Size of the text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp). -->
            <attr name="textSize" />
            <!-- Style (bold, italic, bolditalic) for the text. -->
            <attr name="textStyle" />
            <!-- Typeface (normal, sans, serif, monospace) for the text. -->
            <attr name="typeface" />
            <!-- Font family (named by string) for the text. -->
            <attr name="fontFamily" />
            <!-- Color of the text selection highlight. -->
            <attr name="textColorHighlight" />
            <!-- Color of the hint text. -->
            <attr name="textColorHint" />
            <!-- Color of the links. -->
            <attr name="textColorLink" />
            <!-- Present the text in ALL CAPS. This may use a small-caps form when available. -->
            <attr name="textAllCaps" format="boolean" />
    

    第三个参数android.R.attr.textAppearanceMedium在源码的attrs.xml的

    <declare-styleable name="Theme">
    <attr name="textAppearanceMedium" format="reference" />
    </declare-styleable>
    

    这里的格式是format="reference"即需要引用其它的属性,然后我在themes.xml中找到了该引用的地方:

    <style name="Theme">
    <item name="textAppearanceMedium">@style/TextAppearance.Medium</item>
    </style>
    <style name="Theme.Dialog">
    <item name="textAppearanceMedium">@style/TextAppearance.Medium</item>
    </style>
    

    发现这里有两个地方是textAppearanceMedium,但是一个是Theme,一个是Theme.Dialog,说明不同的theme 指向不同的style,虽然这里还是指向同一个style。。。

    第四个参数在源码的styles.xml中

    <style name="TextAppearance.Medium">
            <item name="textSize">18sp</item>
        </style>
    

    【摘抄】几个参数的优先级如下示:“>大于符号” xml里的显示定义如 bar:attr1="12345">xml里的style定义如:android:style=@style/test>当前theme>备用Style。android系统会按照优先级依次去查找。大家有兴趣可以自己做个实验看一看。

    上述解释需要好好体会一下,总体来说

    final TypedArray a = mContext.obtainStyledAttributes(
     null,
     android.R.styleable.TextAppearance, 
     android.R.attr.textAppearanceMedium,
     android.R.style.TextAppearance_Medium);
    

    这段代码就是获取一个属性值,把android.R.styleable.TextAppearance里的textSize的属性用@style/TextAppearance.Medium的

    <item name="textSize">18sp</item>
    

    来代替。因为第三个参数是表示系统当前theme下默认的属性集是这个android.R.attr.textAppearanceMedium,这里做法仅仅是代码中临时调整,并不会真的改变当前theme下的默认属性集,当前theme是在activity或application中配置的。

    一般自定义view获取属性模板代码

    TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.button); 
        this.setTextSize(typedArray.getDimension(R.styleable.button_textSize, 15)); 
        typedArray.recycle();
    

    我们通过下面的代码

    final int textSize = a.getDimensionPixelSize(
    android.R.styleable.TextAppearance_textSize, 0);
    

    取出来的textSize是px的单位,所以给textview设置的时候要指定单位。
    android-getTextSize返回值是以像素(px)为单位的,setTextSize()以sp为单位
    使用如下代码时,发现字号不会变大,反而会变小:
    size = (int) mText.getTextSize() + 1;
    mText.setTextSize(size);
    后来发现getTextSize返回值是以像素(px)为单位的,而setTextSize()是以sp为单位的,两者单位不一致才造成这样的结果。

    这里可以用setTextSize()的另外一种形式,可以指定单位:
    setTextSize(int unit, int size)
    TypedValue.COMPLEX_UNIT_PX : Pixels
    TypedValue.COMPLEX_UNIT_SP : Scaled Pixels
    TypedValue.COMPLEX_UNIT_DIP : Device Independent Pixels

    下面这样就正常了:
    size = (int) mText.getTextSize() + 1;
    mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);

    相关文章

      网友评论

          本文标题:Dialog源码学习笔记

          本文链接:https://www.haomeiwen.com/subject/arcwsttx.html