事件传递、响应者链条


UITouch的解释及作用

  • 一根手指对应一个UITouch对象
  • 当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象
  • 保存着跟手指相关的信息,比如触摸的位置、时间、阶段
  • 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置
  • 当手指离开屏幕时,系统会销毁相应的UITouch对象

UITouch的方法

  - (CGPoint)locationInView:(UIView *)view;
返回值表示触摸在view上的位置
这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))
调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置

- (CGPoint)previousLocationInView:(UIView *)view;
该方法记录了前一个触摸点的位置


UITouch的属性

触摸产生时所处的窗口
@property(nonatomic,readonly,retain) UIWindow    *window;

触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView      *view;


UIEvent的解释及作用

  • 每产生一个事件,就会产生一个UIEvent对象

  • UIEvent:称为事件对象,记录事件产生的时刻和类型

  • UIEvent还提供了相应的方法可以获得在某个view上面的触摸对象(UITouch)


UIEvent的属性

@property(nonatomic,readonly) UIEventType     type;
@property(nonatomic,readonly) UIEventSubtype  subtype;
@property(nonatomic,readonly) NSTimeInterval  timestamp;


touches和event参数

一次完整的触摸过程,会经历3个状态:
触摸开始:- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
触摸移动:- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
触摸结束:- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
触摸取消(可能会经历):- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

  • 4个触摸事件处理方法中,都有NSSet *touches和UIEvent *event两个参数

  • 一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数

  • 如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象

  • 如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象

  • 根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸


事件的产生和传递

  • 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中

  • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)

    • 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步

    • 找到合适的视图控件后,就会调用视图控件的touches方法( touchesBegan…
      touchesMoved…
      touchedEnded…)来作具体的事件处理


触摸事件处理的详细过程

  • 用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件

  • 找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理(touchesBegan…,touchesMoved…,touchedEnded…)

  • touchesBegan方法的默认做法是将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理


响应者对象

  • 在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象”(注意:之前说的类对象其实就是类,例如UIImageView类对象,其实就是UIImageView类,而不是UIImageView类创建出来的对象)
  • UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件

UIResponder内部提供了以下方法来处理事件.因为UIApplication、UIViewController、UIView都继承UIResponder,所以这三个都能拥有以下方法(事件)
触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

加速计事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;


UIView不接收触摸事件的三种情况

  • 1:userInteractionEnabled = NO(不接收用户交互)
  • 2:hidden = YES(隐藏)
  • 3:alpha = 0.0 ~ 0.01(透明)
  • 提示:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的

触摸事件的传递

  • 触摸事件的传递是从父控件传递到子控件(最里面的就是父控件,最外面的就是子控件)
    • 点击了绿色的view,事件传递的过程:UIApplication -> UIWindow -> 白色 -> 绿色
    • 点击了蓝色的view,事件传递的过程:UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色
    • 点击了黄色的view,事件传递的过程:UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色 -> 黄色
  • 触摸事件的传递过程中,如果父控件不能接收触摸事件(上面的1,2,3),那么子控件也不可能接收到触摸事件(掌握)。
    • 这种情况接收到触摸事件的只有父控件的父控件。例如蓝色userInteractionEnabled = NO,那么子控件黄色就不能接收到触摸事件,此时点击黄色区域,提示橙色被点击,而不是提示黄色被点击。
    • 详细解释:点击黄色,触摸事件的传递过程是从父控件到子控件,当传递到橙色时,橙色要传递给蓝色,因为蓝色不接收用户交互,所以触摸事件就在经过橙色view停止,不再进行传递。
  • 如果父控件隐藏,那么里面的子控件也跟着隐藏
  • 如果父控件透明(alpha),那么子控件也跟着透明。不过在storyboard中看不出子控件也跟着透明,运行时才会看到

  • 如何找到最合适的控件来处理事件(利用事件的传递过程)?
    • A:自己是否能接收触摸事件?
    • B:触摸点是否在自己身上?(触摸点在子控件身上,若这个子控件有父控件,那么这个触摸点也在父控件身上)
    • C:从后往前遍历子控件,重复前面的两个步骤
    • D:如果没有符合条件的子控件,那么就自己最适合处理

事例+附图

  • 点击(触摸)黄色的View,找到最合适的控件来处理事件的详细步骤:
    • UIApplication从队列当中取出事件
    • 事件先传递给UIWindow
    • UIWindow满足A,B,此时事件传递给控制器的白色View
    • 白色的View满足A,B,此时白色的View把事件传递给橙色View(因为白色View子控件是绿色和橙色,在storyboard中,橙色是后添加的,后添加的先遍历)
    • 橙色的View满足A,B,此时橙色的view把事件传递给红色的View
    • 红色的View满足A,不满足B。然后橙色的view把事件传递给蓝色的View
    • 蓝色的View满足A,B。蓝色的View把事件传递给黄色的View
    • 黄色的View满足A,B,但是没有子控件,所以C,D不满足,最终蓝色的View来处理事件,所以执行蓝色的View点击
16-02.png

16-03.png

16-04.png

精华总结1:

  • 判断点在不在白色的View身上,一定要以白色View的左上角为坐标原点,不能以父控件(红色View)的左上角为原点判断,
  • 判断依据:判断点A(x,y)是否大于当前View(白色View)的宽度和高度,如果有一个不满足,则点不在当前的View上。例如如果A的坐标为(15,4),而白色View的宽高都为28,所以满足条件,可得知A点在白色View上
  • 如果以红色View的左上角为坐标原点,判断当前点A是在白色View上的话,A点的坐标为(50,30),而白色的View的宽高都为28,所以不满足条件,由此判断A点不在白色View。
    -判断点在不在白色View身上的官方语言是判断当前点在不在方法调用者身上。就是说,当前方法的调用者:白色View
  • 精华:hitTest方法内部实现了A,B,C,D(详细看下面的代码)。 所以说方法的调用者就是 执行白色View的hitTest方法

精华总结2(结合总结1):

  • 事件传递到红色View时,执行红色view的hitTest方法,hitTest方法里面的A,B,C,执行到C,从后往前遍历子控件,子控件就是白色View,执行白色View(截图16-06)的hitTest方法,参数中的point是父控件红色View的传递过来的坐标,所以传递过来的point要转换成当前方法所在的View的piont

  • 转换规则:当前点的位置不变,变得只是坐标原点(父控件的坐标原点转成了子控件的坐标原点。)然后计算点的位置距离这个子控件的x,y坐标,这个坐标就是这个点在子控件的point,如果此时point大于这个子控件的宽和高,那么point不在这个子控件上
    代码方式转换:如16-06

  • 代码截图(结合 总结1,总结2)

16-06.png
  • 详细图解 (结合 总结1,总结2)
16-05.png

HitTest方法底层实现

  • 精华:hitTest方法内部实现了A,B,C,D
//什么时候调用:当一个事件传递给当前View时调用
//作用:寻找最适合的View
//返回值:找到的最适合的View,如果没有找到最适合的View,返回是一个nil.
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    //1.判断当前View能否接收事件
    if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
        return nil;
    }
    //2.判断当前点在不在View身上
    if (![self pointInside:point withEvent:event]) {
        return nil;
    }
    //3.从后往前遍历自己的子控件.让自己的子控件寻找最适合的View;
    int count = (int)self.subviews.count;
    for (int i = count - 1; i >= 0; i--) {
        //取出子控件
        UIView *childV = self.subviews[i];
        //把当前点的坐标(父控件)系转换成子控件身上的坐标系
        CGPoint childP = [self convertPoint:point toView:childV];
        UIView *fitView = [childV hitTest:childP withEvent:event];
        if (fitView) {
            return fitView;
        }
    }
    //4.没有找到比自己更适合的view,那么它自己就是最适合的view
    return self;
    
    
}
//什么时候调用:在hitTest方法当中调用
//作用:判断当前点在不在方法调用者身上.
//返回值:如果返回yes,在View身,如果 为NO,不在View身上.
//传入的点point,必须得与方法调用者在同一个坐标系(即point是父控件中的坐标,必须转换成子控件身上的坐标,即坐标原点要由父控件变成子控件的坐标原点,唯一不变的是坐标代表的距离不变,这个距离要和当前子控件的宽高进行比较,如果都小于的话,才能证明这个带你在子控件身上).
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
//    return YES;
//}
@end

hitTest方法练习


CharBtn.h文件

#import <UIKit/UIKit.h>

@interface ChatBtn : UIButton

/** <#注释#>*/
@property (nonatomic, weak) UIButton *popBtn;

@end

CharBtn.m文件

#import "ChatBtn.h"

@implementation ChatBtn
//当第一次点击"弹出对话框"时,按照事件的传递过程:APPLication--->UIWindow--->"弹出对话框"按钮,执行到"弹出对话框"按钮时候,所以执行"弹出对话框"重写的hitTest方法(重写只为拦截之用,但还是会执行btnclick方法),在这个方法里面进行判断(A,B,C,D步骤),结果返回系统的做法return [super hitTest:point withEvent:event];所以跳出hitTest方法之后,就会执行btnclick方法.在里面创建一个btn控件并添加到父控件"弹出对话框",并把这个控件赋值给ChatBtn类型的对象sender的popBtn属性,从而popBtn就拿到了btn,拥有了btn的图片以及尺寸存储在内存中
//第一次点击"弹出对话框"时打印两行null的原因: 第一行:因为没有给self.popBtn赋值,所以self.popBtn为null.第二行:self.popBtn为null,返回的是一个方法,所以再次执行这个重写的方法就返回了,打印第二行null。

//第二次点击"对话框",按照事件的传递过程:APPLication--->UIWindow--->"弹出对话框"按钮,执行到"弹出对话框"按钮时候,"弹出对话框"的hitTest:方法同样会进行拦截(虽然点击的是"对话框",但是因为事件是有传递过程的,每传递到一个控件时,都会执行控件的hitTest方法),self.popBtn此时已经被对话框(小孩)btn赋值,所以就会执行return self.popBtn;就会执行“对话框”按钮的响应事件,即点击“对话框”按钮有反应(popBtn里面之前已经存储了不惦记btn的图片和点击btn时图片的状态以及尺寸等).注意:不会来到btnClick方法,因为你点击的是"对话框“按钮(点在对话框"按钮身上).如果返回的是系统原有的做法return [super hitTest:point withEvent:event];,那肯定是执行btnClick方法(就相当于没重写htiTest方法,即:点击谁,就执行谁的btnClick)。


//如果 一个子控件超出父控件的大小, 默认情况下是不能处理的事件.
//点击"弹出对话框"按钮的子控件是 "对话框"按钮.  并且子控件超出了父控件的范围

//精华:点击对话框按钮,事件传递过程:APPLication--->UIWindow--->按钮,执行到按钮时,在按钮的hitTest方法中先判断出按钮能接收事件,但是点不在按钮身上(因为子控件超出了父控件的大小,并且当前点是父控件未包含的区域,)所以不会执行第三步(不会从后往前遍历按钮的子控件,),就会执行第四步:没有符合条件的子控件,那么就自己最适合处理,所以"弹出对话框"按钮自己处理

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    //通过sender.popBtn = btn;拿到对话框按钮btn,在这个类中用self.popBtn表示
    //point是点击"对话框"按钮中的某个点(当然第一次点击"弹出对话框"时,point指的就是那时的点,第二次点击时,才是“对话框”按钮的点)
    
    
     NSLog(@"%@--",self.popBtn);
    //如果点在自己的子控件上面.让子控件处理事件.
    if (self.popBtn) {
        //把当前点转换成"对话框"坐标系上面的点
        CGPoint popBtnP = [self convertPoint:point toView:self.popBtn];
        if ( [self.popBtn pointInside:popBtnP withEvent:event]) {//判断点在不在对话框"按钮身上
            return self.popBtn;
        }else {
            return [super hitTest:point withEvent:event];//点不在对话框"按钮身上,保持系统原有的做法
        }
    }
    
    return   [super hitTest:point withEvent:event];
}

//
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //获取UITouch
    UITouch *touch = [touches anyObject];
    //获取当前手指的点
    CGPoint curP = [touch locationInView:self];
    //获取上一个手指所在的点
    CGPoint preP = [touch previousLocationInView:self];
    //计算偏移量
    CGFloat offsetX = curP.x - preP.x;
    CGFloat offsetY =curP.y - preP.y;
    //平移
    self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}



@end

ViewController.h文件

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController


@end

ViewController.m文件

#import "ViewController.h"
#import "ChatBtn.h"

@interface ViewController ()

@end

@implementation ViewController

//重:因为"弹出对话框"按钮已经和ChatBtn绑定,所以点击"弹出对话框",传递的sender的类型是CharBtn类型
- (IBAction)btnClick:(ChatBtn *)sender {
    
    //添加一个子控件
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    [btn setImage:[UIImage imageNamed:@"对话框"] forState:UIControlStateNormal];
    //UIControlStateHighlighted可以添加图片,UIControlStateSelected不可以添加图片,只能被选中
    [btn setImage:[UIImage imageNamed:@"小孩"] forState:UIControlStateHighlighted];
    btn.frame = CGRectMake(50, -80, 100, 80);
    //btn是UIButton类型的指针,popBtn也是UIButton类型的指针,所以可以赋值
    sender.popBtn =btn;
    //sender.popBtn =(ChatBtn *)btn;
    
    [sender addSubview:btn];
}
@end

Main.storyboard文件

16-08.png


触摸事件处理的详细过程

  • 用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件

  • 找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理(touchesBegan…,touchesMoved…,touchedEnded…)

  • touchesBegan方法的默认做法是将事件顺着响应者链条向上传递(父类),将事件交给上一个响应者进行处理


事件响应代码

  • 上一个响应者
    • 1.点击当前的View,如果当前的View是控制器的View.那么它的上一个响应者就是所在的控制器
    • 2.如果不是控制器View,那么它的上一个响应者就是它的父控件
//重写系统的方法--->保持系统默认做法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    [super touchesBegan:touches withEvent:event];//保持系统默认做法
}

//重写系统的方法--->处理事件,没有保持系统默认做法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@---touch",[self class]);   //处理事件(打印当前类),没有保持系统默认做法
 

}

//重写系统的方法--->处理事件,并保持系统默认做法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@---touch",[self class]);//处理事件(打印当前类)
    [super touchesBegan:touches withEvent:event];//保持系统默认做法,即顺着响应者链条传递给上一个响应者(父类)。
}
  • E:左侧的图:事件传递到了绿色view
    • 如果绿色view不处理(事件),就会保持系统默认的做法,沿着响应者链条(1)向上传递给黄色View
    • 如果黄色view也不处理(事件),就会保持系统默认的做法,沿着响应者链条(2)向上传递给青色View(控制器的view)。
    • 如果青色view(控制器的view)也不处理(事件),就会保持系统默认的做法,沿着响应者链条(3)向上传递给View Controlleer控制器
    • 如果View Controlleer控制器也不处理事件,就会保持系统默认的做法,沿着响应者链条(4)向上传递给window(窗口)
    • 如果window(窗口)也不处理事件,就会保持系统默认的做法,沿着响应者链条(5)向上传递给Application
    • 此时Application发现没有人处理事件,那么这个事件就会被抛弃掉

  • F:右侧的图:事件传递到了绿色view
    • 如果绿色view不处理(事件),就会保持系统默认的做法,沿着响应者链条(1)向上传递给黄色View(子控制器的view)
    • 如果黄色view(子控制器的view)也不处理,就会保持系统默认的做法,沿着响应者链条(2)向上传递给iew Controlleer控制器
    • 如果View Controlleer控制器也不处理事件,就会保持系统默认的做法,沿着响应者链条(3)向上传递给青色view(父控制器的view)
    • 如果青色view(父控制器的view)也不处理(事件),就会保持系统默认的做法,沿着响应者链条向上传递给View Controlleer控制器
    • 如果View Controlleer控制器也不处理事件,就会保持系统默认的做法,沿着响应者链条(4)向上传递window(窗口)
    • 如果window(窗口)也不处理事件,就会保持系统默认的做法,沿着响应者链条(5)向上传递给Application
    • 此时Application发现没有人处理事件,那么这个事件就会被抛弃掉

事件的传递和响应过程(看ABCDEF): 传递:在hitTest方法中找到适合的view(A,B,C,D) 响应:在toucherBegan方法中处理事件(E或F)

16-07.png

UIGestureRecognizer(手势识别器)

  • UIGestureRecognizer是一个抽象类,定义了所有手势的基本行为,使用它的子类才能处理具体的手势
    • UITapGestureRecognizer(敲击)
    • UIPinchGestureRecognizer(捏合,用于缩放)
    • UIPanGestureRecognizer(拖拽)
    • UISwipeGestureRecognizer(轻扫)
    • UIRotationGestureRecognizer(旋转)
    • UILongPressGestureRecognizer(长按)
  • 如果将手势识别的代理方法(下面的方法):支持多个手势设置为NO,那么系统默认只能识别一个手势.
    • 例如:果你添加了2个手势(旋转,缩放)到代码中,当你运行时,旋转图片的时候,不能缩放, 缩放图片的时候不能旋转
//是否允许支持多个手势
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {

    return NO;
}


//旋转,缩放,位移中:  
//第一个参数是上次(之前)的位置
//第二个参数(第三个参数)是距离上次(之前)位置的偏移量
//等号左边的self.imageV.transform是(这次)现在的位置(现在的位置是根据右边的参数计算出来的位置)

//旋转
 self.imageV.transform = CGAffineTransformRotate(self.imageV.transform, rotation.rotation);
 
 //缩放
 self.imageV.transform = CGAffineTransformScale(self.imageV.transform, pinch.scale, pinch.scale);
 
 //位移
 self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform, transP.x, transP.y);

手势识别

#import "ViewController.h"

@interface ViewController ()<UIGestureRecognizerDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *imageV;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
   self.imageV.userInteractionEnabled = YES;

    [self panGes];
    
}

//是否允许支持多个手势
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {

    return NO;
}

//拖动手势
- (void)panGes {
    //1.创建手势
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    //2.添加手势
    [self.imageV addGestureRecognizer:pan];
    
}


//当拖动时调用
- (void)pan:(UIPanGestureRecognizer *)pan {
    
    //NSLog(@"%s",__func__);
    //获取偏移量(相对于最原始的值),用transP表示。例如偏移量为100(相对于0来说)(其实是有x和y值)
    //pan.view是当前手势的所在view。所在的view是UIImageView。所以pan.view等价于self.imageV;
    CGPoint transP = [pan translationInView:pan.view];
    NSLog(@"transP=%@",NSStringFromCGPoint(transP));//1

    //CGAffineTransformTranslate的第一个参数是记录上次的位置,第二个参数和第三个参数是偏移量x和偏移量y,只有知道上次的位置和偏移量,才能知道下次的位置
    //如果屏蔽2的代码,当你拖动图片的时候,其实系统底层已经记录了你拖动到了什么位置,只是不显示。通过1的打印就可以知道,只是我们通过2将拖动到的位置显示了出来,仅此而已
    self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform, transP.x, transP.y);//2
    
//复位(相对于上一次的操作) ,把偏移量为1进行清零,那么下次获取偏移量时,就相对于0来说,如果不清零,下次真实的偏移量若为200时,就会走100+200=300的距离,但是实际的偏移量为200
    [pan setTranslation:CGPointZero inView:pan.view];
}


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

推荐阅读更多精彩内容