android view的绘制

1.为什么要自定义View?

参考链接之超精辟的自定义view 绘制

  • UI的奇葩设计。
  • 多界面的组件复用。

2.知识点

2.1 MeasureSpec

MeasureSpec是一个32为的整数值,前两位表示测量的模式Spec
Mode,后30位表示该模式下的规格大小SpecSize。

MeasureSpec核心代码如下:

  public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        /**
         * Creates a measure specification based on the supplied size and mode.
         *
         * The mode must always be one of the following:
         * <ul>
         *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
         *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
         *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
         * </ul>
         *
         * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
         * implementation was such that the order of arguments did not matter
         * and overflow in either value could impact the resulting MeasureSpec.
         * {@link android.widget.RelativeLayout} was affected by this bug.
         * Apps targeting API levels greater than 17 will get the fixed, more strict
         * behavior.</p>
         *
         * @param size the size of the measure specification
         * @param mode the mode of the measure specification
         * @return the measure specification based on size and mode
         */
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        /**
         * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
         * will automatically get a size of 0. Older apps expect this.
         *
         * @hide internal use only for compatibility with system widgets and older apps
         */
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         * Extracts the mode from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the mode from
         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
         *         {@link android.view.View.MeasureSpec#AT_MOST} or
         *         {@link android.view.View.MeasureSpec#EXACTLY}
         */
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        /**
         * Extracts the size from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the size from
         * @return the size in pixels defined in the supplied measure specification
         */
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         * Returns a String representation of the specified measure
         * specification.
         *
         * @param measureSpec the measure specification to convert to a String
         * @return a String with the following format: "MeasureSpec: MODE SIZE"
         */
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");

            sb.append(size);
            return sb.toString();
        }
    }
  • 主要方法
方法 用法
makeMeasureSpec 重置MeasureSpec约束
getMode 获取MeasureSpec的模式specMode
getSize 获取Measure的模式specSize
  • 三种模式
方法(音标) 用法
UNSPECTIFIED(ʌn'spɛsɪfaɪd) 未指明尺寸
EXACTLY(ɪɡ'zæktli) 精确的尺寸
AT_MOST 父视图允许的最大尺寸

2.2 Measure

Measure操作用来计算View的实际大小,用于确定当前View或ViewGroup的实际宽高。
常用方法如下:

  • onMeasure的源码
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

由onMeasure的源码可以看出,该方法的目的,就是为了确定view的具体宽高;

  • setMeasuredDimension的源码
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        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的宽和高,在每一次view尺寸运算完成使用,可以理解为,onMeasure中最后一个必调的方法;

  • setMeasuredDimensionRaw的源码
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

设置每一行的宽高,很少使用,略;

  • getMeasuredWidth的源码
public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

获取view的宽度;

  • getLayoutParams()的源码
 @ViewDebug.ExportedProperty(deepExport = true, prefix = "layout_")
    public ViewGroup.LayoutParams getLayoutParams() {
        return mLayoutParams;
    }

获取View的Layoutparams(备注:layout_开头的所有参数都放在了里面);

  • measureChildren的源码(ViewGroup)
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) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

遍历所有的子View,测量全部View的宽高。一般与getMeasuredWidth()或getMeasuredHeight()配合使用;

  • measureChild的源码(ViewGroup)
protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

用于测量具体子view的宽和高;一般与getMeasuredWidth()或getMeasuredHeight()配合使用;

  • getChildAt(i)(ViewGroup)
    获取子View

  • getChildCount()(ViewGroup)
    获取子View的数量

  • 备注:layout_width 三种设置值的方式,以及对应的模式流程

赋值方式 模式流程
wrap_content AT_MOST
match_parent AT_MOST --> EXACTLY
100dp EXACTLY

2.3 Layout(ViewGroup)

Layout的过程是用于确定View在父布局中的位置。由父布局获取参数,然后将参数传递给子View的layout方法中,将view放在具体的位置;
在onLayout中没有太复杂的逻辑需要处理,相应的参数都可以在onMeasure中获得。建议使用LayoutParams进行传值,若子view的值固定,可以使用makeMeasureSpec进行重置约束,通过setMeasuredDimension或onMeasure进行设置子view的约束。

实例代码如下

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.layout(l, t, r, b);//这里不能直接使用onLayout的参数,具体的参数值可以通过,全局变量或LayoutParams进行传递;
        }
    }

2.4 Draw(多用于View)

onDraw操作用于绘制view的具体界面,可以绘制常见的图形、文字等,通过对view的操作,也可以实现具体的动画,如MD动画的实现。
同时,由于ViewGroup大多为容器,用户承载view,很少会使用onDraw,当然,也可以使用Draw绘制背景等。

核心知识点
  • Paint 画笔
    Paint方法用于在Canvas上绘制内容,可以设置Paint的宽度、颜色、笔触、以及对图片进行滤镜处理等。
    详细的Paint效果,请查看Android 画笔Paint

常用的Paint代码:

 private void initPaint() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);//设置画笔样式
        mPaint.setStrokeWidth(10);// 设置画笔宽度
        mPaint.setColor(ContextCompat.getColor(getContext(), R.color.colorAccent));//设置画笔颜色
        mPaint.setAntiAlias(true);//设置抗锯齿
        mPaint.setTextSize(60);//设置文字尺寸
    }

设置

笔触类型 描述
FILL_AND_STROKE 填充内部且描边
STROKE 描边
FILL 填充内部
  • Canvas 画布

    绘制文字

        /**
         * @param text  文本
         * @param x     水平方向起点
         * @param y     竖直方向的文字底部
         * @param paint 画笔
         */
        canvas.drawText("画圆:", 50, 1000, mPaint);

绘制矩形

       /**
         * RectF : 屏幕左上角是坐标原点,4个参数:left,top,right,bottom
         * left:屏幕左边到矩形左边的距离
         * top:屏幕顶部到矩形顶边的距离
         * right:屏幕左边到矩形右边的距离
         * bottom:屏幕顶边到矩形底边的距离
         *
         * @desc 绘制一个宽50的矩形
         */
        canvas.drawRect(new RectF(50,50,100,100),mPaint);

** 将整个画布绘制成画笔的颜色**

       /**
         * 将整个画布绘制成画笔的颜色
         */
        canvas.drawPaint(mPaint);

** 绘制一条水平直线**

      /**
         * @param startX X轴的起点
         * @param startY Y轴的起点
         * @param stopX X轴的终点
         * @param stopY Y轴的终点
         * @param paint
         */
        canvas.drawLine(10,10,100,10,mPaint);
   

** 绘制一条水平直线**

      /**
         * @param startX X轴的起点
         * @param startY Y轴的起点
         * @param stopX X轴的终点
         * @param stopY Y轴的终点
         * @param paint
         */
        canvas.drawLine(10,10,100,10,mPaint);
   

绘制圆弧或扇形

        /**
           * oval:圆弧所在的RectF对象。
           * startAngle:圆弧的起始角度。
           * sweepAngle:圆弧的结束角度。
           * useCenter:是否显示半径连线,true表示显示圆弧与圆心的半径连线,false          表示不显示。
           * paint:绘制时所使用的画笔。
           */
       //绘制扇形
        RectF oval1 = new RectF(50, 50, 300, 300);
        canvas.drawArc(oval1,90,90,true,mPaint);
        //绘制圆弧
        mPaint.setStyle(Paint.Style.STROKE);
        RectF oval2 = new RectF(150, 150, 600, 600);
        canvas.drawArc(oval2,90,90,false,mPaint);
       

绘制圆角矩形

/**
         * RectF : 屏幕左上角是坐标原点,4个参数:left,top,right,bottom
         * left:屏幕左边到矩形左边的距离
         * top:屏幕顶部到矩形顶边的距离
         * right:屏幕左边到矩形右边的距离
         * bottom:屏幕顶边到矩形底边的距离
         * @param rx 圆角X轴半径
         * @param ry 圆角Y轴半径
         * @desc 绘制一个宽50的矩形
         */
        canvas.drawRoundRect(new RectF(50,50,100,100),mPaint);

绘制图片

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
/**
 * @param bitmap The bitmap to be drawn
 * @param left   左上角x坐标
 * @param top    左上角y坐标
 * @param paint  画笔
 */
canvas.drawBitmap(bitmap, 360, 1300, mPaint);

绘制点

/**
 * x,y坐标
 */
//画一个点
canvas.drawPoint(500, 1200, p);
//画多个点
canvas.drawPoints(new float[]{600, 1200, 650, 1250, 700, 1200}, mPaint);

绘制三角形

Path path = new Path();
path.moveTo(500, 750);// 此点为多边形的起点
path.lineTo(400, 850);
path.lineTo(600, 850);
path.close(); // 使这些点构成封闭的多边形
canvas.drawPath(path, mPaint);

绘制贝塞尔曲线

Path path2 = new Path();
path2.moveTo(500, 1050);//设置Path的起点
//设置贝塞尔曲线的控制点坐标和终点坐标
path2.quadTo(600, 950, 700, 1050);
path2.quadTo(800, 1150, 900, 1050); 
canvas.drawPath(path2, mPaint);

备注:画线的时候,一定要将画笔设置为Paint.Style.STROKE

2.5 自定义xml中的属性

1. 创建attrs.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="FlowLayout">
        <attr name="space" format="dimension"></attr>
    </declare-styleable>
</resources>

declare-styleable 设置自定义view的名称,attr中设置view中需要使用的具体属性,以及相应属性的具体格式;

所有属性的格式如下:

格式 描述 使用
reference 资源ID @drawable/图片ID
color 颜色值 #ffffff
boolean 布尔值 false or true
dimension 尺寸dp值 100dp or 100dip
float 浮点值 1.0
integer 整型值 100
string 字符串 "str"
fraction 百分数 100%
enum 枚举类型 使用enum标签设置
flag 位或运算 类事枚举,使用flag标签设置

2. 在View中引用
获取自定义属性值

  void init(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
        space = ta.getDimensionPixelSize(R.styleable.FlowLayout_space, 0);
        ta.recycle();
    }

init方法应该放在以下的构造方法中。(备注:默认都会调用以下构造方法,无论是否进行findViewById)

public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.e(TAG, "FlowLayout(Context context, AttributeSet attrs)");
        init(context, attrs);
        Log.e(TAG,space+"");
    }

3. 在布局文件中使用

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="study.zxh.com.viewdemo.MainActivity">

    <study.zxh.com.viewdemo.FlowLayout
        android:id="@+id/fl"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:space="10dp" />

</android.support.constraint.ConstraintLayout>

在跟标签引入

xmlns:app="http://schemas.android.com/apk/res-auto"

在自定义的布局中使用

app:space="10dp"

2.6 LayoutParam

  • 自定义LayoutParams
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    private static class LayoutParams extends ViewGroup.LayoutParams {
        private int left;
        private int top;
        private int right;
        private int bottom;

        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 LayoutParams(LayoutParams source) {
            super(source);
            this.left = source.left;
            this.top = source.top;
            this.right = source.right;
            this.bottom = source.bottom;
        }
    }
  • 引用步骤
  1. 实现静态内部类LayoutParams,并继承自ViewGroup.LayoutParams或其子类;
  2. 重写checkLayoutParams用于检测其类型。
  3. 重写generateLayoutParams实现自定义LayoutParams的构造;
  4. 重写generateDefaultLayoutParams()传入默认的LayoutParams,一般传入ViewGroup.LayoutParams。
  5. 使用时,直接通过view.getLayoutParams(),获取子view的LayoutParams使用;

3.自定义View之实现MD按钮动画

4.自定义ViewGroup之流式布局

实现的最终效果

实现的最终效果

最终代码

/**
 * Created by zhangxuehui on 2017/6/16.
 * 实现动态添加文字标签
 */
public class FlowLayout extends ViewGroup {
    private static final String TAG = "FlowLayout";
    private int space = 0;//文字间的间距,以及四周的边距;


    public FlowLayout(Context context) {
        super(context);
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
        Log.e(TAG, space + "");
    }

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //初始化自定义的属性
    void init(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
        space = ta.getDimensionPixelSize(R.styleable.FlowLayout_space, 0);
        ta.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int curWidth = 0;
        int curHeight = 0;
        int maxWidth = 0;
        int maxHeight = 0;
        int pos = 0;//用于判读最后一行是否有数据
        for (int i = 0; i < this.getChildCount(); i++) {
            View child = this.getChildAt(i);
            //测量child的宽高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            lp.left = curWidth + space;
            lp.top = maxHeight + space;
            lp.right = lp.left + child.getMeasuredWidth();
            lp.bottom = lp.top + child.getMeasuredHeight();
            curWidth = lp.right;
            curHeight = Math.max(child.getMeasuredHeight(), curHeight);
            if (curWidth > widthSize - space) {//翻页,需要去除右边距,否则,最右侧标签可能会贴紧屏幕;
                //翻页后,初始化相应的参数
                maxWidth = Math.max(maxWidth, curWidth);
                pos = i;
                curHeight = 0;
                curWidth = 0;
                maxHeight = lp.bottom;
                //重新设置超出屏幕的view
                lp.left = curWidth + space;
                lp.top = maxHeight + space;
                lp.right = lp.left + child.getMeasuredWidth();
                lp.bottom = lp.top + child.getMeasuredHeight();
                //取得最新的参数
                curWidth = lp.right;
                curHeight = Math.max(child.getMeasuredHeight(), curHeight);
            }
            Log.e(TAG, lp.toString());
        }
        if (getChildCount() > pos) {
            maxHeight += curHeight + space * 2;
        }
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY);//重建约束
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);//重建约束
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, MeasureSpec.EXACTLY), resolveSizeAndState(maxHeight, heightMeasureSpec, MeasureSpec.EXACTLY));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < this.getChildCount(); i++) {
            View child = this.getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            child.layout(lp.left, lp.top, lp.right, lp.bottom);
        }
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    private static class LayoutParams extends ViewGroup.LayoutParams {
        private int left;
        private int top;
        private int right;
        private int bottom;

        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 LayoutParams(LayoutParams source) {
            super(source);
            this.left = source.left;
            this.top = source.top;
            this.right = source.right;
            this.bottom = source.bottom;
        }

        public void clear() {
            this.left = 0;
            this.top = 0;
            this.right = 0;
            this.bottom = 0;
        }

        @Override
        public String toString() {
            return "LayoutParams{" +
                    "left=" + left +
                    ", top=" + top +
                    ", right=" + right +
                    ", bottom=" + bottom +
                    '}';
        }
    }
}

5.组合view之微信聊天界面

最终效果
实现思想
首先整个聊天界面通过系统的ListView实现,通过组合控件实现聊天气泡和输入框,全部代码可以划分为三个组合控件,WeChatMsgList、WeChatMsgInput、WeChatBubble
最终代码

  • WeChatMsgList

  • WeChatMsgInput

  • WeChatBubble

参考地址:
Canvas绘图
Android 画笔Paint
官方api Canvas

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

推荐阅读更多精彩内容