iOS View 编程指导(三)-View

View

iOSAPP中和用户打交道最多就是view,view有多作用,下面随便列举几个:

  • 布局管理
    • a view能够定义和父视图相关的,默认的大小变动行为
    • a view使用一个数组管理它的subviews
    • a view能够改变subview的size和position
    • a view能将一个坐标系下的point转换到另一个坐标系的点
  • 绘制内容和动画
    • 能够在一个矩形区域绘制内容
    • view的属性可以做动画
  • 响应事件
    • view可以接受事件
    • view参与响应链
      本文讲解如何创建view,管理views,绘制内容,view的层级树,view如何处理事件传递(更多内容请看Event Handling Guide for iOS)

创建和设置View

可以使用代码手动创建也可用XIB创建,创建完view后,将其组合到view的层级树中.

使用XIB创建View

  • xib创建view是一种便捷方式,在xib中你可以拖拽UI元素进入你的界面,配置各种属性. xib的另一个好处是,所见即所得(xib中见到和运行时的一样)方便调试. 你可以将view的行为和代码绑定起来,这样view可以进行用户交互. 创建好后,xib会将view和view状态等配置信息保存在nib文件中(一种资源文件)
  • 通常一个nib文件代表一个整个view层级树,顶层是controller的view,然后再往controller的view中添加其他view. 要注意顶层view的大小要和设备以及内容匹配.
  • 通常一个nib文件是和viewController绑定在一起的,在使用是controller会自动从nib中加载UI界面; 如果你的nib文件没有和controller绑定在一起的话,可以使用NSBundle或者UINib来手动从nib文件中加载界面.

想要学习更多的关于xib使用的知识请参考Apple文档Interface Builder User Guide
以及controller如何加载nib文件,创建自定义viewController请看View Controller Programming Guide for iOS
以及学习如何手动从nib文件中加载UI界面的知识请看Resource Programming Guide中的Nib Files

使用代码创建View

通常使用allocation/initialization模式来创建view的,view的默认初始化方法是initWithFrame:

CGRect  viewRect = CGRectMake(0, 0, 100, 100);
UIView* myView = [[UIView alloc] initWithFrame:viewRect];

注意:虽然所有的view都支持initWithFrame:方法,但有的view有其自己的初始化方法,比如UIButton,通常都是使用buttonWithType:来创建的,UIImageView的initWithImage:等等.

view创建后需要将其添加到window中或其他view中,否则不能显示.

给view的属性赋值

通过UIView的属性来控制view的显示和行为.
下表展示view的属性和作用

Properties Usage
alpha, hidden, opaque 这些控制view透明度. 注意opaque属性,opaque属性设置为YES可以提高性能
bounds,frame,center,transform 这些属性控制view的size和position. transform用来做动画或者做view的复杂整体移动
autoresizingMask, autoresizesSubviews 这些属性用来控制view和subviews的automatic resizing行为. 当superview的bounds发生改变时,autoresizingMask控制view的变化;autoresizesSubviews控制view的subviews是否需要resize.
contentMode,contentStretch,contentScaleFactor 这些影响view的内容绘制. contentScaleFactor属性用于需要自定义重绘view的高分辨率的屏幕.
gestureRecognizer, userInteractionEnabled, MultipleTouchEnabled, exclusiveTouch 这些属性控制view对于touch events的处理.
backgroundColor, subviews, drawRect:,layer 这些属性控制view的内容显示和绘制

想要知道更多请看UIView的接口UIView Class Reference

给view添加一个记号

UIView有个tag属性(integer,整型,默认为0),用来标记view的,方面后续使用tag值从view的层级树中找到该view.使用tag来获取view比遍历寻找要快.

通过UIView的实例方法viewWithTag:,该方法使用深度优先算法(参考数据结构-树)从层级树中搜索目标,而且只会从view的本身和subview开始搜索,view的superview和其他层级树不会搜索,也就是说如果你对root view调该方法,那么它会搜索整个页面的层级树,如果是树中的某个view调用该方法,那么只会搜索某个子树.

创建和管理view的层级树

创建和管理view的层级树,就是创建和管理APP的UI界面,层级树决定了那个view响应事件. 下图展示Clock应用的图层,由许多view构成UI界面:

Clock应用的图层

这一节讲解如何创建view的层级树,以及如何从层级树找到特定的view,转换不同的view的坐标系.

添加移除subview

如果使用xib创建view层级树,那么可以直观地发现view之间的层级(父-子关系),而且界面不需要运行就可以看到.
使用代码创建的话,需要使用下么方法来创建和管理:

  • 将subview添加到superview使用addSubview:方法,该方法将subview添加superview的属性subviews数组中末尾
  • 要将subview加入superview的subviews中的某一个为使用方法insertSubView:...
  • 要想将某个view位置改变一下,可以使用bringSubviewToFront:,sendSubviewToBack:,exchangeSubviewAtIndex:withSubviewAtIndex:,使用这些方法比使用add,remove,insert等方法要快.
  • 想将一个view从superview中移除,可以使用removeFromSuperview方法

当一个subview添加到superview后,会根据frame来确定位置和大小. subview超出superview的区域默认是可见的,如果你想superview裁剪subview,可以将superview的clipsToBounds设置为YES.

往view的层级树中插入subview的代码可以写controller的loadView(适合手动用代码)或者viewDidLoad中(适合xib)

下列代码展示了Apple官方demoUIKit Catalog (iOS): Creating and Customizing UIKit Controls中类TransitionsViewController方法viewDidload中的代码. TransitionsViewController用来管理两个view间切换的动画. viewdidload中的代码顺序地创建一个容器view,image views用来做切换动画. 容器view的作用是方面做两个image间的切换动画.

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = NSLocalizedString(@"TransitionsTitle", @"");
    // create the container view which we will use for transition animation (centered horizontally)
    CGRect frame = CGRectMake(round((self.view.bounds.size.width - kImageWidth) / 2.0),
                                                        kTopPlacement, kImageWidth, kImageHeight);
    self.containerView = [[UIView alloc] initWithFrame:frame];
    [self.view addSubview:self.containerView];
 
    // create the initial image view
    frame = CGRectMake(0.0, 0.0, kImageWidth, kImageHeight);
    self.mainView = [[[UIImageView alloc] initWithFrame:frame] autorelease];
    self.mainView.image = [UIImage imageNamed:@"scene1.jpg"];
    [self.containerView addSubview:self.mainView];
 
    // create the alternate image view (to transition between)
    CGRect imageFrame = CGRectMake(0.0, 0.0, kImageWidth, kImageHeight);
    self.flipToView = [[[UIImageView alloc] initWithFrame:imageFrame] autorelease];
    self.flipToView.image = [UIImage imageNamed:@"scene2.jpg"];
}

如果你将一个subview又添加到另一个view,UIKit会通知它superview和它的subview. 如果是自定义的view,你可以在重写下面方法来监听该通知:

  • willMoveToSuperview:,willMoveToWindow:,willRemoveSubview:
  • didAddSubview:,didMoveToSuperview,didMoveToWindow
    你可以使用上述通知来做一些和view层级树变动有关的操作

隐藏view

  • 有两种方式:①设置属性hidden为YES②设置属性alpha为0.0
  • 隐藏的view不会响应事件,但会参与view的布局
  • 如果想移除一个view通常隐藏该view,特别是当该view在未来某刻需要显示
  • 如果想给view做个隐藏/显示动画,那么你应该使用alpha而不是hidden

注意:如果你隐藏的view当前是first responder,那么事件会继续传递给它,所以你因该在隐藏它同时将其resign first responder. 更多关于响应链的知识请看Event Handling Guide for iOS

如何在层级树中找到特定的view

  • 有两种方法:①通过保存一个该view的一个引用 ②设定一个唯一性的tag值,在使用viewWithTag:寻找
  • 通过引用方法的经常使用,但使用tag的方法更加灵活硬编码少点.而且tag的方式也可以用来做数据的持久化操作,界面的恢复.比如,在做界面恢复操作时,可以先用个文件保存view的tag,然后将该文件写到磁盘中,比把正界面保存好多了.在界面恢复时,根据tag值可以快速确定view间的关系和是否需要显示.

view的位移/缩放/旋转

  • 每个view都有个transform属性用来给view做仿射变换的, 改变view的transform会影响view的最终渲染的结果,一般用于实现滚动,动画,等视觉效果.
  • view的属性transform的类型是一个CGAffineTransform结构体,默认值是identity transform(不会改变view外观).你可以随时给transform赋值,如下:
// M_PI/4.0 is one quarter of a half circle, or 45 degrees.
CGAffineTransform xform = CGAffineTransformMakeRotation(M_PI/4.0);
self.view.transform = xform;

下图展示了transform如何旋转一张图片:


旋转一张图片
  • 给view添加的多个仿射变换的顺序会影响最终结果,比如选择后位移和位移后选择的结果是不一样的,即使旋转和位移的次数相同.做放射变换时view的center是不会变的,想知道更多的知识请看文档Quartz 2D Programming Guide中的Transforms

切换不同的坐标系

很多时候,特别是在处理touch events的时候,经常要计算一个view的坐标在其他view中的坐标; 比如要计算touches在某个view中的坐标. UIView提供了下面的方法用来计算其他view在该view本地坐标:

  • convertPoint:fromView:
  • convertRect:fromView:
  • convertPoint:toView:
  • convertRect:toView:

上面方法中convert...:fromView:将其他view中的坐标转换到当前view的坐标,相反地,convert...:toView:试讲当前view的坐标转换到其他view中的坐标.在上面4个方法中如果view的值设为nil,那么自动地认为和window进行转换.

UIWindow也停供了和UIView类似的工具方法:

  • convertPoint:fromWindow:
  • convertRect:fromWindow:
  • convertPoint:toWindow:
  • convertRect:toWindow:

这里有个涉及将一个旋转过的view中的坐标转换到其他view的问题,UIKit会算出该旋转view刚好包含旋转view的矩形框,然后再讲矩形框转换到其他view的坐标,看下图解释:


转换旋转后的view的坐标

如何在运行时调整view的大小和位置

只要view的size改变了,那么view的subview的position和size也要相应的改变. UIView提供两种方式进行View的布局:①自动布局(当superview变动时,设置view间的布局规则,实际的位置和大小有系统根据前面设置的规则自己计算) ②手动布局(superview的size改变时,开发者自己计算subview的size和position)

为布局变动做准备

布局的变动会因为下面的这些原因:

  • 改变view中bounds的size
  • 旋转了界面方向,通常会改变root view的bounds
  • view的layer中加了CoreAnimation要求改变布局
  • 调用了view的setNeedsLayoutlayoutIfNeeded方法
  • 给view的layer发送setNeedsLayout消息

使用Autoresizing(和autolayout不一样)进行布局

  • 当view的size改变时,view可以用属性autoresizesSubviews来控制subviews是否要重新resize. 如果给整个属性设置为NO,那么当view改变时它的subview也不会重新布局.
  • subview使用autoresizingMask来决定subview如何进行大小和位置的设置.
  • 同样的规则对subview的subview同样有效.

在自动布局的时候,给view设置autoresizingMask很重要,下表列举了autoresizingMask(宽高上下左右)可能的取值,和每一个值对应的布局操作,并且这些值可以叠加(做或运算),然后赋值给view的autoresizingMask. 如果你是XIB来矩形局部可以使用Autosizing inspector进行相应的设置.

Autoresizing Mask 描述
UIViewAutoresizingNone 不进行autoresize(默认值)
UIViewAutoresizingFlexibleHeight 高度随superview而变,如果不包含该值,高度不会改变
UIViewAutoresizingFlexibleWidth 宽度随superview而变,如果不包含该值,宽度不变
UIViewAutoresizingFlexibleLeftMargin view的左边和superview左边的距离可以可变,如果不包含该值,那么间距不变
UIViewAutoresizingFlexibleRightMargin view的右边和superview右边的距离可以可变,如果不包含该值,那么间距不变
UIViewAutoresizingFlexibleBottomMargin view的底边和superview底边的距离可以可变,如果不包含该值,那么间距不变
UIViewAutoresizingFlexibleTopMargin view的顶边和superview顶边的距离可以可变,如果不包含该值,那么间距不变

下图展示上面取值代表物理意义上的图示,某一个值的缺失代表这一物理意义是固定值,否则是随superview的大小可变. 如果你对view进行配置是,在同一轴上有多个可变配置,比如你对一个view同时设置UIViewAutoresizingFlexibleTopMarginUIViewAutoresizingFlexibleBottomMargin,那么UIKit会这一轴上平均的分配任意大小

autoresizingMask图示

上面的配置同xib中的Autoresizing inspector来设置autoresizingMask最简单,而且还有一个动画展示方便理解.

注意:如果view的transform的值不为identity transform,那么view的frame会失效,同样地对autoresizingMask也是一样.

当对view进行了autoresizing设置好,UIKit还有提供一个接口开发者手动的调整view的布局.

手动对view的布局进行调整

当一个view的size改变时,UIKit利用autoresizingMask对view的subview进行autoresizing,然后调用view的layoutSubViews方法,以供开发者手动调整.你可以在自定义view中重写该方法:

  • 调整subview的size和position
  • 添加或者移除subview或者CoreAnimation layer
  • 给subview发送setNeedsDisplaysetNeedsDisplayInRect:消息强制subview重绘

特别提醒:如果你的应用中有个需要滚动显示大量视图的view,那么layoutSubviews方法中的代码很重要. 因为用一大块显示所有的内容是不现实的,通常的做法是将大量内容分块显示在subview中,就像砖头(tile View)一样,可以复用. 所以view滚动时,在layoutSubViews中需要将显示完的tile View的位置放到即将要显示的位置,然后重绘它的内容. 关于如何显示tileview的具体做法可以参考Apple的demoScrollViewSuite

当你进行布局时,代码中要确认下面几件事:

  • 当旋转手机屏幕时,你的布局代码是否还能正确生效
  • 你的布局代码能否适应status bar高度的改变,因为status bar有时会变,比如电话进来后status bar的高会变化.
    想学更多关于autoresizing的知识请参考苹果文档Handling Layout Changes Automatically Using Autoresizing Rules

在运行时修改view

view会因为用户事件改变(size,position,hidden,或者创建一个view的层级树等),在iOS中view的改变可以发生下面的位置或者一下面的方法就行改变:

  • 在view controller中
    • view controller负责创建界面需要的view,可以从nib文件中加载,也可以从代码中创建,而且controller也负责干掉无用了的view
    • 当屏幕旋转时,controller负责调整view(大小位置隐藏创建等改变)
    • 当controller处理可编辑内容时,在进入/退出可编辑状态时,controller可能会调整view的层级树; 比如,添加一个额外的button和其他控件来处理编辑内容,这需要调整view的层级树.
  • 在Animation block中
    • 你可能会在Animation block处理两组view的切换,隐藏界面中的一组view然后显示另一组view
    • 当你需要实现一个特殊的动画时,你在Animation block中会对view的属性进行各种调整; 比如改变一个view的size
  • 其他方式
    • 你可能创建一组新的view以响应手势或者其它用户事件
    • 当你滚动scroll view时,你可能会同时隐藏和显示tile subview
    • 当键盘显示时,你可能会reposition和resize被键盘遮住的部分view,关于更多和键盘交互的知识请看Text Programming Guide for iOS

view controller是view的层级树的管理者,大部分的view的修改都发生在这里,controller是view改变的终极负责人. 特别地,你可以在view controller中的setEditing:animated:方法中,将用户界面切换到可编辑模式.

Animation block中是另一个频繁需要修改view的地方. UIView内置的动画接口可以做一些简单的动画,比如你可以用如下几个方法进行view的切换动画:

  • transitionWithView:duration:options:animations:completion:
  • transitionFromView:toView:duration:options:completion:

CoreAnimation Layers的交互

每个view都一个layer用来展示内容和动画. 尽管你通过view可以做很多,但你也可以直接操作view的layer

修改view的layer class

view中的layer类型在view创建后是不能修改的,因此可以通过view的layerClass类方法修改layer的类型.这个方法的默认实现是返回[CALayer class],你可以在自定义view中重写该方法然后返回想要的layer类型,如下代码返回CATiledLayer类型.

+ (Class)layerClass {
    return [CATiledLayer class];
}

每个view在初始化实例之前会调用上面的方法返回layer的类型,然后根据类型创建layer对象. 另外将view自己设置为layer的delegate,此时layer和view的联系就建立起来了,之后不能改变,你不能再将view自己设置为别的layer的delegate,如果你修改layer和view之间的关系,会导致view的内容绘制出问题,和其他一些不可预的问题(比如crash掉)

知道其他Layer类型和作用吗?请看Core Animation Reference Collection

往view中插入其他layer对象

如果你偏向使用layer而不是view,那么你可以将一个自定义的layer插入到view中. 一个自定义的layer对象是一个没有任何view绑定的CALayer实例. 自定义layer中要使用Core Animation代码,layer无法响应事件只能绘制内容,可以响应view的size变化
下面的代码展示了,如何使用layer,该layer用来显示一个图像:

- (void)viewDidLoad {
    [super viewDidLoad];
 
    // Create the layer.
    CALayer* myLayer = [[CALayer alloc] init];
 
    // Set the contents of the layer to a fixed image. And set
    // the size of the layer to match the image size.
    UIImage layerContents = [[UIImage imageNamed:@"myImage"] retain];
    CGSize imageSize = layerContents.size;
 
    myLayer.bounds = CGRectMake(0, 0, imageSize.width, imageSize.height);
    myLayer = layerContents.CGImage;
 
    // Add the layer to the view.
    CALayer*    viewLayer = self.view.layer;
    [viewLayer addSublayer:myLayer];
 
    // Center the layer in the view.
    CGRect        viewBounds = backingView.bounds;
    myLayer.position = CGPointMake(CGRectGetMidX(viewBounds), CGRectGetMidY(viewBounds));
}

你可以往view中加入多个layer,因为view的layer也有个数组属性sublayers来保存加入view中的layer, 具体请看Core Animation Programming Guide

如何自定义view

当UIKit提供的view无法满足需求时,就必须走上自定义view的道路. 自定义view可以完全由你控制,非常灵活.

注意:如果你使用OpenGL ES绘制内容的话,你必须使用GLKView代替继承UIView.具体请看OpenGL ES Programming Guide

关于实现自定义View的基本操作

实现自定义view要做的事主要有两件:①展示内容 ②管理view的交互,当想更好的实现自定义view光这两点还不够,下面列举了实现自定义view需要完成的步骤:

  • 给view定义几个何时的初始化方法:
    • 如果手动创建,需要重写initWithFrame:方法,或者自定义一个初始化方法
    • 如果重nib文件中加载,重写initWithCoder:方法,在该方法中对view进行一些状态设置
  • 显示dealloc方法,用来销毁一些对象的
  • 要想定制任何内容就需要重写drawRect:方法:
  • 设置属性autoresizingMask给view加上autoresizing功能
  • 如果你的view需要集成和管理许多的subview,那么:
    • 在初始化view的时候,创建subviews
    • 在创建subview的时候顺便设置各个subview的autoresizingMask属性
    • 如果view的subview需要手动布局,重写view的layoutSubviews
  • 实现touch-event,那么:
    • 通过addGestureRecognizer:方法给view添加合适的手势
    • 如果你想手动处理touches,那么可以重写view的touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:,touchesCancelled:withEvent: 四个方法(不管其他touch方法有没有重写,牢记你需要始终重写touchesCancelled:withEvent:方法)
  • 如果你想定制打印的view,那么你需要重写drawRect:forViewPrintFormatter:方法,具体请看Drawing and Printing Guide for iOS

另外,在重写上面提到的方法中,你可以对view的许多属性进行操作,比如contentMode,也可以直接地或间接地的操作layer

初始化自定义view

每个自定义的view都需要提供initWithFrame:初始化方法.该方法在你手动创建的view初始化时调用.下面的代码展示了一个initWithFrame:方法的模板,在重写该方法时,你需要调用父类的的方法,设置view的状态,初始化实例变量,然后再将初始化完成的view返回.

- (id)initWithFrame:(CGRect)aRect {
    self = [super initWithFrame:aRect];
    if (self) {
          // setup the initial properties of the view
          ...
       }
    return self;
}

如果从nib文件中加载view,那么你要记得回调用initWithCoder:方法而不是initWithFrame:,该方法是协议NSCoding的一部分. 在该方法中,你可以view的状态进行设置,也可以重写awakeFromNib方法对view进一步设置.

实现重绘

如果自定义view需要绘制内容,那么需要重写drawRect:方法,在刚方法中实现重绘. Apple建议如果不是迫不得已的话,最好还是不要走重绘的路,可以用其他view代替.

drawRect:方法中只能干和内容绘制相关的内容,像更APP的数据结构等其他和绘制无关的操作千万不要放到这个方法中.该方法中的任务要尽量快速完成,如果你频繁调该方法的话,那么需要优化你的绘制算法,能够快速完成.

在调用drawRect:方法前,UIKit会先给view配置内容绘制环境. 特别是创建graphic context对象和调整坐标系. 当环境创建后,你才能用UIKit和core graphic等技术进行绘制.可以通过UIGraphicsGetCurrentContext方法来获取当前绘画上下文.

注意:当前绘画上下文(current graphics context)只要在调用drawRect:时有效. UIKit可能会在不同绘制操作步骤中创建不同的绘画上下文,所以你不要将该对像缓存起来供未来使用.

下面代码展示了使用drawRect:方法绘制一个边宽为10.0的view:

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect    myFrame = self.bounds;
 
    // Set the line width to 10 and inset the rectangle by
    // 5 pixels on all sides to compensate for the wider line.
    CGContextSetLineWidth(context, 10);
    CGRectInset(myFrame, 5, 5);
 
    [[UIColor redColor] set];
    UIRectFrame(myFrame);
}

如果你知道你的view的内容是不透明的,那么你可以将view的opaque属性设置为YES,这样可以提高性能. 如果你设置NO的话,UIKit还要绘制被view遮住的内容.
另外一个提高view性能的操作是设置clearsContextBeforeDrawing为NO,特别地,当滚动view的时候.如果你设置为YES的话,在drawRect方法更新内容之前,UIKit要自动地将view设置透明黑色. 设置NO可以避免这一操作.

响应事件

view是一个响应者(因为UIView集成UIResponder). 为了能够直接响应事件,view可以通过手势监听像taps,swipes,pinches等等这些手势,但这是Apple封装好的,你要可以重写view的touches方法来自定义响应事件:

  • touchesBegan:withEvent:
  • touchesMoved:withEvent:
  • touchesEnded:withEvent:
  • touchesCancelled:withEvent:

如果你想开启多手指事件设置multipleTouchEnable为YES.
有的view,比如label是默认关闭监听用户事件的,既可以设置userInteractionEnabled为YES
你可以通过UIApplication对象的beginIgnoringInteractionEventsendIgnoringInteractionEvents方法来控制整个APP的事件响应能力

注意:用UIView提供的动画方法进行动画时是无法响应用户事件的. 你可以通过重写相应方法来配置相应的特性,具体细节请看本系列文章(四)
在事件传递过程中,可以通过hitTest:withEvent:pointInside:withEvent:方法判断一个view是否具有响应特定event的能力.

垃圾清理-dealloc

自定义view有时需要用到该方法来清理垃圾. 不过很少用.

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

推荐阅读更多精彩内容