iOS手势密码开发

前序

最近公司APP要做改版,其中要使用手势密码锁,所以提前写了一个小Demo练练手,该Demo参考了DBGuestureLock。这里将详细阐述代码细节,希望可以帮助到需要帮助的人。

相关界面GIF

Gesture.gif

代码实现

一、创建相关类

  • GesturePasswordButton
    该类主要是表示手势密码中的圆点
  • GesturePasswordView
    该类是手势密码View
  • GesturePasswordViewController
    该类是用于使用GesturePasswordView

二、绘制界面

从GIF图可以看出,手势密码一共有三种状态(正常、选择、不正确),因此现在GesturePasswordView.h文件中创建一个枚举类型

typedef NS_ENUM(NSInteger, GesturePasswordButtonState) {
    GesturePasswordButtonStateNormal = 0,
    GesturePasswordButtonStateSelected,
    GesturePasswordButtonStateIncorrect,
};

根据Button的形状,可以在GesturePasswordView.h中定义以下属性(之所以写在GesturePasswordView.h中是为了根据不同的状态改变button的样式)

@property (nonatomic, assign)CGFloat strokeWidth; //圆弧的宽度
@property (nonatomic, assign)CGFloat circleRadius; //半径
@property (nonatomic, assign)CGFloat centerPointRadius; //中心圆半径
@property (nonatomic, assign)CGFloat lineWidth; //连接线宽度
@property (nonatomic, strong)UIColor *strokeColor; //圆弧的填充颜色
@property (nonatomic, strong)UIColor *fillColor; //除中心圆点外 其他部分的填充色
@property (nonatomic, strong)UIColor *centerPointColor; //中心圆点的颜色
@property (nonatomic, strong)UIColor *lineColor; //线条填充颜色
@property (nonatomic, assign)BOOL showCenterPoint; //是否显示中心圆
@property (nonatomic, assign)BOOL fillCenterPoint; //是否填充中心圆

在GesturePasswordView.m文件中定义以下属性,用于后面的代码编写。(GesturePasswordStatus枚举类型将会在后面写出)

@property (nonatomic, strong) NSMutableArray *selectorAry;//存储已经选择的按钮
@property (nonatomic, assign)CGPoint currentPoint;//当前处于哪个按钮范围内
@property (nonatomic, assign)GesturePasswordStatus status;//当前控件器所处状态(设置、重新设置、登录)
@property (nonatomic, assign)NSInteger inputNum;//输入的次数
@property (nonatomic, assign)NSInteger resetInputNum;//重置密码时验证旧密码 输入的次数
@property (nonatomic, strong)NSString *firstPassword;//表示设置密码时 第一次输入的手势密码

在GesturePasswordView.m文件中写一个方法来改变button当前的属性值

- (void)setPropertiesByState:(GesturePasswordButtonState)buttonState{
    switch (buttonState) {
        case GesturePasswordButtonStateNormal:
            self.fillCenterPoint = YES;
            self.showCenterPoint = YES;
            self.strokeWidth = 1.f;
            self.centerPointRadius = 10.f;
            self.lineWidth = 2.f;
            self.lineColor = [UIColor whiteColor];
            self.fillColor = [UIColor whiteColor];
            self.strokeColor = [UIColor whiteColor];
            self.centerPointColor = [UIColor grayColor];
            break;
        case GesturePasswordButtonStateSelected:
            self.fillCenterPoint = YES;
            self.showCenterPoint = YES;
            self.strokeWidth = 1.f;
            self.centerPointRadius = 10.f;
            self.lineWidth = 2.f;
            self.lineColor = [UIColor greenColor];
            self.fillColor = RGBA(127, 255, 212, 1);
            self.strokeColor = RGBA(127, 255, 212, 1);
            self.centerPointColor = [UIColor greenColor];
            break;
        case GesturePasswordButtonStateIncorrect:
            self.fillCenterPoint = YES;
            self.showCenterPoint = YES;
            self.strokeWidth = 1.f;
            self.centerPointRadius = 10.f;
            self.lineWidth = 2.f;
            self.lineColor = [UIColor orangeColor];
            self.fillColor = RGBA(255, 245, 238, 1);
            self.strokeColor = RGBA(255, 245, 238, 1);
            self.centerPointColor = [UIColor orangeColor];
            break;
        default:
            break;
    }
}

在GesturePasswordView.m文件中使用initWithFrame:(CGRect)frame方法绘制手势图,这里GesturePasswordView的大小以(320,320)为基准。

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.selectorAry = [[NSMutableArray alloc] init];
        [self setPropertiesByState:GesturePasswordButtonStateNormal];
        NSInteger distance = 320/3;
        NSInteger size = distance/1.5;
        NSInteger margin = size/4;
        float ins = (SCREEN_WIDTH-320)/2;
        self.circleRadius = size/2;
        for (int i = 0; i < 9; i++) {
            NSInteger row = i/3;
            NSInteger col = i%3;
            GesturePasswordButton *gesturePasswordButton = [[GesturePasswordButton alloc] initWithFrame:CGRectMake(ins+col*distance+margin, row*distance+margin, size, size)];
            [gesturePasswordButton setTag:i+1];
            [self addSubview:gesturePasswordButton];
        }
        [self setBackgroundColor:[UIColor clearColor]];
    }
    return self;
}

在GesturePasswordButton.m文件中重新initWithFrame:(CGRect)frame方法。

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.userInteractionEnabled = NO;
    }
    return self;
}

在GesturePasswordButton.m文件中重新- (void)drawRect:(CGRect)rect方法。

  • 获取到父视图(GesturePasswordView),使用其相关描述button的属性。
    __weak GesturePasswordView *gesView = nil;
    if ([self.superview isKindOfClass:[GesturePasswordView class]]) {
        gesView = (GesturePasswordView *)self.superview;
    }
  • 绘制外圈大圆
    CGFloat radius = gesView.circleRadius - gesView.strokeWidth;
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, gesView.strokeWidth);
    CGPoint centerPoint = CGPointMake(rect.size.width/2, rect.size.height/2);
    CGFloat startAngle = -((CGFloat)M_PI/2);
    CGFloat endAngle = ((2 * (CGFloat)M_PI) + startAngle);
    [gesView.strokeColor setStroke];
    CGContextAddArc(context, centerPoint.x, centerPoint.y, radius+gesView.strokeWidth/2, startAngle, endAngle, 0);
    CGContextStrokePath(context);//CGContextStrokePath来描线,即形状 
  • 根据是否显示中心点绘制形状
if (gesView.showCenterPoint) {
        [gesView.fillColor set];//同时设置填充和边框色
        CGContextAddArc(context, centerPoint.x, centerPoint.y, radius, startAngle, endAngle, 0);
        CGContextFillPath(context);//CGContextFillPath来填充形状内的颜色
        if (gesView.fillCenterPoint) {
            [gesView.centerPointColor set];//同时设置填充和边框色
        }else{
            [gesView.centerPointColor setStroke];//设置边框色
        }
        CGContextAddArc(context, centerPoint.x, centerPoint.y, gesView.centerPointRadius, startAngle, endAngle, 0);
        if (gesView.fillCenterPoint) {
            CGContextFillPath(context);//CGContextFillPath来填充形状内的颜色
        }else{
            CGContextStrokePath(context);//CGContextStrokePath来描线,即形状 
        }
    }

整体drawRect方法如下:

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    __weak GesturePasswordView *gesView = nil;
    if ([self.superview isKindOfClass:[GesturePasswordView class]]) {
        gesView = (GesturePasswordView *)self.superview;
    }
    CGFloat width = rect.size.height > rect.size.width ? rect.size.width : rect.size.height;
    CGFloat radius = (width - 2*gesView.strokeWidth)/2;
    if (radius > (gesView.circleRadius - gesView.strokeWidth)) {
        radius = gesView.circleRadius - gesView.strokeWidth;
    }
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, gesView.strokeWidth);
    CGPoint centerPoint = CGPointMake(rect.size.width/2, rect.size.height/2);
    CGFloat startAngle = -((CGFloat)M_PI/2);
    CGFloat endAngle = ((2 * (CGFloat)M_PI) + startAngle);
    [gesView.strokeColor setStroke];
    CGContextAddArc(context, centerPoint.x, centerPoint.y, radius+gesView.strokeWidth/2, startAngle, endAngle, 0);
    CGContextStrokePath(context);//CGContextStrokePath来描线,即形状 
    
    if (gesView.showCenterPoint) {
        [gesView.fillColor set];//同时设置填充和边框色
        CGContextAddArc(context, centerPoint.x, centerPoint.y, radius, startAngle, endAngle, 0);
        CGContextFillPath(context);//CGContextFillPath来填充形状内的颜色
        
        if (gesView.fillCenterPoint) {
            [gesView.centerPointColor set];//同时设置填充和边框色
        }else{
            [gesView.centerPointColor setStroke];//设置边框色
        }
        
        CGContextAddArc(context, centerPoint.x, centerPoint.y, gesView.centerPointRadius, startAngle, endAngle, 0);
        if (gesView.fillCenterPoint) {
            CGContextFillPath(context);//CGContextFillPath来填充形状内的颜色
        }else{
            CGContextStrokePath(context);//CGContextStrokePath来描线,即形状 
        }
    }
}

这样的话只需要在GesturePasswordViewController中加载GesturePasswordView就可以显示出手势密码的界面。

三、实现三种手势状态

为了实现手势的三种状态,我们需要获取到用户的触摸动作,这里我们将使用UIResponder类,UIResponder类是专门用来响应用户的操作处理各种事件的,所以我们需要在GesturePasswordView.m实现touchesBegan、touchesMoved、touchesEnded方法。

  • touchesBegan方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self];//获取触摸点在view中的位置
    self.currentPoint = point;
    for (GesturePasswordButton *btn in self.subviews) {
        if (CGRectContainsPoint(btn.frame, point)) {//判断触摸点是否在当前btn的范围之内
            [btn setSelected:YES];
            if (![self.selectorAry containsObject:btn]) {
                [self.selectorAry addObject:btn];
                [self setPropertiesByState:GesturePasswordButtonStateSelected];//改变该btn的状态
            }
        }
    }
    [self setNeedsDisplay];//重绘view来画线
}
  • touchesMoved方法
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self];
    self.currentPoint = point;
    for (GesturePasswordButton *btn in self.subviews) {
        if (CGRectContainsPoint(btn.frame, point)) {
            [btn setSelected:YES];
            if (![self.selectorAry containsObject:btn]) {
                [self.selectorAry addObject:btn];
                [self setPropertiesByState:GesturePasswordButtonStateSelected];
            }
        }
    }
    [self setNeedsDisplay];
}
  • touchesEnded方法
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    GesturePasswordButton *btn = [self.selectorAry lastObject];
    [self setCurrentPoint:btn.center];
    [self setNeedsDisplay];
}
  • 调用drawRect方法 重绘view
- (void)drawRect:(CGRect)rect{
    [super drawRect:rect];
    if ([self.selectorAry count] == 0) {
        return;
    }
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path setLineWidth:self.lineWidth];
    [self.lineColor set];
    [path setLineJoinStyle:kCGLineJoinRound];
    [path setLineCapStyle:kCGLineCapRound];
    for (NSInteger i = 0; i < self.selectorAry.count; i ++) {
        GesturePasswordButton *btn = self.selectorAry[i];
        if (i == 0) {
            [path moveToPoint:[btn center]];
        }else{
            [path addLineToPoint:[btn center]];
        }
        [btn setNeedsDisplay];
    }
    [path addLineToPoint:self.currentPoint];
    [path stroke];
}
  • 为了实现当用户输入错误后,不正确状态显示0.5秒后恢复为正常状态,需要在上面实现的setPropertiesByState方法中加入如下代码,另外实现resetButtons和lockState方法
case GesturePasswordButtonStateNormal:
            [self setUserInteractionEnabled:YES];\\设置当前用户可以进行触摸操作
            [self resetButtons];
            self.fillCenterPoint = YES;
case GesturePasswordButtonStateIncorrect:
            [self setUserInteractionEnabled:NO];\\设置当前用户不可进行触摸操作
            self.fillCenterPoint = YES;
            self.showCenterPoint = YES;
            self.strokeWidth = 1.f;
            self.centerPointRadius = 10.f;
            self.lineWidth = 2.f;
            self.lineColor = [UIColor orangeColor];
            self.fillColor = RGBA(255, 245, 238, 1);
            self.strokeColor = RGBA(255, 245, 238, 1);
            self.centerPointColor = [UIColor orangeColor];
            [self performSelector:@selector(lockState:) withObject:[NSArray arrayWithObject:[NSNumber numberWithInteger:GesturePasswordButtonStateNormal]] afterDelay:0.5f];\\0.5秒后执行lockState方法
\\重置selectorAry数组
- (void)resetButtons {
    for (NSInteger i=0; i<[self.selectorAry count]; i++) {
        GesturePasswordButton *button = self.selectorAry[i];
        [button setSelected:NO];
    }
    [self.selectorAry removeAllObjects];
    [self setNeedsDisplay];
}
\\切换btn状态
- (void)lockState:(NSArray *)states {
    NSNumber *stateNumber = [states objectAtIndex:0];
    [self setPropertiesByState:[stateNumber integerValue]];
}

四、调用GesturePasswordView

现在我们要明确一下这个GesturePasswordView主要用于手势登录、设置手势密码、重置手势密码这三种情况。那么在GesturePasswordViewController.h中可以创建一个枚举类型。

typedef NS_ENUM(NSInteger, GesturePasswordStatus) {
    GesturePasswordStatusSet           = 0,\\设置
    GesturePasswordStatusReset         = 1,\\重置
    GesturePasswordStatusLogin         = 2,\\登录
};

登录、设置、重置三种状态下的流程图

流程图.png

根据流程图我们可以在GesturePasswordView.h中创建六种类型的Block方法

@property (nonatomic, copy) void(^verificationPassword)();//验证旧密码正确
@property (nonatomic, copy) void(^verificationError)();//验证旧密码错误
@property (nonatomic, copy) void(^onPasswordSet)();//第一次输入密码
@property (nonatomic, copy) void(^onGetCorrectPswd)();//第二次输入密码且和第一次一样
@property (nonatomic, copy) void(^onGetIncorrectPswd)();//第二次输入密码且和第一次不一样
@property (nonatomic, copy) void(^errorInput)();//手势密码小于四位数

根据三种状态我们可以在GesturePasswordView.h中创建三种初始化方法

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput;//用于登录

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame  onPasswordSet:(void (^)())onPasswordSet onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput;//用于设置

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame verificationPassword:(void (^)())verificationPassword verificationError:(void (^)())verificationError onPasswordSet:(void (^)())onPasswordSet onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput;//用于重置

在GesturePasswordView.m中实现这三种方法

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput
{
    return [self status:status frame:frame onPasswordSet:nil onGetCorrectPswd:GetCorrectPswd onGetIncorrectPswd:GetIncorrectPswd errorInput:errorInput];
}

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame  onPasswordSet:(void (^)())onPasswordSet onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput
{
    return [self status:status frame:frame verificationPassword:nil verificationError:nil onPasswordSet:onPasswordSet onGetCorrectPswd:GetCorrectPswd onGetIncorrectPswd:GetIncorrectPswd errorInput:errorInput];
}

+(instancetype)status:(GesturePasswordStatus)status frame:(CGRect)frame verificationPassword:(void (^)())verificationPassword verificationError:(void (^)())verificationError onPasswordSet:(void (^)())onPasswordSet onGetCorrectPswd:(void (^)())GetCorrectPswd onGetIncorrectPswd:(void (^)())GetIncorrectPswd errorInput:(void (^)())errorInput
{
    GesturePasswordView *gesView = [[GesturePasswordView alloc] initWithFrame:frame];
    gesView.status = status;
    gesView.verificationPassword = verificationPassword;
    gesView.verificationError = verificationError;
    gesView.onPasswordSet = onPasswordSet;
    gesView.onGetCorrectPswd = GetCorrectPswd;
    gesView.onGetIncorrectPswd = GetIncorrectPswd;
    gesView.errorInput = errorInput;
    return gesView;
}

在touchesEnded方法中,我们可以根据当前出入哪种状态做出相应的处理,所以touchesEnded方法应该是这样的:

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    if (self.selectorAry.count < 4) {
        self.errorInput();
        [self setPropertiesByState:GesturePasswordButtonStateNormal];
    }else if (self.status == GesturePasswordStatusSet) {
        [self setPasswordBlock];
    }else if(self.status == GesturePasswordStatusReset){
        NSString *password = [self getPassword];
        NSString *inputPassword  = [[NSString alloc] init];
        if (self.resetInputNum == 0) {
            for (GesturePasswordButton *btn in self.selectorAry) {
                inputPassword = [inputPassword stringByAppendingFormat:@"%@",@(btn.tag)];
            }
            if ([inputPassword isEqualToString:password]) {
                self.verificationPassword();
                self.resetInputNum += 1;
                [self performSelector:@selector(lockState:) withObject:[NSArray arrayWithObject:[NSNumber numberWithInteger:GesturePasswordButtonStateNormal]] afterDelay:0.3f];
            }else{
                self.verificationError();
                [self setPropertiesByState:GesturePasswordButtonStateIncorrect];
            }
        }else if(self.resetInputNum == 1){
            [self setPasswordBlock];
        }
    }else if(self.status == GesturePasswordStatusLogin){
        NSString *password = [self getPassword];
        NSString *inputPassword  = [[NSString alloc] init];
        for (GesturePasswordButton *btn in self.selectorAry) {
            inputPassword = [inputPassword stringByAppendingFormat:@"%@",@(btn.tag)];
        }
        if ([inputPassword isEqualToString:password]) {
            self.onGetCorrectPswd();
            [self setPropertiesByState:GesturePasswordButtonStateNormal];
        }else{
            self.onGetIncorrectPswd();
            [self setPropertiesByState:GesturePasswordButtonStateIncorrect];
        }
    }
    GesturePasswordButton *btn = [self.selectorAry lastObject];
    [self setCurrentPoint:btn.center];
    [self setNeedsDisplay];
}
- (void)setPasswordBlock
{
    if (self.inputNum == 0) {
        self.firstPassword = [[NSString alloc] init];
        for (GesturePasswordButton *btn in self.selectorAry) {
            self.firstPassword = [self.firstPassword stringByAppendingFormat:@"%@",@(btn.tag)];
        }
        self.onPasswordSet();
        self.inputNum += 1;
        [self performSelector:@selector(lockState:) withObject:[NSArray arrayWithObject:[NSNumber numberWithInteger:GesturePasswordButtonStateNormal]] afterDelay:0.3f];
    }else{
        NSString *secondPassword = [[NSString alloc] init];
        for (GesturePasswordButton *btn in self.selectorAry) {
            secondPassword = [secondPassword stringByAppendingFormat:@"%@",@(btn.tag)];
        }
        if ([self.firstPassword isEqualToString:secondPassword]) {
            [self savePassWord:secondPassword];
            self.onGetCorrectPswd();
            [self performSelector:@selector(lockState:) withObject:[NSArray arrayWithObject:[NSNumber numberWithInteger:GesturePasswordButtonStateNormal]] afterDelay:0.3f];
        }else{
            self.onGetIncorrectPswd();
            [self setPropertiesByState:GesturePasswordButtonStateIncorrect];
            self.inputNum -= 1;
        }
    }
}

存取密码的方法

- (void)savePassWord:(NSString *)password
{
    NSUserDefaults *userdefault = [NSUserDefaults standardUserDefaults];
    [userdefault setObject:@{@"password":password} forKey:GESTUREPASSWORD];
    [userdefault synchronize];
}

- (NSString *)getPassword
{
    NSDictionary *dic = [[NSUserDefaults standardUserDefaults] objectForKey:GESTUREPASSWORD];
    return [dic objectForKey:@"password"];
}

在GesturePasswordViewController.h这样调用

if (self.status == GesturePasswordStatusSet) {
        self.infoLabel.text = @"请设置手势密码";
        GesturePasswordView *view = [GesturePasswordView status:GesturePasswordStatusSet frame:CGRectMake(0, 200,  SCREEN_WIDTH, 400) onPasswordSet:^() {
            self.infoLabel.text = @"请重新输入刚才设置的手势密码";
        } onGetCorrectPswd:^ {
            self.infoLabel.text = @"设置成功";
            [weakSelf setGesturePasswordSwitchOpen];
        } onGetIncorrectPswd:^ {
            self.infoLabel.text = @"与上一次输入不一致,请重新设置";
        } errorInput:^{
            self.infoLabel.text = @"请至少连接4个点";
        }];
        [self.view addSubview:view];
    }else if (self.status == GesturePasswordStatusReset){
        self.infoLabel.text = @"请验证旧密码";
        GesturePasswordView *view = [GesturePasswordView status:GesturePasswordStatusReset frame:CGRectMake(0, 200,  SCREEN_WIDTH, 400) verificationPassword:^{
            self.infoLabel.text = @"请输入新的手势密码";
        } verificationError:^{
            self.infoLabel.text = @"旧密码验证错误";
        }  onPasswordSet:^ {
            self.infoLabel.text = @"请重新输入刚才设置的手势密码";
        } onGetCorrectPswd:^ {
            self.infoLabel.text = @"设置成功";
            [weakSelf setGesturePasswordSwitchOpen];
        } onGetIncorrectPswd:^ {
            self.infoLabel.text = @"与上一次输入不一致,请重新设置";
        } errorInput:^{
            self.infoLabel.text = @"请至少连接4个点";
        }];
        [self.view addSubview:view];
    }else if (self.status == GesturePasswordStatusLogin){
        self.infoLabel.text = @"请输入手势密码";
        GesturePasswordView *view = [GesturePasswordView status:GesturePasswordStatusLogin frame:CGRectMake(0, 200,  SCREEN_WIDTH, 400) onGetCorrectPswd:^ {
            self.infoLabel.text = @"解锁成功";
            [self loginSuccess];
        } onGetIncorrectPswd:^ {
            self.infoLabel.text = @"密码错误,请重新输入";
        } errorInput:^{
            self.infoLabel.text = @"请至少连接4个点";
        }];
        [self.view addSubview:view];
    }

结束语

整个思路大致就是这样的,主体实现的代码已经在文章中写出来了,当然还有手势密码锁的开启与关闭等一些细节代码没有写出,如果需要完整代码的可以前往github下载Demo。如果有什么不对的地方,希望大家可以及时的指出,谢谢!!!

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

推荐阅读更多精彩内容