View的創(chuàng)建,measure(),layout(),invalidate()源碼分析

View源碼分析

簡(jiǎn)述

  • 主要分析從XML資源文件中生成View對(duì)象過程;
  • 以及View的構(gòu)造函數(shù),measure(),layout()方法分析;
  • invalidate()請(qǐng)求刷新重繪視圖過程分析;
  • View自身touch事件處理onTouchEvent()方法分析

View對(duì)象的生成

一般View對(duì)象的生成有兩種,從XML文件中通過LayoutInflater(子類PhoneLayoutInflater)的inflate()經(jīng)反射生成對(duì)象,一種是直接new View(Context)直接通過構(gòu)造函數(shù)生成;

在Activity.onCreate()中的setContentView(@LayoutRes int layoutResID),最終調(diào)用的是其window(PhoneWindow)的setContentView(int layoutResID):

@Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        // 填充資源文件中視圖到activity的window的頂層視圖內(nèi)容部分
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

PhoneWindow是在Activity.attach()中生成的,且傳入的Context是當(dāng)前Activity對(duì)象本身,并由window的final Context mContext成員變量引用;

在為window初始化頂層視圖DecorView后,會(huì)調(diào)用LayoutInflater的inflate(@LayoutRes int resource, @Nullable ViewGroup root)方法,下面具體看下該過程:

// root參數(shù)為由資源文件生成的view的父view
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

// attachToRoot參數(shù)表示是否將生成的view 添加add到root父view中
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }
    // 從資源文件中解析出"layout"即與布局相關(guān)的Parser對(duì)象
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

// 主要方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
        final Context inflaterContext = mContext;
        // 轉(zhuǎn)換為AttributeSet屬性集合
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            // Look for the root node.
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }
            
            final String name = parser.getName();
            
            if (DEBUG) {
                System.out.println("**************************");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("**************************");
            }
            // 首先看首節(jié)點(diǎn)是否屬于"merge"標(biāo)簽,如果inflate時(shí)候沒有給予父view,則會(huì)拋出異常
            if (TAG_MERGE.equals(name)) {
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }

                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
                // 真正生成view的地方
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    // 傳入給予的父view不為空,則根據(jù)屬性集合在父view中為當(dāng)前生成的view生成一個(gè)LayoutParams布局參數(shù)對(duì)象
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        // 如果傳入父view不為空,一般attachToRoot==true,就不會(huì)在這里直接為生成的view賦值LayoutParams
                        temp.setLayoutParams(params);
                    }
                }

                if (DEBUG) {
                    System.out.println("-----> start inflating children");
                }

                // Inflate all children under temp against its context.
                // 這里去為當(dāng)前生成的view填充其節(jié)點(diǎn)下面的子view
                rInflateChildren(parser, temp, attrs, true);

                if (DEBUG) {
                    System.out.println("-----> done inflating children");
                }

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    // 這里將由xml生成的view樹addview到傳入的父view中
                    root.addView(temp, params);
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    // 如果調(diào)用inflate()沒有傳入root父view,或者attachToRoot==false,則當(dāng)前方法返回的對(duì)象是反射生成的view本身,否則返回的其實(shí)是父view即傳入的root參數(shù)
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            InflateException ex = new InflateException(e.getMessage());
            ex.initCause(e);
            throw ex;
        } catch (Exception e) {
            InflateException ex = new InflateException(
                    parser.getPositionDescription()
                            + ": " + e.getMessage());
            ex.initCause(e);
            throw ex;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return result;
    }
}

看下createViewFromTag()的實(shí)現(xiàn)

private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    return createViewFromTag(parent, name, context, attrs, false);
}

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    // Apply a theme wrapper, if allowed and one is specified.
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }

    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    try {
        View view;
        // 一般情況這里的mFactory2==null,mFactory==null
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        // 在acitivity.attach()中為其window的LayoutInflater mLayoutInflater設(shè)置了mPrivateFactory = acitivity本身.
        // 這里主要用于在xml中直接使用Fragment標(biāo)簽,會(huì)去acitivity中生成該Fragment對(duì)象并返回其Fragment.onCreatView()中返回的該fragment封裝的view
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    // 通過反射生成Android系統(tǒng)view
                    view = onCreateView(parent, name, attrs);
                } else {
                    // 通過反射生成我們自定義的view
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } catch (InflateException e) {
        throw e;

    } catch (ClassNotFoundException e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name);
        ie.initCause(e);
        throw ie;

    } catch (Exception e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name);
        ie.initCause(e);
        throw ie;
    }
}

看下為當(dāng)前節(jié)點(diǎn)view填充子節(jié)點(diǎn)view所調(diào)用的rInflateChildren()方法過程:

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();
    int type;

    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        final String name = parser.getName();
        
        if (TAG_REQUEST_FOCUS.equals(name)) {
            parseRequestFocus(parser, parent);
        } else if (TAG_TAG.equals(name)) {
            // 解析"tag"標(biāo)簽節(jié)點(diǎn)
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            // 解析"include"標(biāo)簽節(jié)點(diǎn)
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            // 由此可見"merge"節(jié)點(diǎn)只能是首節(jié)點(diǎn)
            throw new InflateException("<merge /> must be the root element");
        } else {
            // 這里同之前一樣,先生成該節(jié)點(diǎn)view對(duì)象,然后循環(huán)填充該節(jié)點(diǎn)view的子view,最后會(huì)將該節(jié)點(diǎn)addview到該節(jié)點(diǎn)view的上一層級(jí)節(jié)點(diǎn)view即傳入父節(jié)點(diǎn)view中.
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }
    // 通知該父節(jié)點(diǎn)View其內(nèi)部所有子view已經(jīng)由xml打入填充完畢
    if (finishInflate) {
        parent.onFinishInflate();
    }
}
  • 可以看出,在調(diào)用Acitivity.setContentView()方法完成后,已經(jīng)將視圖樹建立完畢,即將資源文件各個(gè)view節(jié)點(diǎn)分別通過反射生成對(duì)象然后addview到父view中,且將xml資源文件的根view填充依附到window的頂層視圖容器DecorView的內(nèi)容部分mContentParent中.這一過程中,所有view都在構(gòu)造函數(shù)中做了初始化,且都設(shè)置了由父view容器調(diào)用generateLayoutParams()生成對(duì)應(yīng)的LayoutParams對(duì)象.
  • 手動(dòng)調(diào)用LayoutInflater.inflate()資源文件的時(shí)候,最大的區(qū)別就在inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 中的root參數(shù)和attachToRoot參數(shù)設(shè)置,如果root為null則返回?zé)o設(shè)置LayoutParams()的xml根節(jié)點(diǎn)view對(duì)象,若root!=null,但是attachToRoot==false則返回是設(shè)置LayoutParams()的xml根節(jié)點(diǎn)view對(duì)象,若root!=null同時(shí)attachToRoot==true則將xml根view生成LayoutParams()且通過父view即root依附addview到root上

View的構(gòu)造函數(shù)

view的構(gòu)造函數(shù)有5個(gè),除了無參數(shù)的構(gòu)造函數(shù)一般最后都調(diào)用如下2個(gè):

public View(Context context) {
    mContext = context;
    mResources = context != null ? context.getResources() : null;
    mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
    // Set some flags defaults
    mPrivateFlags2 =
            (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
            (TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
            (PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
            (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
            (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
            (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
    mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
    mUserPaddingStart = UNDEFINED_PADDING;
    mUserPaddingEnd = UNDEFINED_PADDING;
    mRenderNode = RenderNode.create(getClass().getName(), this);

    if (!sCompatibilityDone && context != null) {
        final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;

        // Older apps may need this compatibility hack for measurement.
        sUseBrokenMakeMeasureSpec = targetSdkVersion <= JELLY_BEAN_MR1;

        // Older apps expect onMeasure() to always be called on a layout pass, regardless
        // of whether a layout was requested on that View.
        sIgnoreMeasureCache = targetSdkVersion < KITKAT;

        Canvas.sCompatibilityRestore = targetSdkVersion < M;

        // In M and newer, our widgets can pass a "hint" value in the size
        // for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
        // know what the expected parent size is going to be, so e.g. list items can size
        // themselves at 1/3 the size of their container. It breaks older apps though,
        // specifically apps that use some popular open source libraries.
        sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M;

        sCompatibilityDone = true;
    }
}



public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    this(context);

    final TypedArray a = context.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);

    if (mDebugViewAttributes) {
        saveAttributeData(attrs, a);
    }

    Drawable background = null;

    int leftPadding = -1;
    int topPadding = -1;
    int rightPadding = -1;
    int bottomPadding = -1;
    int startPadding = UNDEFINED_PADDING;
    int endPadding = UNDEFINED_PADDING;

    int padding = -1;

    int viewFlagValues = 0;
    int viewFlagMasks = 0;

    boolean setScrollContainer = false;

    int x = 0;
    int y = 0;

    float tx = 0;
    float ty = 0;
    float tz = 0;
    float elevation = 0;
    float rotation = 0;
    float rotationX = 0;
    float rotationY = 0;
    float sx = 1f;
    float sy = 1f;
    boolean transformSet = false;

    int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
    int overScrollMode = mOverScrollMode;
    boolean initializeScrollbars = false;
    boolean initializeScrollIndicators = false;

    boolean startPaddingDefined = false;
    boolean endPaddingDefined = false;
    boolean leftPaddingDefined = false;
    boolean rightPaddingDefined = false;

    final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;

    final int N = a.getIndexCount();
    for (int i = 0; i < N; i++) {
        int attr = a.getIndex(i);
        switch (attr) {
        ....

        }

    }
    ....
    setOverScrollMode(overScrollMode);
    if (background != null) {
        setBackground(background);
    }
    internalSetPadding(
            mUserPaddingLeftInitial,
            topPadding >= 0 ? topPadding : mPaddingTop,
            mUserPaddingRightInitial,
            bottomPadding >= 0 ? bottomPadding : mPaddingBottom);
    if (x != 0 || y != 0) {
        scrollTo(x, y);
    }

    if (transformSet) {
        setTranslationX(tx);
        setTranslationY(ty);
        setTranslationZ(tz);
        setElevation(elevation);
        setRotation(rotation);
        setRotationX(rotationX);
        setRotationY(rotationY);
        setScaleX(sx);
        setScaleY(sy);
    }
    ....

}

可以看出,view的構(gòu)造函數(shù)設(shè)置了初始化了一些基本屬性,比如Context,mRenderNode等,主要還是解析并獲取了對(duì)應(yīng)屬性集合中一些參數(shù)并賦值給成員變量,比如mBackground,mPaddingTop,mPaddingLeft,mPaddingRight,mPaddingBottom等等,為mRenderNode賦值設(shè)置translationX,translationY,rotationX等相關(guān)顯示參數(shù)等;

measure()測(cè)量方法

view的measure()是由父view分發(fā)調(diào)用(最開始是從ViewRootImpl的performMeasure()測(cè)量DecroView開始的)的,傳入的參數(shù)widthMeasureSpec和heightMeasureSpec,是由32位int值來表示,高2位代表該view的測(cè)量模式,剩余30位代表了實(shí)際了父view根據(jù)自身尺寸情況和子view布局參數(shù)生成的一個(gè)期望值.

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // 通常沒有設(shè)置layoutMode,這里為false
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }

    // Suppress sign extension for the low bytes
    // 生成mMeasureCache Long-long稀疏數(shù)組中保存測(cè)量值的key
    long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
    // 這里看flag的PFLAG_FORCE_LAYOUT位是否被強(qiáng)制置位,或者測(cè)量參數(shù)與上次是否有不一樣
    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {

        // first clears the measured dimension flag
        // 清除flag的PFLAG_MEASURED_DIMENSION_SET位,表明view當(dāng)前沒有設(shè)置尺寸
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        resolveRtlPropertiesIfNeeded();
        // 查詢保存在緩存中是否有當(dāng)前測(cè)量值的key,有的話同時(shí)flag的PFLAG_FORCE_LAYOUT位沒有強(qiáng)制設(shè)置的話則不需要重新onMeasure()測(cè)量
        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            // 這里去具體設(shè)置該view的測(cè)量寬mMeasuredWidth和高mMeasuredHeight
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            // 清除flag3的PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT位
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            // Casting a long to int drops the high 32 bits, no mask needed
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            // 如果是直接拿緩存沒有走onMeasure()則在這里置該位,在layout()中再去onMeasure()
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        // flag not set, setMeasuredDimension() was not invoked, we raise
        // an exception to warn the developer
        // 這里檢查一下,防止子類重新沒有真正為該view賦值設(shè)置測(cè)量寬高
        if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
            throw new IllegalStateException("View with id " + getId() + ": "
                    + getClass().getName() + "#onMeasure() did not set the"
                    + " measured dimension by calling"
                    + " setMeasuredDimension()");
        }
        // 置flag的PFLAG_LAYOUT_REQUIRED位表明需要走layout()
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
    // 保存當(dāng)前由父view下發(fā)的測(cè)量值
    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;

    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

// View默認(rèn)的onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

// 這里可以看出,view默認(rèn)的測(cè)量中l(wèi)ayoutParams寬高設(shè)置為match_parent和wrap_content效果一樣,均為父view計(jì)算給出的size
// 所以自定義view一般要重寫onMeasure()區(qū)分設(shè)置layoutParams為wrap_content情況
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}


protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    // 一般情況沒有設(shè)置layoutmode時(shí)候,為false
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int opticalWidth  = insets.left + insets.right;
        int opticalHeight = insets.top  + insets.bottom;

        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

// 這里真正為該view賦值mMeasuredWidth和mMeasuredHeight,同時(shí)標(biāo)記flag的PFLAG_MEASURED_DIMENSION_SET位,表明已經(jīng)設(shè)置好測(cè)量寬高
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

view的measure方法是final修飾的,通常在派生類中重新onMeasure()方法可以根據(jù)layoutParams的情況(match_parent或是wrap_content)重新設(shè)置賦值測(cè)量的寬和高.對(duì)于自定義ViewGroup,還要在onMeasure()中去遍歷測(cè)量其子view.

layout()布局方法

View的layout()方法也是由父view分發(fā)調(diào)用的,最開始是從ViewRootImpl的performLayout()開始的

/**
 * @param l Left position, relative to parent 該view左邊界距離父容器view左邊界距離
 * @param t Top position, relative to parent 該view上邊界距離父容器view上邊界距離
 * @param r Right position, relative to parent 該view右邊界距離父容器view左邊界距離
 * @param b Bottom position, relative to parent 該view下邊界距離父容器view上邊界距離
 */
// 很明顯,傳入的參數(shù)分別是當(dāng)前view相對(duì)父view的左邊界,上邊界,由邊界,下邊界的距離
public void layout(int l, int t, int r, int b) {
    // 這里如果在上面的measeure()中是直接從緩存中拿的沒有走onMeasure()的話要去走一次onMeasure()
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    // 首先記錄上次該view四邊與其父view左邊界,上邊界相應(yīng)的距離,僅僅用于通知位置改變回調(diào)
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    // ture 表示該view在父view中位置發(fā)生變化,即該view有一邊與其父view左or上邊界相應(yīng)的距離發(fā)生改變
    // 真正改變位置的一般是在setFrame()中
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    // 如果該view位置改變,或者flag的PFLAG_LAYOUT_REQUIRED被強(qiáng)制置位,則去重新onLayout()布局子view
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        // 當(dāng)前該view位置在其父view中位置變化了,則要重新去布局其子view,當(dāng)然如果有子view的話
        // 一般想系統(tǒng)的LinearLayout,FrameLayout,RelativeLayout以及自定義viewGroup進(jìn)要重新該方法
        // 去分配設(shè)置子view的相對(duì)該view的位置,View.java默認(rèn)為空實(shí)現(xiàn),ViewGroup一般要去重寫該方法
        onLayout(changed, l, t, r, b);
        // 自己和子view都已經(jīng)分配好位置后,清除flag的PFLAG_LAYOUT_REQUIRED位
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        // 通知位置變化的監(jiān)聽回調(diào)
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }
    // 清除flag的PFLAG_FORCE_LAYOUT位
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

// 為當(dāng)前view指定其在父容器view中具體位置的是在該方法中
protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (DBG) {
        Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                + right + "," + bottom + ")");
    }

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        // Remember our drawn bit
        // 首先記錄當(dāng)前的flag的PFLAG_DRAWN位
        int drawn = mPrivateFlags & PFLAG_DRAWN;

        // 每個(gè)view都是一個(gè)矩形,實(shí)際寬度為對(duì)應(yīng)邊界
        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        // Invalidate our old position
        // 該view的位置即將改變,先去刷新一遍該view的當(dāng)前視圖區(qū)域
        invalidate(sizeChanged);
        // 分配賦值新位置,同時(shí)更新RenderNode mRenderNode的參數(shù)
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
        // 置位flag的PFLAG_HAS_BOUNDS位,表示該view有邊界束縛范圍了
        mPrivateFlags |= PFLAG_HAS_BOUNDS;


        // view寬度or高度變化的方法通知
        if (sizeChanged) {
            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
        }

        if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
            // If we are visible, force the DRAWN bit to on so that
            // this invalidate will go through (at least to our parent).
            // This is because someone may have invalidated this view
            // before this call to setFrame came in, thereby clearing
            // the DRAWN bit.
            // 因?yàn)橐粋€(gè)invalidate()過程開始進(jìn)入后會(huì)清除flag的PFLAG_DRAWN位,直到當(dāng)次UI繪制結(jié)束會(huì)重新置該位表示當(dāng)次UI繪制完成
            // 所以這里為了保證一定走該方法去刷新視圖,故先強(qiáng)制設(shè)置
            mPrivateFlags |= PFLAG_DRAWN;
            // 這里是真正去刷新位置變化后該view的視圖了
            invalidate(sizeChanged);
            // parent display list may need to be recreated based on a change in the bounds
            // of any child
            invalidateParentCaches();
        }

        // Reset drawn bit to original value (invalidate turns it off)
        // 在invalidate()方法調(diào)用之后,恢復(fù)之前 的flag的PFLAG_DRAWN位
        mPrivateFlags |= drawn;
        // 該view位置變化的同時(shí)告知其背景size改變了
        mBackgroundSizeChanged = true;
        if (mForegroundInfo != null) {
            mForegroundInfo.mBoundsChanged = true;
        }

        notifySubtreeAccessibilityStateChangedIfNeeded();
    }
    return changed;
}

從上面看出,view的layout()主要為該view指定其在父容器view中位置,且會(huì)兩次調(diào)用invalidate()方法去刷新UI,之后會(huì)走onLayout()方法,如果該view本身是一個(gè)容器ViewGroup子類的話,則需要在該方法中調(diào)用子view的layout()方法為其子view分配指定相對(duì)其左邊界和上邊界的相對(duì)位置

invalidate()

這個(gè)方法通常用來刷新某個(gè)view的UI視圖,主要是會(huì)調(diào)用onDraw()方法;

該方法內(nèi)部十分復(fù)雜,下面簡(jiǎn)要分析其大概過程

/**
 * Invalidate the whole view. If the view is visible,
 * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
 * the future.
 * <p>
 * This must be called from a UI thread. To call from a non-UI thread, call
 * {@link #postInvalidate()}.
 */
// 由注釋可知,調(diào)用該方法必須在主線程且會(huì)在隨后調(diào)用onDraw()方法
public void invalidate() {
    invalidate(true);
}

/**
 * This is where the invalidate() work actually happens. A full invalidate()
 * causes the drawing cache to be invalidated, but this function can be
 * called with invalidateCache set to false to skip that invalidation step
 * for cases that do not need it (for example, a component that remains at
 * the same dimensions with the same content).
 *
 * @param invalidateCache Whether the drawing cache for this view should be
 *            invalidated as well. This is usually true for a full
 *            invalidate, but may be set to false if the View's contents or
 *            dimensions have not changed.
 */
// invalidateCache為ture會(huì)使view的flag的置位PFLAG_INVALIDATED,清除PFLAG_DRAWING_CACHE_VALID位
void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
    if (mGhostView != null) {
        mGhostView.invalidate(true);
        return;
    }
    // 一般如果該view不是VISIBLE狀態(tài)且不處于動(dòng)畫狀態(tài)則中斷刷新UI
    if (skipInvalidate()) {
        return;
    }

    // 首先判斷PFLAG_DRAWN位上傳UI繪制是否完成,PFLAG_HAS_BOUNDS位是否已經(jīng)指定該view四邊界位置,在layout()的setFrame()方法調(diào)用中置位了
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
            || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
            || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
            || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque();
            // 這里清楚該位表明當(dāng)次UI繪制已經(jīng)開始沒有完成
            mPrivateFlags &= ~PFLAG_DRAWN;
        }

        mPrivateFlags |= PFLAG_DIRTY;
        // PFLAG_INVALIDATED表示需要重新創(chuàng)建View的DisplayListCanvas
        // PFLAG_DRAWING_CACHE_VALID表示view的Bitmap drawing cache緩存有效可用,清除該位表明需要
        // 去創(chuàng)建該view的Bitmap drawing cache
        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        // Propagate the damage rectangle to the parent view.
        // mAttachInfo這個(gè)成員變量是在繪制顯示窗口頂層視圖時(shí)候創(chuàng)建ViewRootImpl這個(gè)頂級(jí)ViewParent對(duì)象的時(shí)候生成的
        // 并在performTraversals()中調(diào)用host.dispatchAttachedToWindow(mAttachInfo, 0)方法沿著view樹體系向下賦值引用
        final AttachInfo ai = mAttachInfo;
        // 每個(gè)view的容器父view都賦值引用到子View的mParent成員變量,最頂層的容器View即DecorView的mParent指向了頂級(jí)ViewParent即ViewRootImpl對(duì)象
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);
        }

        // Damage the entire projection receiver, if necessary.
        if (mBackground != null && mBackground.isProjected()) {
            final View receiver = getProjectionReceiver();
            if (receiver != null) {
                receiver.damageInParent();
            }
        }

        // Damage the entire IsolatedZVolume receiving this view's shadow.
        if (isHardwareAccelerated() && getZ() != 0) {
            damageShadowReceiver();
        }
    }
}

由上面可以看出,view.invalidate()最后是把其自身邊界尺寸傳遞到上一層父view中,讓父view調(diào)用invalidateChild()方法,一般父容器view是ViewGroup這個(gè)實(shí)現(xiàn)了ViewParent的抽象類的派生類,最頂層的容器View即DecorView的mParent指向了頂級(jí)ViewParent即ViewRootImpl,下面看下ViewGroup的invalidateChild()方法:

public final void invalidateChild(View child, final Rect dirty) {

    ViewParent parent = this;
    ....
    final int[] location = attachInfo.mInvalidateChildLocation;
    location[CHILD_LEFT_INDEX] = child.mLeft;
    location[CHILD_TOP_INDEX] = child.mTop;

    ....

    do {
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }

            if (drawAnimation) {
                if (view != null) {
                    view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                } else if (parent instanceof ViewRootImpl) {
                    ((ViewRootImpl) parent).mIsAnimating = true;
                }
            }

            // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
            // flag coming from the child that initiated the invalidate
            if (view != null) {
                if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                        view.getSolidColor() == 0) {
                    opaqueFlag = PFLAG_DIRTY;
                }
                if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                    view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                }
            }
            // 重點(diǎn)方法,這里parent先就是當(dāng)前this對(duì)象
            parent = parent.invalidateChildInParent(location, dirty);
            if (view != null) {
                // Account for transform on current parent
                Matrix m = view.getMatrix();
                if (!m.isIdentity()) {
                    RectF boundingRect = attachInfo.mTmpTransformRect;
                    boundingRect.set(dirty);
                    m.mapRect(boundingRect);
                    dirty.set((int) (boundingRect.left - 0.5f),
                            (int) (boundingRect.top - 0.5f),
                            (int) (boundingRect.right + 0.5f),
                            (int) (boundingRect.bottom + 0.5f));
                }
            }
        } while (parent != null);

}


/**
 * Don't call or override this method. It is used for the implementation of
 * the view hierarchy.
 *
 * This implementation returns null if this ViewGroup does not have a parent,
 * if this ViewGroup is already fully invalidated or if the dirty rectangle
 * does not intersect with this ViewGroup's bounds.
 */
// 聯(lián)合設(shè)置統(tǒng)一當(dāng)前需要重繪的UI視圖矩形與該view本身的四邊界
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
            (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
        if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
                    FLAG_OPTIMIZE_INVALIDATE) {
            dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                    location[CHILD_TOP_INDEX] - mScrollY);
            if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
            }

            final int left = mLeft;
            final int top = mTop;

            if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                    dirty.setEmpty();
                }
            }
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

            location[CHILD_LEFT_INDEX] = left;
            location[CHILD_TOP_INDEX] = top;

            if (mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
            }

            return mParent;

        } else {
            mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;

            location[CHILD_LEFT_INDEX] = mLeft;
            location[CHILD_TOP_INDEX] = mTop;
            if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
            } else {
                // in case the dirty rect extends outside the bounds of this container
                dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
            }

            if (mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
            }

            return mParent;
        }
    }

    return null;
}

invalidateChild()里面的do..while()循環(huán)一直不端的調(diào)用invalidateChildInParent(),invalidateChildInParent()返回父ViewParent并聯(lián)合設(shè)置統(tǒng)一當(dāng)前需要重繪的UI矩形與view本身四邊界,直到view樹向上到達(dá)ViewRootImpl的invalidateChildInParent()方法返回null中斷循環(huán),看下頂層ViewParent實(shí)現(xiàn)類的ViewRootImpl的invalidateChildInParent()方法;

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    // 檢查方法調(diào)用的線程是否就是UI線程即APP主線程,不是則拋出異常,這也是為何invaldate()要在主線程調(diào)用的直接原因
    checkThread();
    if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);

    if (dirty == null) {
        invalidate();
        return null;
    } else if (dirty.isEmpty() && !mIsAnimating) {
        return null;
    }

    if (mCurScrollY != 0 || mTranslator != null) {
        mTempRect.set(dirty);
        dirty = mTempRect;
        if (mCurScrollY != 0) {
            dirty.offset(0, -mCurScrollY);
        }
        if (mTranslator != null) {
            mTranslator.translateRectInAppWindowToScreen(dirty);
        }
        if (mAttachInfo.mScalingRequired) {
            dirty.inset(-1, -1);
        }
    }

    invalidateRectOnScreen(dirty);

    return null;
}

// mThread即當(dāng)前APP進(jìn)程主線程,在ViewRootImpl構(gòu)造函數(shù)中賦值,因?yàn)閂iewRootImpl必然是在主線程創(chuàng)建的
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

// 合并由view樹體系向上傳來的需要重繪的區(qū)域與成員變量mDirty區(qū)域,并保存在成員變量mDirty中
// 之后調(diào)用scheduleTraversals()向主線程Looper的MessageQueue消息隊(duì)列發(fā)送一個(gè)Message去請(qǐng)求刷新視圖了
private void invalidateRectOnScreen(Rect dirty) {
    final Rect localDirty = mDirty;
    if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
        mAttachInfo.mSetIgnoreDirtyState = true;
        mAttachInfo.mIgnoreDirtyState = true;
    }

    // Add the new dirty rect to the current one
    localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
    // Intersect with the bounds of the window to skip
    // updates that lie outside of the visible region
    final float appScale = mAttachInfo.mApplicationScale;
    final boolean intersected = localDirty.intersect(0, 0,
            (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    if (!intersected) {
        localDirty.setEmpty();
    }
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

如果當(dāng)前已經(jīng)有一個(gè)UI刷新過程在進(jìn)行中的時(shí)候,mWillDrawSoon為true,則不再去刷新視圖了;

onTouchEvent()

不管是ViewGroup容器還是View本身,首先都是一個(gè)View,作為View都有自身的基本的touch事件處理邏輯,即View類中的onTouchEvent()方法;
如果是ViewGroup的話,一般是沒有子view處理分發(fā)的事件或者自己攔截了事件強(qiáng)制分發(fā)到自身處理,然后調(diào)用其onTouchEvent()方法

PS:寫到這里,不想在這篇文章分析view的touch事件處理了,今天中秋節(jié),想想很有空,還是寫一篇View和ViewGroup的事件分發(fā),攔截,自身處理的源碼分析文章.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末孽尽,一起剝皮案震驚了整個(gè)濱河市阔逼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拧咳,老刑警劉巖乎澄,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壤短,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡雁社,警方通過查閱死者的電腦和手機(jī)浴井,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霉撵,“玉大人磺浙,你說我怎么就攤上這事⊥狡拢” “怎么了撕氧?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長喇完。 經(jīng)常有香客問我伦泥,道長,這世上最難降的妖魔是什么锦溪? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任不脯,我火速辦了婚禮,結(jié)果婚禮上海洼,老公的妹妹穿的比我還像新娘跨新。我一直安慰自己,他們只是感情好坏逢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布域帐。 她就那樣靜靜地躺著赘被,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肖揣。 梳的紋絲不亂的頭發(fā)上民假,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音龙优,去河邊找鬼羊异。 笑死,一個(gè)胖子當(dāng)著我的面吹牛彤断,可吹牛的內(nèi)容都是我干的野舶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼宰衙,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼平道!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起供炼,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤一屋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后袋哼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冀墨,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年涛贯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诽嘉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疫蔓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出衅胀,到底是詐尸還是另有隱情岔乔,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布滚躯,位于F島的核電站雏门,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏掸掏。R本人自食惡果不足惜茁影,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丧凤。 院中可真熱鬧募闲,春花似錦、人聲如沸愿待。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至要出,卻和暖如春鸳君,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背患蹂。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工或颊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人传于。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓囱挑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親格了。 傳聞我的和親對(duì)象是個(gè)殘疾皇子看铆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容