Android 自定义View学习(十一)——ViewGroup测量知识学习

学习资料

上篇学习了View的测量方法,了解一些Android UI架构图的知识,这篇记录学习ViewGroup的测量


1. ViewGroup <p>

A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers. This class also defines the ViewGroup.LayoutParams class which serves as the base class for layouts parameters.

直译: ViewGroup是一个可以包含其他子View特殊的View。并且是那些子View或者布局的父容器。而且ViewGroup定义了ViewGroup.LayoutParams这个类

ViewGroup是一个抽象类,内部的子View可以是一个View也可以是另一个ViewGroup

例如,在LinearLayout中,可以加入一个TextView也可以加入另外一个LinearLayout


ViewGroup的职责

ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属性,都是为用于告诉容器的),我们的宽度(layout_width)、高度(layout_height)、对齐方式(layout_gravity)等;当然还有margin等;于是乎,ViewGroup的职能为:给childView计算出建议的宽和高和测量模式 ;决定childView的位置;为什么只是建议的宽和高,而不是直接确定呢,别忘了childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。

View的职责

View的职责,根据测量模式和ViewGroup给出的建议的宽和高,计算出自己的宽和高;同时还有个更重要的职责是:在ViewGroup为其指定的区域内绘制自己的形态。

以上摘抄鸿洋大神的Android 手把手教您自定义ViewGroup(一)


2. 测量方法 <p>

View的测量大小除了自身还会受父容器的影响。一般这个父容器就是一个ViewGroup。对于一个ViewGroup来说,除了完成自身的测量外,还要遍历内部的childView的测量方法,各个childView再递归执行这个步骤。

ViewGroup源代码内并没有重写onMeasure()方法,而是提供了几个测量相关的方法。

原因也比较容易理解,由于ViewGroup是一个抽象类,有不同的子类childView,有不同的布局属性,测量的细节不同。例如LinearLayputRelativeLayout。每个继承之ViewGroupLayout,各自根据自身的布局属性来重写onMeasure()方法


2.1 测量的过程 <p>

ViewGroup的测量过程主要用到了三个方法

  1. measureChildren() ,遍历所有的childView
  2. getChildMeasureSpec(),确定测量规格
  3. measureChild(),调用测量规格。这个方法内,根据2确定好的测量规格,childView调用了measure()方法,而measure()内部调用的方法就有onMeasure()

2.1.1 measureChildren() 遍历所有的childView <p>

源码:

/**
 * Ask all of the children of this view to measure themselves, taking into account both the MeasureSpec requirements for this view and its padding.
 *
 * We skip children that are in the GONE state The heavy liftingis done in getChildMeasureSpec.
 *
 * @param widthMeasureSpec The width requirements for this view
 * @param heightMeasureSpec The height requirements for this view
 */
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) { //进行遍历
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {//确定childview是否可见
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

方法内主要就是遍历了所有的chlidView,判断每个childViewvisibility值,确定当前的这个childView可见,然后调用了measureChild(child, widthMeasureSpec, heightMeasureSpec)方法


2.1.2 measureChild(),调用测量规格 <p>

把这个方法放在getChildMeasureSpec()确定测量规格之前,是因为measureChild()内部调用了getChildMeasureSpec()

源码:

    /**
     * Ask one of the children of this view to measure itself, taking into account both the MeasureSpec requirements for this view and its padding.
     *
     * The heavy lifting is done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param parentHeightMeasureSpec The height requirements for this view
     */
    protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {
        // 获取childView的布局参数
        final LayoutParams lp = child.getLayoutParams();

        //将ViewGroup的测量规格,上下和左右的边距还有childView自身的宽高传入getChildMeasureSpec方法计算最终测量规格 
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);

        //调用childView的measure(),measure()方法内就是回调`onMeasure()`方法
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

Viewmeasure()测量方法调用过程,在上篇View的测量方法学习过程中,只是用文字简单概括了几句,并没有记录学习源码方法的调用过程,可以去爱哥的自定义控件其实很简单7/12进行补充学习 : )


2.1.3 getChildMeasureSpec(),确定childview的测量规格 <p>

源码:

    /**
     * Does the hard part of measureChildren: figuring out the MeasureSpec to pass to a particular child. This method figures out the right MeasureSpec for one dimension (height or width) of one child view.
     *
     * The goal is to combine information from our MeasureSpec with the LayoutParams of the child to get the best possible results. For example, if the this view knows its size (because its MeasureSpec has a mode of EXACTLY), and the child has indicated in its LayoutParams that it wants to be the same size as the parent, the parent should ask the child to layout given an exact size.
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and margins, if applicable
     * @param childDimension How big the child wants to be in the current dimension
     * @return a MeasureSpec integer for the child
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //ViewGroup的测量模式及大小
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        //将ViewGroup的测量大小减去内边距
        int size = Math.max(0, specSize - padding);

        // 声明临时变量存值  
        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        case MeasureSpec.EXACTLY://ViewGroup的测量模式为精确模式
            //根据childView的布局参数判断 
            if (childDimension >= 0) {//如果childDimension是一个具体的值  
                // 将childDimension赋予resultSize ,作为结果
                resultSize = childDimension;
                //将临时resultMode 也设置为精确模式
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {//childView的布局参数为精确模式  
               //将ViewGroup的大小做为结果
                resultSize = size;
                //因为ViewGroup的大小是受到限制值的限制所以childView的大小也应该受到父容器的限制  
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {//childView的布局参数为最大值模式 
                //ViewGroup的大小作为结果  
                resultSize = size;
              //将临时resultMode 也设置为最大值模式
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.AT_MOST://ViewGroup的测量模式为精确模式
            //根据childView的布局参数判断 
            if (childDimension >= 0) {//如果childDimension是一个具体的值  
                 // 将childDimension赋予resultSize ,作为结果
                resultSize = childDimension;
                //将临时resultMode 也设置为精确模式
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {//如果childDimension是精确模式 
                //因为ViewGroup的大小是受到限制值的限制所以chidlView的大小也应该受到父容器的限制 
                 //ViewGroup的大小作为结果  
                resultSize = size;
                 //将临时resultMode 也设置为最大值模式
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {// 如果childDimension是最大值模式 
                
                 //ViewGroup的大小作为结果  
                resultSize = size;
                 //将临时resultMode 也设置为最大值模式
                //childView的大小包裹了其内容后不能超过ViewgGroup               
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.UNSPECIFIED://ViewGroup尺寸大小未受限制  
            if (childDimension >= 0) {//如果childDimension是一个具体的值  
                 // 将childDimension赋予resultSize ,作为结果
                resultSize = childDimension;
                 // 将临时resultMode 也设置为精确模式 
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {如果childDimension是精确模式
               //ViewGroup大小不受限制,对childView来说也可以是任意大小,所以不指定也不限制childView的大小
               //对是否总是返回0进行判断 sUseZeroUnspecifiedMeasureSpec受版本影响
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                 // 将临时resultMode 也设置为UNSPECIFIED,无限制摸式 
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {如果childDimension是最大值
                //ViewGroup大小不受限制,对childView来说也可以是任意大小,所以不指定也不限制childView的大小
                //sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                 // 将临时resultMode 也设置为UNSPECIFIED,无限制摸式 
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //返回封装后的测量规格  
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

至此我们可以看到一个View的大小由其父容器的测量规格MeasureSpecView本身的布局参数LayoutParams共同决定,但是即便如此,最终封装的测量规格也是一个期望值,究竟有多大还是我们调用setMeasuredDimension方法设置的。上面的代码中有些朋友看了可能会有疑问为什么childDimension >= 0就表示一个确切值呢?原因很简单,因为在LayoutParams中MATCH_PARENTWRAP_CONTENT均为负数、哈哈!!正是基于这点,Android巧妙地将实际值和相对的布局参数分离开来。

以上摘自爱哥的自定义控件其实很简单7/12


3. 布局方法 <p>

ViewGorup是个抽象类,继承ViewGroup,肯定就有必须要实现的抽象方法,这个抽象方法就是onLayout()

代码:

public class CustomLayout extends ViewGroup {
    public CustomLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }
}

经过了onMeasure()方法后,确定ViewGroup的位置和childView宽高后,在ViewGrouponLayout()方法内,遍历ViewGroup内所有的childView,并让每个childView调用Viewlayout()方法,在layout()方法内,首先会确定每个childView的顶点的位置,之后又调用childViewonLayout()方法


3.1 简单实现CustomLayout <p>

public class CustomLayout extends ViewGroup {
    public CustomLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 测量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        final int count = getChildCount();
        if (count > 0) {
            measureChildren(widthMeasureSpec, heightMeasureSpec);
        }
    }

    /**
     * 布局
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        if (count > 0) {
            // 遍历内部的childView
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);                                    
                child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
            }
        }
    }
}

代码很简单,就是先遍历测量,在遍历布局


布局xml:

<com.szlk.customview.custom.CustomLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="英勇青铜5"
        android:textColor="@color/colorAccent"
        android:textSize="30sp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="@string/view_group__name" />
</com.szlk.customview.custom.CustomLayout>
CustomLayout

虽然TextViewButtonCustomLayout都已经绘制出来,但ButtonTextView给盖住了。原因很明显,在绘制第2个子控件Button时,依然从CustomView(0,0)点开始绘制,并没有考虑TextView的高度


3.2 进行优化修改 <p>

修改需要考虑的就是已经绘制过的childView的高度

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int count = getChildCount();
    if (count > 0) {
        int mHeight = 0;
        // 遍历内部的childView
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            child.layout(0, mHeight, child.getMeasuredWidth(), child.getMeasuredHeight()+mHeight);
            mHeight +=  child.getMeasuredHeight();
        }
    }
}

增加一个临时变量int mHeight = 0,绘制过TextView就将高度加起来,就等于绘制Button时,开始绘制的点便是(0,mHeight),于是,Button也就在TextView下方

考虑已经绘制过的childView的高

有点像一个超级简单的VerticalLinearLayout

Horizontal的,就可以考虑child.layout()时,改变开始绘制时,x轴的坐标点


3.3 getMeasuredWidth()和getWidth() <p>

onLayout()方法中

child.layout(0, 0, child.getMeasuredWidth(),child.getMeasuredHeight())

使用的是child.getMeasuredWidth(),而不是child.getWidth()


child.getWidth()源码:

/**
 * Return the width of the your view.
 *
 * @return The width of your view, in pixels.
 */
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
    return mRight - mLeft;
}

其中mRightmleft值,是在onLayout()方法后拿到的,在onLayout()方法中,返回的是0


child.getMeasuredWidth()源码:

    /**
     * Like {@link #getMeasuredWidthAndState()}, but only returns the raw width component (that is the result is masked by {@link #MEASURED_SIZE_MASK}).
     *
     * @return The raw measured width of this view.
     */
    public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

之后便是追着mMeasuredWidth这个值走,经过一系列的测量方法后,最终来到onMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(...);
}

mMeasuredWidth则在onMeasure()方法后便可以到了,拿到的时间比getWidth()要早


使用场景:

  • getMeasuredWidth():onLayout()方法内
  • getWidth():除了onLayout()方法,其他之外

使用场景绝大部分情况下都是符合的,这两个方法拿到的值,绝大多数时候也是一样的

可以看看Android开发之getMeasuredWidth和getWidth区别从源码分析


4.考虑Padding,Margins <p>

有了上篇onMeasure()经验,知道PaddingMargins,也需要优化处理的


4.1 Padding

xml文件中加入padding之后

Padding将内容吃掉

修改代码:

protected void onLayout(boolean changed, int l, int t, int r, int b) {
   final int count = getChildCount();
   final int parentPaddingLeft = getPaddingLeft();
   final int parentPaddingTop = getPaddingTop();
   if (count > 0) {
       int mHeight = 0;
       // 遍历内部的childView
       for (int i = 0; i < count; i++) {
           View child = getChildAt(i);
           final int left = parentPaddingLeft;
           final int top = mHeight + parentPaddingTop;
           final int right = child.getMeasuredWidth() + parentPaddingLeft;
           final int bottom = child.getMeasuredHeight() + mHeight + parentPaddingTop;
    child.layout(left, top, right, bottom);
            mHeight += child.getMeasuredHeight();
        }
    }
}

主要就是考虑getPaddingLeft()getPaddingTop()

简单优化Padding

这样也只是做了最简单的优化,一旦Padding大到了一定程度,还是会吃掉内部的childView


4.2 Margins <p>

CustomLayout内加Margins有效,可内部的childView加了却无效。上篇提到过,ViewMargins是封装在LayoutParams后由ViewGroup来处理的

自定义LayoutParams:

public static class LayoutParams extends MarginLayoutParams {

    public LayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);
    }

    public LayoutParams(int width, int height) {
        super(width, height);
    }

    public LayoutParams(ViewGroup.LayoutParams source) {
        super(source);
    }
}

并没有做任何设置,还对更多属性进行设置,以后再学习


完整代码:

public class CustomLayout extends ViewGroup {


    public CustomLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 测量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int count = getChildCount();
        // 临时ViewGroup大小值
        int viewGroupWidth = 0;
        int viewGroupHeight = 0;
        if (count > 0) {
            // 遍历childView
            for (int i = 0; i < count; i++) {
                // childView
                View child = getChildAt(i);
                LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //测量childView包含外边距
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                // 计算父容器的期望值
                viewGroupWidth += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
                viewGroupHeight += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            }

            // ViewGroup内边距
            viewGroupWidth += getPaddingLeft() + getPaddingRight();
            viewGroupHeight += getPaddingTop() + getPaddingBottom();

            //和建议最小值进行比较
            viewGroupWidth = Math.max(viewGroupWidth, getSuggestedMinimumWidth());
            viewGroupHeight = Math.max(viewGroupHeight, getSuggestedMinimumHeight());
        }
        setMeasuredDimension(resolveSize(viewGroupWidth, widthMeasureSpec), resolveSize(viewGroupHeight, heightMeasureSpec));
    }


    /**
     * 布局
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // ViewGroup的内边距
        int parentPaddingLeft = getPaddingLeft();
        int parentPaddingTop = getPaddingTop();
        if (getChildCount() > 0) {
            int mHeight = 0;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                //获取 LayoutParams
                LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //childView的四个顶点
                final int left = parentPaddingLeft + lp.leftMargin;
                final int top = mHeight + parentPaddingTop + lp.topMargin;
                final int right = child.getMeasuredWidth() + parentPaddingLeft + lp.leftMargin;
                final int bottom = child.getMeasuredHeight() + mHeight + parentPaddingTop + lp.topMargin;

                child.layout(left, top, right, bottom);
                // 累加已经绘制的childView的高
                mHeight += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            }
        }
    }

    /**
     *  获取布局文件中的布局参数
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new CustomLayout.LayoutParams(getContext(), attrs);
    }

    /**
     *  获取默认的布局参数
     */
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    /**
     *  生成自己的布局参数
     */
    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    /**
     *  检查当前布局参数是否是我们定义的类型
     */
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    /**
     * 自定义LayoutParams
     */
    public static class LayoutParams extends MarginLayoutParams {

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }
}

代码基本照搬的爱哥的。。。。


xml布局文件

<com.szlk.customview.custom.CustomLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="50dp"
    android:background="@android:color/holo_blue_bright"
    android:padding="10dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:background="@color/colorPrimary"
        android:text="英勇青铜5"
        android:textColor="@color/colorAccent"
        android:textSize="30sp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:text="@string/view_group__name"
        android:textAllCaps="false" />

</com.szlk.customview.custom.CustomLayout>
支持Margin

这时,CustomLayout和内部控件的Margin都已经支持,但真正以后实际开发,要优化考虑的要比这严谨。这里只是了解学习


5.最后 <p>

重点是理解ViewGroup的测量过程,理解后,接下来再学习View的工作流程就会比较容易理解

本人很菜,有错误,请指出

共勉 : )

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容