iOS 手势滑动解锁功能简析

题记

在平常的生活中,我们大概经常遇见手势滑动解锁---也就是九宫格啊,已经出现好久了,虽然随着Apple的指纹解锁的发展手势解锁虽然还有但是因为其不如指纹解锁方便也用的也少了,但是在大多数APP中这两种方式都是并存的,比如qq,微信,支付宝等等,最近项目里面也刚好有这个需求,趁着刚完成抽出时间来记录下来当时的一些思路,可能有的地方理解的不到位,还需多总结,闲言少叙了,看重点。
功能描述如图:大概说一下思路,这个功能用来做相当于密令,用于两端的匹配,教师端设置了路径生成密码,储存在本地,学生端用来滑动输入进行验证。然后根据各种情况上部的label给与各种提示信息,这里只是一个比较原始简陋的demo,也只是实现了我们最常见的功能,所以重在领会其中的精神了,哈哈哈。


GestureUnlockPic.gif

功能模块分析

根据GIF可以简单的把这块儿功能分为几个部分来理解,第一个就是首页:ViewController,首页里面有两个Controller分别是StudViewControllerTeacViewController用来分作不同功能的载体。在StudViewController中分为三部分上中下statusLabel GestureLockView clearBtn,其中GestureLockView是手势解锁界面。在TeacViewController中也是分为三部分,statusLabel GestureLockView BottomView下方的bottomView分为两个button resetBtn重置按钮sureBtn确定按钮。说完大体的结构,接下来分部分说一下每个功能的实现思路。

拆解分析

首先说一下GestureLockView这个view控件:

首先在.h 文件中

  • 定义两个枚举:分别用来定义两端的不同类型,stu端用ResultKindType来对画的手势结果进行分类分为这四类,下面都会用到,并说明用途。teac端用TeacKindType来分两类:
//检测手势密码答案情况 对/错/不够4个数字
typedef NS_ENUM(NSUInteger, ResultKindType) {
    ResultKindTypeTrue,
    ResultKindTypeFalse,
    ResultKindTypeNoEnough,
    ResultKindTypeClear
};

typedef NS_ENUM(NSUInteger, TeacKindType) {
    TeacKindTypeNoEnough,
    TeacKindTypeTrue
};
  • 协议:用来监听手势变化时传出的转化的密码(这个密码是用button的tag值来表示的),因为手势是在变化的,所以这里用了NSMutableString向外传递。
@protocol GestureLockDelegate <NSObject>

- (void)gestureLockView:(GestureLockView *)lockView drawRectFinished:(NSMutableString *)gesturePassword;

@end
  • 属性:
@property (nonatomic, weak) id<GestureLockDelegate> delegate;

@property (nonatomic, assign) BOOL isTeac;//教师端 用来验证是否是教师端
  • 方法:
- (void)clearLockView;//清除布局 重新开始

- (void)checkPwdResult:(ResultKindType)resultType;//检测学生端的结果

- (void)checkTeacResult:(TeacKindType)resultType;//检测老师端的结果

.m文件中(具体创建和应用的方法)

  • 声明变量供下方使用:
@interface GestureLockView ()

@property (strong, nonatomic) NSMutableArray *selectBtns;//选中的按钮数组
@property (nonatomic, strong) NSMutableArray *errorBtns;//错误的按钮数组
@property(nonatomic, assign)BOOL finished;//是否完成
@property (nonatomic, assign) CGPoint currentPoint;//当前触摸点
@property (nonatomic, assign) ResultKindType resultType;//学生端结果
@property (nonatomic, assign) TeacKindType teacResultType;//教师端结果

@end
  • 懒加载初始化数组
- (NSMutableArray *)selectBtns {
    if (!_selectBtns) {
        _selectBtns = [NSMutableArray array];
    }
    return _selectBtns;
}

- (NSMutableArray *)errorBtns {
    if (!_errorBtns) {
        _errorBtns = [NSMutableArray array];
    }
    return _errorBtns;
}
  • 子视图初始化:在这里创建9个按钮并将它们add到self中,并给self 添加UIPanGestureRecognizer *pan手势
- (void)initSubviews {
    self.backgroundColor = [UIColor clearColor];
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self addGestureRecognizer:pan];
    
    //创建9个按钮
    for (NSInteger i = 0; i < 9; i++) {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        btn.userInteractionEnabled = NO;
        [btn setImage:[UIImage imageNamed:@"sign_img_circle_n"] forState:UIControlStateNormal];
        [btn setImage:[UIImage imageNamed:@"sign_img_circle_s"] forState:UIControlStateSelected];
        btn.tag = i+1;
        [self addSubview:btn];
    }
}
  • 界面布局:
   CGFloat minWidth = MIN(self.bounds.size.height, self.bounds.size.width);
    CGFloat boundsWidth = self.bounds.size.width;
    CGFloat margin = (minWidth - cols * w) / (cols + 1);//间距
    CGFloat xMargin = (boundsWidth-2*margin-3*w)/2;

这里这一块是对不同机型进行的适配,因为这个控件有可能会被添加在一个height<width的view上,所以在这里margin是指比较短的长度也就是上下的height,xMargin是用来对width进行伸展的,这块是这个逻辑,应该可以通用的。

- (void)layoutSubviews {
    [super layoutSubviews];
    NSUInteger count = self.subviews.count;
    int cols = 3;//总列数
    CGFloat x = 0,y = 0,w = 0,h = 0;
    if (SCREEN_WIDTH == 320) {
        w = 50;
        h = 50;
    } else {
        w = 60;
        h = 60;
    }
    CGFloat minWidth = MIN(self.bounds.size.height, self.bounds.size.width);
    CGFloat boundsWidth = self.bounds.size.width;
    CGFloat margin = (minWidth - cols * w) / (cols + 1);//间距
    CGFloat xMargin = (boundsWidth-2*margin-3*w)/2;
    
    CGFloat col = 0;
    CGFloat row = 0;
    for (int i = 0; i < count; i++) {
        col = i % cols;
        row = i / cols;
        if (i == 0 || i == 3 || i == 6) {
            x = xMargin;
        } else if (i == 1 || i == 4 || i == 7) {
            x = xMargin + w + margin;
        } else {
            x = xMargin + 2 * (margin+w);
        }
        y = (w+margin)*row;
        UIButton *btn = self.subviews[i];
        btn.frame = CGRectMake(x, y, w, h);
    }
}
  • 手势代理方法: 在手势代理方法中监听手势滑动位置的改变,当pan.state == UIGestureRecognizerStateBegan开始滑动的时候,从盛放显示错误状态的button改变为普通初始状态,并且将self.errorBtns数组清除。将_currentPoint = [pan locationInView:self];检测当滑动时当前的位置point,如果划过的位置包含在button的范围内,如果button.selected == NO那么将button.selected = YES;//设置为选中并且将button添加到self.selectBtns数组中[self.selectBtns addObject:button];调用[self setNeedsDisplay];方法进行重绘,调用setNeedsDisplay方法系统会自动调用drawReact方法进行界面的重新布局。最后监听手指是否松开//监听手指松开 if (pan.state == UIGestureRecognizerStateEnded) { self.finished = YES; }如果松开将self.finished = YES;
#pragma mark 手势
- (void)pan:(UIPanGestureRecognizer *)pan {
    
    if (pan.state == UIGestureRecognizerStateBegan) {
        for (UIButton *btn in _errorBtns) {
            [btn setImage:[UIImage imageNamed:@"sign_img_circle_n"] forState:UIControlStateNormal];
            [btn setImage:[UIImage imageNamed:@"sign_img_circle_s"] forState:UIControlStateSelected];
        }
        [self.errorBtns removeAllObjects];
    }
    _currentPoint = [pan locationInView:self];
    
    for (UIButton *button in self.subviews) {
        if (CGRectContainsPoint(button.frame, _currentPoint)) {
            if (button.selected == NO) {
                //点在按钮上
                button.selected = YES;//设置为选中
                [self.selectBtns addObject:button];
            } else {
                
            }
        }
    }
    
    //重绘
    [self setNeedsDisplay];
    //监听手指松开
    if (pan.state == UIGestureRecognizerStateEnded) {
        self.finished = YES;
    }
}
  • 传递设置的手势密码方法

//传递设置的手势密码
- (NSMutableString *)transferGestureResult {
    //创建可变字符串
    NSMutableString *result = [NSMutableString string];
    for (UIButton *btn in self.selectBtns) {
        [result appendFormat:@"%ld", btn.tag - 1];
    }
    return result;
}
  • 两端的外部操作对内部状态的改变:

case ResultKindTypeFalse:
_errorBtns = [NSMutableArray arrayWithArray:self.selectBtns];
break;

这里如果绘制错误,将之前选中的按钮放在 _errorBtns盛放错误按钮的数组中

case ResultKindTypeClear:
{
[[UIColor clearColor] set];
for (int i = 0; i < self.errorBtns.count; i++) {
UIButton *btn = [self.errorBtns objectAtIndex:i];
[btn setImage:[UIImage imageNamed:@"sign_img_circle_n"] forState:UIControlStateNormal];
}
[self.errorBtns removeAllObjects];
}
break;

当外界改变状态为“清除”时,将路径置为透明 并改变errorBtns数组中button的背景色状态,并且将errorBtns清空,这里为什么在这里做这些改变呢?这是因为在学生端进行清除操作的时候,手势的状态已经是finish并且这是后代理方法已经走完,并且我们在调用clearLockView方法的时候也将selectBtns数组清空了,因此在setNeedsDisplay被调用,drawReact进行重绘的时候,检测到if (_selectBtns.count == 0) return;也就不再继续往下进行了。

//学生端状态改变
- (void)checkPwdResult:(ResultKindType)resultType {
    self.resultType = resultType;
    switch (resultType) {
        case ResultKindTypeFalse:
            _errorBtns = [NSMutableArray arrayWithArray:self.selectBtns];
            break;
            case ResultKindTypeTrue:
            break;
            case ResultKindTypeNoEnough:
            break;
            case ResultKindTypeClear:
        {
            [[UIColor clearColor] set];
            for (int i = 0; i < self.errorBtns.count; i++) {
                UIButton *btn =  [self.errorBtns objectAtIndex:i];
                [btn setImage:[UIImage imageNamed:@"sign_img_circle_n"] forState:UIControlStateNormal];
            }
            [self.errorBtns removeAllObjects];
        }
            break;
        default:
            break;
    }
    [self clearLockView];
}
//教师端状态改变
- (void)checkTeacResult:(TeacKindType)resultType {
    self.teacResultType = resultType;
    switch (resultType) {
        case TeacKindTypeTrue:
            break;
        case TeacKindTypeNoEnough:{
            [self clearLockView];
        }
            break;
        default:
            break;
    }
}
  • 清除方法: 这里将self.finished = NO;因为如果是通过代理将值传到外界并且外界对该值进行了校验,对内容的resultType进行改变,这时候其实还是在drawReact方法中,并且已经走完了self.finished 方法,在这里将其设置为NO 是为了改变下一次绘制的finished状态,虽然不起眼,但是也是写出来了。
- (void)clearLockView {
    self.finished = NO;
    //遍历所有选中的按钮
    for (UIButton *btn in self.selectBtns) {
        //取消选中状态
        btn.selected = NO;
    }
    [self.selectBtns removeAllObjects];
    //
    [self setNeedsDisplay];
}
  • 利用贝塞尔曲线绘制路径,并根据对应的状态修改路径颜色,按钮颜色,当系统调用这个方法的时候会进行重绘,重绘时,从self.selectBtns数组中取出选中的button,当button是第一个的时候将其设置为bezierPath的起点[path moveToPoint:btn.center];其余的按钮绘制路径[path addLineToPoint:btn.center];。在这里判断是否松开手指,在这个判断里逻辑判断比较多,大致捋一下,当self.finished == YES的时候,就将创建的密码利用先前声明的代理传递出去在外部进行检验。根据外部返回的操作状态,如果是self.isTeac根据返回的结果状态进行判断

case TeacKindTypeNoEnough:
{
[[UIColor clearColor] set];
}
break;
case TeacKindTypeTrue:
{
[[UIColor colorWithRed:94/255.0 green:195/255.0 blue:49/255.0 alpha:0.8] set];
}

如果画的点不足4个,那么清除选中的点并且将绘制路径的颜色透明(或者如果考虑到性能的话,最好用和背景色一样的颜色),如果是绘制完成,用“绿色”填充,这里我选择了教师绘制完毕不清楚绘制路径,以便查看。
如果是学生端的话:绘制正确清除路径,错误用“红色”标识路径并从盛放错误按钮的数组self.errorBtns中取出按钮进行状态的改变。

switch (self.resultType) {
case ResultKindTypeTrue:
{
//正确
[[UIColor clearColor] set];
}
break;
case ResultKindTypeFalse:
{
//错误
[[UIColor redColor] set];
for (int i = 0; i < self.errorBtns.count; i++) {
UIButton *btn = [self.errorBtns objectAtIndex:i];
[btn setImage:[UIImage imageNamed:@"sign_img_circle_p"] forState:UIControlStateNormal];
}
break;
case ResultKindTypeNoEnough:
{
[[UIColor clearColor] set];
}
break;
case ResultKindTypeClear:
break;
default:
break;
}

之后便是若没有finish 就是在绘制路径了,对path进行一些相关设置。

- (void)drawRect:(CGRect)rect {
    if (_selectBtns.count == 0) return;
    // 把所有选中按钮中心点连线
    UIBezierPath *path = [UIBezierPath bezierPath];
    for (int i = 0; i < self.selectBtns.count; i ++) {
        UIButton *btn = self.selectBtns[i];
        if (i == 0) {
            [path moveToPoint:btn.center]; // 设置起点
        } else {
            [path addLineToPoint:btn.center];
        }
    }
    
    //判断是否松开手指
    if (self.finished) {
        //松开手
        NSMutableString *pwd = [self transferGestureResult];//传递创建的密码
        [[UIColor colorWithRed:94/255.0 green:195/255.0 blue:49/255.0 alpha:0.8] set];
        if ([self.delegate respondsToSelector:@selector(gestureLockView:drawRectFinished:)]) {
            [self.delegate gestureLockView:self drawRectFinished:pwd];
        }
        
        if (self.isTeac) {
            //教师端
            switch (self.teacResultType) {
                case TeacKindTypeNoEnough:
                {
                    [[UIColor clearColor] set];
                }
                    break;
                case TeacKindTypeTrue:
                {
                    [[UIColor colorWithRed:94/255.0 green:195/255.0 blue:49/255.0 alpha:0.8] set];
                }
                    break;
                default:
                    break;
            }
        } else {
            switch (self.resultType) {
                case ResultKindTypeTrue:
                {
                    //正确
                    [[UIColor clearColor] set];
                }
                    break;
                case ResultKindTypeFalse:
                {
                    //错误
                    [[UIColor redColor] set];
                    for (int i = 0; i < self.errorBtns.count; i++) {
                        UIButton *btn =  [self.errorBtns objectAtIndex:i];
                        [btn setImage:[UIImage imageNamed:@"sign_img_circle_p"] forState:UIControlStateNormal];
                    }
                    break;
                case ResultKindTypeNoEnough:
                    {
                        [[UIColor clearColor] set];
                    }
                    break;
                case ResultKindTypeClear:
                    break;
                default:
                    break;
                }
            }        
        }
    } else {
        [path addLineToPoint:self.currentPoint];
        [[UIColor colorWithRed:94/255.0 green:195/255.0 blue:49/255.0 alpha:0.8] set];
    }
    path.lineWidth = 1;
    path.lineJoinStyle = kCGLineCapRound;
    path.lineCapStyle = kCGLineCapRound;
    [path stroke];
}
ViewController

这个比较容易理解:分别跳转两个界面:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.row == 0) {
        TeacViewController *vc = [[TeacViewController alloc] init];
        [self.navigationController pushViewController:vc animated:YES];
    } else {
        StudViewController *vc = [[StudViewController alloc] init];
        [self.navigationController pushViewController:vc animated:YES];
    }
}
TeacViewController
  • 主要的应用就是调用GestureLockViewDelegate了,接受并校验密码,
#pragma mark gestureLockView代理事件
- (void)gestureLockView:(GestureLockView *)lockView drawRectFinished:(NSMutableString *)gesturePassword {
    [self createGesturesPassword:gesturePassword];
}

//创建手势密码
- (void)createGesturesPassword:(NSMutableString *)gesturePassword {
    if (self.lastGesturePsw.length == 0) {
        if (gesturePassword.length < 4) {
            self.lastGesturePsw = nil;
            [self.gestureLockView checkTeacResult:TeacKindTypeNoEnough];
            self.statusLabel.text = @"至少连接4个点,重新输入";
            [self shakeAnimationForView:self.statusLabel];
            return;
        }
        self.lastGesturePsw = gesturePassword;
        [self.gestureLockView checkTeacResult:TeacKindTypeTrue];
        NSLog(@"---%@", self.lastGesturePsw);
        self.statusLabel.text = [NSString stringWithFormat:@"密码是%@", gesturePassword];
    }
}
  • 两个按钮的点击事件
#pragma mark 按钮点击事件
//重置按钮
- (void)resetBtnClick:(UIButton *)btn {
    self.lastGesturePsw = nil;
    [TeacViewController addGesturePassword:@""];
    [self.gestureLockView checkTeacResult:TeacKindTypeNoEnough];
    self.statusLabel.text = @"请绘制手势密码";
    NSLog(@"resetPwd == %@, resetUserDefaultsPwd == %@", self.lastGesturePsw, [TeacViewController gesturePassword]);
}
//确定按钮
- (void)sureBtnClick:(UIButton *)btn {
    if (!self.lastGesturePsw) {
        self.statusLabel.text = @"请绘制手势密码";
        return;
    }
    [TeacViewController addGesturePassword:self.lastGesturePsw];
    [self.gestureLockView checkTeacResult:TeacKindTypeTrue];
    self.statusLabel.text = @"密码设置成功";
    NSLog(@"resetPwd == %@, resetUserDefaultsPwd == %@", self.lastGesturePsw, [TeacViewController gesturePassword]);
//    [self.navigationController popViewControllerAnimated:YES];
}

  • 用本地存储进行模拟
#pragma mark 本地存储模拟
+ (void)deleteGestuesPassword {
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:GESPWD];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

+ (void)addGesturePassword:(NSString *)gesturePassword {
    [[NSUserDefaults standardUserDefaults] setObject:gesturePassword forKey:GESPWD];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

+ (NSString *)gesturePassword {
    return [[NSUserDefaults standardUserDefaults] objectForKey:GESPWD];
}
StudViewController
  • GestureLockDelegate代理方法 并校验对当前的手势密码和本地存储的教师端密码
#pragma mark 手势密码界面代理
- (void)gestureLockView:(GestureLockView *)lockView drawRectFinished:(NSMutableString *)gesturePassword {
    [self validateGesturePassword:gesturePassword];
}

//校验手势密码
- (void)validateGesturePassword:(NSMutableString *)gesturePassword {
    
    if (gesturePassword.length < 4) {
        self.statusLabel.text = @"至少连接4个点,重新输入";
        [self.gestureLockView checkPwdResult:ResultKindTypeNoEnough];
        [self shakeAnimationForView:self.statusLabel];
        return;
    }
    self.lastGesturePsw = gesturePassword;
    
    
    /*滑完直接校验*/
    NSLog(@"validPwd == %@, validUserDefaultsPwd == %@", self.lastGesturePsw, [StudViewController gesturePassword]);
    static NSInteger errorCount = 5;
    
    if ([self.lastGesturePsw isEqualToString:[StudViewController gesturePassword]]) {
        [self.gestureLockView checkPwdResult:ResultKindTypeTrue];
        self.statusLabel.text = @"密码校验成功";
        [self shakeAnimationForView:self.statusLabel];
    } else {
        [self.gestureLockView checkPwdResult:ResultKindTypeFalse];
        errorCount = errorCount - 1;
        if (errorCount == 0) {
            //已经输错5次
            self.statusLabel.text = @"请重新输入密码";
            errorCount = 5;
            return;
        }
        self.statusLabel.text = [NSString stringWithFormat:@"密码错误, 还可以再输入%ld次", errorCount];
        [self shakeAnimationForView:self.statusLabel];
    }
}

  • 清除按钮点击事件 改变self.gestureLockView中的resultType 进行界面的重绘等调整
- (void)clearBtnClick:(UIButton *)btn {
    self.statusLabel.text = @"清除!!!";
    [self.gestureLockView checkPwdResult:ResultKindTypeClear];
    [self shakeAnimationForView:self.statusLabel];
}
  • 本地存储:这里也有一个本地存储 和教师端对应 (其实可以单独封装出来的)
#pragma mark 本地存储模拟
+ (void)deleteGestuesPassword {
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:GESPWD];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

+ (void)addGesturePassword:(NSString *)gesturePassword {
    [[NSUserDefaults standardUserDefaults] setObject:gesturePassword forKey:GESPWD];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

+ (NSString *)gesturePassword {
    return [[NSUserDefaults standardUserDefaults] objectForKey:GESPWD];
}

以上,就是这个功能实现的大体流程了,捋顺了思路来看其实还是蛮明确的,当时做的时候也是走路挖坑填坑的,发现这样把自己的思路写下来还真是蛮有收获的,希望自己更好的进步,如果您看到这儿觉得有不合理的地方欢迎随时和我沟通哈。
源代码连接:https://github.com/irembeu/LGJGestureLockDemo.git

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

推荐阅读更多精彩内容