自定义View-Canvas④ 笔

资料链接
1.GcsSloop的自定义系列

一、绘制图形

1.Canvas简介

Canvas - 画布,能够在上面绘制各种东西,是安卓平台2D图形绘制的基础,非常强大。

2.Canvas常用操作表

操作类型 | 相关API | 备注
----|------
绘制颜色 | drawColor, drawRGB, drawARGB | 使用单一颜色填充整个画布
绘制基本形状 | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧
绘制图片 | drawBitmap, drawPicture | 绘制位图和图片
绘制文本 | drawText, drawPosText, drawTextOnPath | 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字
绘制路径 | drawPath | 绘制路径,绘制贝塞尔曲线时也需要用到该函数
顶点操作 | drawVertices, drawBitmapMesh | 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用
画布剪裁 | clipPath, clipRect | 设置画布的显示区域
画布快照 | save, restore, saveLayerXxx, restoreToCount, getSaveCount | 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数
画布变换 | translate, scale, rotate, skew | 依次为 位移、缩放、 旋转、错切
Matrix(矩阵) | getMatrix, setMatrix, concat | 实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。

3.Canvas详解

3.1.绘制颜色 - drawColor

绘制颜色是填充整个画布,常用于绘制底色。

// 绘制蓝色
canvas.drawColor(Color.BLUE);

3.2.创建画笔 - Paint

// 1.创建一个画笔
private Paint mPaint = new Paint();

// 2.初始化画笔
private void initPaint() {
    // 设置画笔颜色
    mPaint.setColor(Color.BLACK);
    // 设置画笔模式为填充
    mPaint.setStyle(Paint.Style.FILL);
    // 设置画笔宽度为10px
    mPaint.setStrokeWidth(10f);
}

// 3.在构造函数中初始化
public View(Context context, AttributeSet attrs) {
   super(context, attrs);
   initPaint();
}

3.3.绘制点 - drawPoint

// 在坐标(200,200)位置绘制一个点
canvas.drawPoint(200, 200, mPaint);
// 绘制一组点,坐标位置由float数组指定
canvas.drawPoints(new float[]{          
      500,500,
      500,600,
      500,700
},mPaint);

3.3.绘制直线 - drawLine/drawLines

绘制直线需要两个点,初始点和结束点

// 在坐标(300,300)(500,600)之间绘制一条直线
canvas.drawLine(300,300,500,600,mPaint);
// 绘制一组线 每四数字(两个点的坐标)确定一条线
canvas.drawLines(new float[]{
    100,200,200,200,
    100,300,200,300
},mPaint);

3.5.绘制矩形 - drawRect

绘制矩形最少需要四个数据,就是对角线的两个点的坐标值,这里一般采用左上角和右下角的两个点的坐标。

// 第一种 (矩形左上角和右下角两个点的坐标)
canvas.drawRect(100,100,800,400,mPaint);

// 第二种
// 先将矩形封装为Rect(整形)
Rect rect = new Rect(100,100,800,400);
canvas.drawRect(rect,mPaint);

// 第三种
// 先将矩形封装为RectF(单精度浮点型)
RectF rectF = new RectF(100,100,800,400);
canvas.drawRect(rectF,mPaint);

3.6.绘制圆角矩形 - drawRoundRect

// 第一种
RectF rectF = new RectF(100,100,800,400);
// rx 和 ry 圆弧的两条半径
canvas.drawRoundRect(rectF,30,30,mPaint);

// 第二种 API21
canvas.drawRoundRect(100,100,800,400,30,30,mPaint);

3.7.绘制椭圆 - drawOval

// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawOval(rectF,mPaint);

// 第二种 API21
canvas.drawOval(100,100,800,400,mPaint);

3.8.绘制圆 - drawCircle

// 绘制一个圆心坐标在(500,500),半径为400 的圆。
canvas.drawCircle(500,500,400,mPaint);

3.9.绘制圆弧 - drawArc

// 第一种
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}
    
// 第二种
public void drawArc(float left, float top, float right, float bottom, float startAngle,float sweepAngle, boolean useCenter, @NonNull Paint paint) {}

从上面可以看出,相比于绘制椭圆,绘制圆弧还多了三个参数:

// 开始角度
startAngle
// 扫过角度
sweepAngle  
// 是否使用中心
useCenter   

例:

/*** ====== 不使用圆心 ====== ***/
RectF rectF = new RectF(100,100,800,400);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);

// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF,0,90,false,mPaint);

/*** ====== 使用圆心 ====== ***/
RectF rectF2 = new RectF(100,600,800,900);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);

// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF2,0,90,true,mPaint);

正圆例:

/*** ====== 不使用圆心 ====== ***/
RectF rectF = new RectF(100,100,600,600);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);

// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF,0,90,false,mPaint);

/*** ====== 使用圆心 ====== ***/
RectF rectF2 = new RectF(100,700,600,1200);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);

// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF2,0,90,true,mPaint);

二、画布操作

合理的使用画布操作可以帮助你用更容易理解的方式创作你想要的效果,这也是画布操作存在的原因。

1.位移 - translate

位移是基于当前位置移动,而不是每次基于屏幕左上角的(0,0)点移动

// ...省略了创建画笔的代码

// 在坐标原点绘制一个黑色圆形
mPaint.setColor(Color.BLACK);
canvas.translate(200,200);
canvas.drawCircle(0,0,100,mPaint);

// 在坐标原点绘制一个蓝色圆形
mPaint.setColor(Color.BLUE);
canvas.translate(200,200);
canvas.drawCircle(0,0,100,mPaint);

2.缩放 - scale

// x轴,y轴的缩放比例
public void scale (float sx, float sy)
// x轴,y轴的缩放比例。 x中心缩放位置,y中心缩放位置
public final void scale (float sx, float sy, float px, float py)

缩放比例(sx,sy)取值范围:

取值范围(n) 说明
[-∞, -1) 先根据缩放中心放大n倍,再根据中心轴进行翻转
-1 根据缩放中心轴进行翻转
(-1, 0) 先根据缩放中心缩小到n,再根据中心轴进行翻转
0 不会显示,若sx为0,则宽度为0,不会显示,sy同理
(0, 1) 根据缩放中心缩小到n
1 没有变化
(1, +∞) 根据缩放中心放大n倍

缩放的中心默认为坐标原点,而缩放中心轴就是坐标轴

2.1.第一种方式

// 将坐标系原点移动到画布正中心
canvas.translate(getWidth() / 2, getHeight() / 2);

// 绘制黑色矩形
RectF rect = new RectF(0, -400, 400, 0);
mPaint.setColor(Color.BLACK);
canvas.drawRect(rect, mPaint);
// 画布缩放
canvas.scale(0.5f, 0.5f);
// 绘制蓝色矩形
mPaint.setColor(Color.BLUE);
canvas.drawRect(rect, mPaint);

2.2.第二种方式

// 将坐标系原点移动到画布正中心
canvas.translate(getWidth() / 2, getHeight() / 2);
// 绘制黑色矩形
RectF rect = new RectF(0, -300, 300, 0);
mPaint.setColor(Color.BLACK);
canvas.drawRect(rect, mPaint);
// 画布缩放  <-- 缩放中心向右上偏移了150个单位
canvas.scale(0.5f, 0.5f, 150, -150);
// 绘制蓝色矩形
mPaint.setColor(Color.BLUE);
canvas.drawRect(rect, mPaint);

2.3.中心点翻转缩放

// 将坐标系原点移动到画布正中心
canvas.translate(getWidth() / 2, getHeight() / 2);
// 绘制黑色矩形
RectF rect = new RectF(0, -300, 300, 0);
mPaint.setColor(Color.BLACK);
canvas.drawRect(rect, mPaint);

// 画布中心点翻转缩放
canvas.scale(-0.5f, -0.5f);
// 绘制蓝色矩形
mPaint.setColor(Color.BLUE);
canvas.drawRect(rect, mPaint);

缩放也是可以叠加的

3.旋转 - rotate

// 旋转
public void rotate (float degrees)
// 旋转,旋转中心点
public final void rotate (float degrees, float px, float py)

3.1.正常翻转

// 将坐标系原点移动到画布正中心
canvas.translate(getWidth() / 2, getHeight() / 2);
// 矩形区域
RectF rect = new RectF(0, -300, 300, 0);
// 绘制黑色矩形
mPaint.setColor(Color.BLACK);
canvas.drawRect(rect, mPaint);

// 旋转180度 <-- 默认旋转中心为原点
canvas.rotate(180);
// 绘制蓝色矩形
mPaint.setColor(Color.BLUE);
canvas.drawRect(rect, mPaint);

3.2.改变旋转中心位置

// 将坐标系原点移动到画布正中心
canvas.translate(getWidth() / 2, getHeight() / 2);
// 矩形区域
RectF rect = new RectF(0, -300, 300, 0);
// 绘制黑色矩形
mPaint.setColor(Color.BLACK);
canvas.drawRect(rect, mPaint);

// 旋转180度 <-- 旋转中心向右偏移150个单位
canvas.rotate(180, 150, 0);
// 绘制蓝色矩形
mPaint.setColor(Color.BLUE);
canvas.drawRect(rect, mPaint);

旋转也是可叠加

例:

// 将坐标系原点移动到画布正中心
canvas.translate(getWidth() / 2, getHeight() / 2);
// 绘制两个圆形
canvas.drawCircle(0, 0, 200, mPaint);
canvas.drawCircle(0, 0, 180, mPaint);

// 绘制圆形之间的连接线
for (int i = 0; i <= 360; i += 45) {
    canvas.drawLine(0, 180, 0, 200, mPaint);
    canvas.rotate(45);
}

4.错切 - skew

/**
 * 错切
 * @param sx :将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值,
 * @param sy :将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值.
 */
public void skew (float sx, float sy)

变换后:

X = x + sx * y
Y = sy * x + y

例:

// 将坐标系原点移动到画布正中心
canvas.translate(getWidth() / 2, getHeight() / 2);
// 矩形区域
RectF rect = new RectF(0, 0, 100, 100);
// 绘制黑色矩形
mPaint.setColor(Color.BLACK);
canvas.drawRect(rect, mPaint);
// 水平错切 <-- 45度
canvas.skew(1, 0);
// 绘制蓝色矩形
mPaint.setColor(Color.BLUE);
canvas.drawRect(rect, mPaint);

5.快照 - save 和回滚 - restore

画布状态API

相关API 简介
save 把当前的画布的状态进行保存,然后放入特定的栈中
saveLayerXxx 新建一个图层,并放入特定的栈中
restore 把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布
restoreToCount 弹出指定位置及其以上所有的状态,并按照指定位置的状态进行恢复
getSaveCount 获取栈中内容的数量(即保存次数)

存储画布状态和图层状态栈:

画布和图层

实际上我们之前讲解的绘制操作和画布操作都是在默认图层上进行的。
在通常情况下,使用默认图层就可满足需求,但是如果需要绘制比较复杂的内容,如地图(地图可以有多个地图层叠加而成,比如:政区层,道路层,兴趣点层)等,则分图层绘制比较好一些。
你可以把这些图层看做是一层一层的玻璃板,你在每层的玻璃板上绘制内容,然后把这些玻璃板叠在一起看就是最终效果。

SaveFlags:

名称 简介
ALL_SAVE_FLAG 默认,保存全部状态
CLIP_SAVE_FLAG 保存剪辑区
CLIP_TO_LAYER_SAVE_FLAG 剪裁区作为图层保存
FULL_COLOR_LAYER_SAVE_FLAG 保存图层的全部色彩通道
HAS_ALPHA_LAYER_SAVE_FLAG 保存图层的alpha(不透明度)通道
MATRIX_SAVE_FLAG 保存Matrix信息( translate, rotate, scale, skew)

5.1.save:

// 保存全部状态
public int save ()

// 根据saveFlags参数保存一部分状态
public int save (int saveFlags)

可以看到第二种方法比第一种多了一个saveFlags参数,使用这个参数可以只保存一部分状态,更加灵活,这个saveFlags参数具体可参考上面表格中的内容。

每调用一次save方法,都会在栈顶添加一条状态信息,以上面状态栈图片为例,再调用一次save则会在第5次上面载添加一条状态。

5.2.saveLayerXxx

// 无图层alpha(不透明度)通道
public int saveLayer (RectF bounds, Paint paint)
public int saveLayer (RectF bounds, Paint paint, int saveFlags)
public int saveLayer (float left, float top, float right, float bottom, Paint paint)
public int saveLayer (float left, float top, float right, float bottom, Paint paint, int saveFlags)

// 有图层alpha(不透明度)通道
public int saveLayerAlpha (RectF bounds, int alpha)
public int saveLayerAlpha (RectF bounds, int alpha, int saveFlags)
public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha)
public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha, int saveFlags)

注意:saveLayerXxx方法会让你花费更多的时间去渲染图像(图层多了相互之间叠加会导致计算量成倍增长),使用前请谨慎,如果可能,尽量避免使用。

使用saveLayerXxx方法,也会将图层状态也放入状态栈中,同样使用restore方法进行恢复。
这个暂时不过多讲述,如果以后用到详细讲解。

5.3.restore

状态回滚,就是从栈顶取出一个状态然后根据内容进行恢复。
同样以上面状态栈图片为例,调用一次restore方法则将状态栈中第5次取出,根据里面保存的状态进行状态恢复。

5.4.restoreToCount

弹出指定位置以及以上所有状态,并根据指定位置状态进行恢复。
以上面状态栈图片为例,如果调用restoreToCount(2) 则会弹出 2 3 4 5 的状态,并根据第2次保存的状态进行恢复。

5.5.getSaveCount

获取保存的次数,即状态栈中保存状态的数量,以上面状态栈图片为例,使用该函数的返回值为5。
不过请注意,该函数的最小返回值为1,即使弹出了所有的状态,返回值依旧为1,代表默认状态。

5.6.常用格式

// 保存状态
save();
// 具体操作    
...
// 回滚到之前的状态
restore();

这种方式也是最简单和最容易理解的使用方法。

三、Canvas之图片

1.drawPicture(矢量图)

使用Picture前请关闭硬件加速,以免引起不必要的问题!
在AndroidMenifest文件中application节点下添上 android:hardwareAccelerated=”false”以关闭整个应用的硬件加速。

1.1.Picture(录制Canvas操作的录像机)

Picture相关方法 简介
public int getWidth () 获取宽度
public int getHeight () 获取高度
public Canvas beginRecording (int width, int height) 开始录制 (返回一个Canvas,在Canvas中所有的绘制都会存储在Picture中)
public void endRecording () 结束录制
public void draw (Canvas canvas) 将Picture中内容绘制到Canvas中

例:

private Picture mPicture;

/**
 * 初始化Picture
 */
private void initPicture() {
    mPicture = new Picture();

    // 开始录制 (接收返回值Canvas)
    Canvas canvas = mPicture.beginRecording(300, 300);
    // 创建一个画笔
    Paint paint = new Paint();
    paint.setColor(Color.BLUE);
    paint.setStyle(Paint.Style.FILL);

    // 在Canvas中具体操作
    // 位移
    canvas.translate(50, 50);
    // 绘制一个圆
    canvas.drawCircle(0, 0, 100, paint);

    mPicture.endRecording();
}

1.2.具体使用:

序号 简介
1 使用Picture提供的draw方法绘制。
2 使用Canvas提供的drawPicture方法绘制。
3 将Picture包装成为PictureDrawable,使用PictureDrawable的draw方法绘制。

以上几种方法主要区别:

主要区别 | 分类 | 简介
----|------
是否对Canvas有影响 | 1有影响
2,3不影响 | 此处指绘制完成后是否会影响Canvas的状态(Matrix clip等)
可操作性强弱 | 1可操作性较弱
2,3可操作性较强 | 此处的可操作性可以简单理解为对绘制结果可控程度。

a.使用Picture提供的draw方法绘制:
// 将Picture中的内容绘制在Canvas上
mPicture.draw(canvas);

PS:这种方法在比较低版本的系统上绘制后可能会影响Canvas状态,所以这种方法一般不会使用。

b.使用Canvas提供的drawPicture方法绘制
public void drawPicture (Picture picture)

public void drawPicture (Picture picture, Rect dst)

public void drawPicture (Picture picture, RectF dst)

Canvas的drawPicture不会影响Canvas状态。

例:

canvas.drawPicture(mPicture, new RectF(0, 0, mPicture.getWidth(), 100));

对照上一张图片,可以比较明显的看出,绘制的内容根据选区进行了缩放。

c.将Picture包装成为PictureDrawable,使用PictureDrawable的draw方法绘制。
// 包装成为Drawable
PictureDrawable drawable = new PictureDrawable(mPicture);
// 设置绘制区域 -- 注意此处所绘制的实际内容不会缩放
drawable.setBounds(0, 0, mPicture.getWidth() / 2, mPicture.getHeight());
// 绘制
drawable.draw(canvas);

此处setBounds是设置在画布上的绘制区域,并非根据该区域进行缩放,也不是剪裁Picture,每次都从Picture的左上角开始绘制。

2.drawBitmap常用的功能

2.1.获取Bitmap

通过BitmapFactory获取:从资源文件/内存卡/网络等地方获取一张图片并转换为内容不可变的Bitmap

a.资源文件(drawable/mipmap/raw):
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),R.raw.bitmap);
b.资源文件(assets):
Bitmap bitmap=null;
try {
    InputStream is = mContext.getAssets().open("bitmap.png");
    bitmap = BitmapFactory.decodeStream(is);
    is.close();
} catch (IOException e) {
    e.printStackTrace();
}
c.内存卡文件:
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/bitmap.png");
d.网络文件:
// 此处省略了获取网络输入流的代码
Bitmap bitmap = BitmapFactory.decodeStream(is);
is.close();

2.2.绘制Bitmap

// 第一种 (matrix, paint)是在绘制的时候对图片进行一些改变
public void drawBitmap (Bitmap bitmap, Matrix matrix, Paint paint)

// 第二种
public void drawBitmap (Bitmap bitmap, float left, float top, Paint paint)

// 第三种 (src:指定绘制图片的区域,dst:指定图片在屏幕上显示的区域)
public void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
例1
canvas.drawBitmap(bitmap,new Matrix(),new Paint());
例2
// 绘制时指定了图片左上角的坐标(距离坐标原点的距离):
canvas.drawBitmap(bitmap,new Matrix(),new Paint());
例3
// 将画布坐标系移动到画布中央
canvas.translate(getWidth() / 2, getHeight() / 2);
// 指定图片绘制区域(左上角的四分之一)
Rect src = new Rect(0, 0, getBitmap().getWidth() / 2, getBitmap().getHeight() / 2);
// 指定图片在屏幕上显示的区域
Rect dst = new Rect(0, 0, 200, 400);
// 绘制图片
canvas.drawBitmap(getBitmap(), src, dst, null);

把同一个动画效果的所有资源图片整理到一张图片上,会大大的减少资源文件数量,方便管理,同时也节省了图片文件头、文件结束块以及调色板等占用的空间。

四、Canvas之文字

1.常用方法

// 第一类:只能指定文本基线位置(基线x默认在字符串左侧,基线y默认在字符串下方)。
public void drawText (String text, float x, float y, Paint paint)
public void drawText (String text, int start, int end, float x, float y, Paint paint)
public void drawText (CharSequence text, int start, int end, float x, float y, Paint paint)
public void drawText (char[] text, int index, int count, float x, float y, Paint paint)

// 第二类:可以分别指定每个文字的位置。
public void drawPosText (String text, float[] pos, Paint paint)
public void drawPosText (char[] text, int index, int count, float[] pos, Paint paint)

// 第三类:指定一个路径,根据路径绘制文字。
public void drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint)
public void drawTextOnPath (char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)

1.2.第一类(drawText)

// 文本(要绘制的内容)
String str = "ABCDEFG";
// 参数分别为 (文本 基线x 基线y 画笔)
canvas.drawText(str, 200, 100, mTPaint);

// 参数分别为 (字符串 开始截取位置 结束截取位置 基线x 基线y 画笔)
canvas.drawText(str, 1, 3, 200, 200, mTPaint);

// 字符数组(要绘制的内容)
char[] chars = "ABCDEFG".toCharArray();
// 参数为 (字符数组 起始坐标 截取长度 基线x 基线y 画笔)
canvas.drawText(chars, 1, 3, 200, 300, mTPaint);

1.3.第二类(drawPosText)

String str = "SLOOP";
// 不推荐使用 
canvas.drawPosText(str, new float[]{
    // 第一个字符位置
    100, 100,
    // 第二个字符位置
    200, 200,
    300, 300,
    400, 400,
    500, 500
}, mTPaint);

1.4.第三类(drawTextOnPath)

// TODO


2017/6/6 17:09:27

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

推荐阅读更多精彩内容