FDFullscreenPopGesture全屏手势返回源码结构及解析

内容均为原创, 如有任何疑问或者错误,请在文章下留言或者直接与我联系,一定及时回复: )
· 前言
· 框架背景
· 框架知识准备
· 源码解析
· 框架工作流程图

· 前言

由于6Plus的出现,iphone的默认导航栏又是在屏幕顶部,对于app的返回操作大屏手机对于小手的用户来说操作显得不那么友好。iOS7为了提升app的返回体验,增加了边缘侧滑返回手势,但是对于小手用户来说,返回体验没有彻底得到改善。于是开发者们开始绞尽脑汁地想各种办法,其中一种办法,也就是今天要讲的---将返回手势变为全屏的框架。
github千星框架 FDFullscreenPopGesture
这个框架能get到4K+的星星,肯定是有过人之处的。抱着学习的态度,去看了下源码,对于runtime以及封装代码的思路,都有很大的帮助。前言就到这里,下面我会尽量清晰的表达我看源码的思路,尽量把一些知识点说的通俗易懂。


· 框架背景

这个扩展来自 @J_雨 同学的这个很天才的思路,他的文章地址:戳这里

如果不太愿意戳进去看,我这里来总结下这位同学的思路:

1.如果自定义手势的话,还要考虑控制器切换的动画,成本太高太麻烦。
2.iOS7.0之后苹果提供了边缘滑动手势,获取到这个边缘滑动手势,然后把它的触发范围从边缘改成全屏不就好了?然后经过一番折腾,发现没有办法修改,这个方法是不可行的。
3.那么既然不能将它的手势触发的方法找到,我们自己去创建一个全屏手势,去调用那个方法,不就好了?bingo,可行。
4.有人对这个思路进行了封装于是有了FDFullscreenPopGesture


· 框架知识准备

· 为分类添加属性

objc_setAssociatedObject objc_getAssociatedObject

传送门:Runtime奇技淫巧之objc_setAssociatedObject,objc_getAssociatedObject

· 在 main 函数调用之前被 ObjC 运行时调用的方法,框架只需要放入项目文件夹即可实现框架功能,都是这个钩子方法的功劳。

+(void)load
传送门:Objective-C Method Swizzling 的最佳实践

· NavigationController可以通过调用setViewController方法将画面的跳转历史路径(堆栈)完全替换

传送门:页面的跳转技巧--setViewControllers


· 框架源码解析

打开项目我们能看到,该框架只有一个.h 和.m文件。
.h中只暴露了UINavigationController 和 UIViewController的两个分类属性。
.m中包括四部分

@implementation _FDFullscreenPopGestureRecognizerDelegate : NSObject (私有)
@implementation UIViewController (FDFullscreenPopGesturePrivate)(私有)
@implementation UINavigationController (FDFullscreenPopGesture)
@implementation UIViewController (FDFullscreenPopGesture)
· 下面为大家一一讲解下这四个implementation都干了一些什么事

1.FDFullscreenPopGestureRecognizerDelegate:定义了一个类遵循了手势代理协议,并且有一点navigationController的属性。自定义的手势是否被触发由这个类来控制。

@interface _FDFullscreenPopGestureRecognizerDelegate : NSObject <UIGestureRecognizerDelegate>
@property (nonatomic, weak) UINavigationController *navigationController;
@end
// 这个类实现了自定义手势的代理方法 
@implementation _FDFullscreenPopGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
    // 当没有控制器入栈的时候,不触发手势
    if (self.navigationController.viewControllers.count <= 1) {
        return NO;
    }
    
    // 如果控制器的fd_interactivePopDisabled属性为NO不触发手势
    //(fd_interactivePopDisabled是作者对UIViewController添加的一个属性,下面会讲)
    UIViewController *topViewController = self.navigationController.viewControllers.lastObject;
    if (topViewController.fd_interactivePopDisabled) {
        return NO;
    }
    
   //当手势开始的位置 超出了fd_interactivePopMaxAllowedInitialDistanceToLeftEdge所设定的值,那么就不触发手势
    CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];
    CGFloat maxAllowedInitialDistance = topViewController.fd_interactivePopMaxAllowedInitialDistanceToLeftEdge;
    if (maxAllowedInitialDistance > 0 && beginningLocation.x > maxAllowedInitialDistance) {
        return NO;
    }

    // 如果导航控制器正在执行转场动画,则不触发手势
    if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
        return NO;
    }
    
    // 1.这个比较神奇,当app语言设置为阿拉伯语等阅读顺序从右到左的语言,且app的布局适配了这个语种,
    // 2.那么导航控制器的入栈动画会由从右到左,调整为从左到右,从作者的代码上来看手势好像是不支持从左到右的app布局的。
    // 3.也就是说,当app语言设置为阿拉伯等语言并且app适配了这种布局,不触发手势
    CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
    BOOL isLeftToRight = [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight;

    CGFloat multiplier = isLeftToRight ? 1 : - 1;
    if ((translation.x * multiplier) <= 0) {
        return NO;
    }
    
    return YES;
}
@end
  1. @interface UIViewController (FDFullscreenPopGesturePrivate) 定义了一个block,在ViewControllerWillAppear的时候会被注入
    @implementation UIViewController (FDFullscreenPopGesturePrivate)在main函数之前会将系统的viewWillAppear方法和viewWillDisappear方法替换成分类中的方法。
typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);

@interface UIViewController (FDFullscreenPopGesturePrivate)

@property (nonatomic, copy) _FDViewControllerWillAppearInjectBlock fd_willAppearInjectBlock;

@end

const NSString *block = @"block";

@implementation UIViewController (FDFullscreenPopGesturePrivate)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
       // 获取系统的viewWillAppear:方法
        Method viewWillAppear_originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));
       // 获取自定义的fd_viewWillAppear:方法 
        Method viewWillAppear_swizzledMethod = class_getInstanceMethod(self, @selector(fd_viewWillAppear:));
       // 两者交换
        method_exchangeImplementations(viewWillAppear_originalMethod, viewWillAppear_swizzledMethod);
    
       // 获取系统的viewWillDisappear:方法 
        Method viewWillDisappear_originalMethod = class_getInstanceMethod(self, @selector(viewWillDisappear:));
       // 获取自定义的viewWillDisappear:方法 
        Method viewWillDisappear_swizzledMethod = class_getInstanceMethod(self, @selector(fd_viewWillDisappear:));
       // 两者交换method_exchangeImplementations(viewWillDisappear_originalMethod, viewWillDisappear_swizzledMethod);
    });
}

- (void)fd_viewWillAppear:(BOOL)animated
{
    // 1.为了不破坏原本的业务逻辑,先执行原来的viewWillAppear方法
    // 2.为什么在fd_viewWillAppear:方法中调用fd_viewWillAppear:方法不会引起死循环?
    // 3.因为fd_viewWillAppear:这个方法已经和viewWillAppear:方法做了交换
    // 4.所以调用 fd_viewWillAppear:方法相当于调用了viewWillAppear:方法,
    // 5.调用viewWillAppear:方法相当于调用了fd_viewWillAppear:方法
    [self fd_viewWillAppear:animated];
    
    // 执行注入的block 这个block到底干了什么事情,会在后面讲到
    if (self.fd_willAppearInjectBlock) {
        self.fd_willAppearInjectBlock(self, animated);
    }
}

- (void)fd_viewWillDisappear:(BOOL)animated
{
    // 同理,这里不再赘述。
    [self fd_viewWillDisappear:animated];
    // 1.当用户有pop或者push操作的时候,
    // 2.根据导航栏栈顶控制的fd_prefersNavigationBarHidden这个分类属性,
    // 3.控制导航栏是否需要隐藏
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        UIViewController *viewController = self.navigationController.viewControllers.lastObject;
        if (viewController && !viewController.fd_prefersNavigationBarHidden) {
            [self.navigationController setNavigationBarHidden:NO animated:NO];
        }
    });
}

- (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock
{
  // 1. 调用fd_willAppearInjectBlock属性的get方法的时候
  // 2. 会在本类中以该get方法的名称为key,找到对应的value,也就是该block的值
  // 3. 更多关于runtime为分类添加属性的知识,请看框架知识储备
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block
{
   // 1. 当调用了fd_willAppearInjectBlock这个分类属性的set方法时候,
   // 2. 会以block为value 以该属性的get方法为key将block存储起来
   // 3. 以后就可以通过调用fd_willAppearInjectBlock属性的get方法,获取block
    objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end

3.全屏手势的核心返回功能在此实现,交换push方法后,将系统返回手势替换为自定义手势,设置代理,如果允许用户根据控制器的分类属性控制导航栏显示或者隐藏,则给入栈的控制器的block赋值。

@implementation UINavigationController (FDFullscreenPopGesture)
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        // 同上,为了注入代码,替换导航控制器的push方法。
        SEL originalSelector = @selector(pushViewController:animated:);
        SEL swizzledSelector = @selector(fd_pushViewController:animated:);
         
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        // 1.这里要注意,class_addMethod是为了检查本类是否实现了这个方法
        // 2.如果方法添加成功,代表本类没有实现该方法(该方法在父类中实现,却没有在子类中实现)
        // 3.打个比方,如果我创建了一个testNavgationController继承自UINavigationController
        // 4.但是,我自定义的testNavgationController只进行了修改navBar背景色的方法
        // 5.自定义的testNavgationController并没有重写 pushViewController:animated: 
        // 6.这个时候,如果我直接调用方法交换的话,会和父类中的pushViewController:animated:交换
        // 7.显然,这不是我们想要的结果,我们只希望和testNavigation的pushViewController:animated:方法交换
        // 8.所以先调用class_addMethod方法,检查本类是否实现了pushViewController:animated: 
        // 9.如果实现了,那很好,直接交换
        // 10.如果没实现,那么class_addMethod已经把push方法 (对应的实现是fd_push)添加到了本类
        // 11.我们只需要再调用class_replaceMethod方法添加fd_push(对应的实现是push) 添加到本类
        // 12.这样,就达到了方法交换的目的
        // 13.pushViewController:animated: 的内部实现为fd_pushViewController:animated: 
        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            // 1. class_replaceMethod方法会检查是否存在swizzledSelector这个方法名
            // 2.如果存在,那么将原来实现替换为 originalMethod
            // 3.如果不存在 则会先 添加这个方法名swizzledSelector,
            // 4.然后再添加这个方法的实现originalMethod
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
            NSLog(@"%s",method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.fd_fullscreenPopGestureRecognizer]) {
        //打印self.interactivePopGestureRecognizer.view我们会发现它的类型是UILayoutContainerView
        // (UILayoutContainerView就是window 上的第一个 subview)
        //判断自定义手势是否已经加在了UILayoutContainerView上
        [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.fd_fullscreenPopGestureRecognizer];
        // 使用自定义手势替换系统边缘返回的手势,
        // 原理已经在本文的“框架背景”章节阐述了,这里就不多啰嗦了
        NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
        id internalTarget = [internalTargets.firstObject valueForKey:@"target"];
        SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
        self.fd_fullscreenPopGestureRecognizer.delegate = self.fd_popGestureRecognizerDelegate;
        [self.fd_fullscreenPopGestureRecognizer addTarget:internalTarget action:internalAction];
        // 关闭导航控制器自带的边缘返回手势(因为它已经被自定义手势取而代之了)
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    
    // 这个方法控制了导航控制器中的子控制器是否有独立控制导航栏显示或者隐藏的权利(下面会讲)
    // fd_viewControllerBasedNavigationBarAppearanceEnabled属性默认为YES
    // 也就是说,默认会根据控制的分类属性fd_prefersNavigationBarHidden来控制栏的隐藏或者显示
    // 如果fd_viewControllerBasedNavigationBarAppearanceEnabled为NO
    // 那么导航控制器的导航栏的显示与否,控制器无权决定
    [self fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
    if (![self.viewControllers containsObject:viewController]) {
        // 调用push方法,将控制器入栈
        [self fd_pushViewController:viewController animated:animated];
    }
}

- (void)fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController
{
   // 上面已经说了,fd_viewControllerBasedNavigationBarAppearanceEnabled为NO,则直接return
    if (!self.fd_viewControllerBasedNavigationBarAppearanceEnabled) {
        return;
    }
    // 前面在2.中我们提到过
    // UIViewController (FDFullscreenPopGesturePrivate) 定义了一个block
    // 从这里我们可以看到,只有在 fd_viewControllerBasedNavigationBarAppearanceEnabled == YES的时候
    // 才会给block赋值,才会执行block,
    // block中会根据fd_prefersNavigationBarHidden 判断是否要显示或者隐藏导航栏
    __weak typeof(self) weakSelf = self;
    _FDViewControllerWillAppearInjectBlock block = ^(UIViewController *viewController, BOOL animated) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            [strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:animated];
        }
    };

    // 1.对即将入栈的控制器的fd_willAppearInjectBlock属性进行赋值
    // 2.在push前,也对栈顶的控制器fd_willAppearInjectBlock赋值
    // 3.请注意,这个时候栈顶的控制器不一定是push入栈的,也有可能是通过-setViewControllers:方法入栈
    // 4.具体请看我的“框架知识储备",了解NavigationController的setViewControllers方法
    appearingViewController.fd_willAppearInjectBlock = block;
    UIViewController *disappearingViewController = self.viewControllers.lastObject;
    if (disappearingViewController && !disappearingViewController.fd_willAppearInjectBlock) {
        // 在有新的控制器入栈前,检查栈顶控制器block属性是否有值,如果没有,就赋值
        disappearingViewController.fd_willAppearInjectBlock = block;
    }
}

- (_FDFullscreenPopGestureRecognizerDelegate *)fd_popGestureRecognizerDelegate
{
   // 1.这是我们在1.中第一个提到的类,自定义的pan手势代理,在这个类实现
   // 2.由于该类在判断手势是否满足触发条件时,需要根据导航控制器的情况来做判断
   // 3.所以将导航控制器交给该类引用(记得用weak,不然会循环引用)
    _FDFullscreenPopGestureRecognizerDelegate *delegate = objc_getAssociatedObject(self, _cmd);
    if (!delegate) {
        delegate = [[_FDFullscreenPopGestureRecognizerDelegate alloc] init];
        delegate.navigationController = self;
        objc_setAssociatedObject(self, _cmd, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return delegate;
}

- (UIPanGestureRecognizer *)fd_fullscreenPopGestureRecognizer
{
    // "懒加载"自定义手势
    // 先获取该手势,如果获取不到,再创建,获取到了 直接返回
    UIPanGestureRecognizer *panGestureRecognizer = objc_getAssociatedObject(self, _cmd);
    if (!panGestureRecognizer) {
        panGestureRecognizer = [[UIPanGestureRecognizer alloc] init];
        panGestureRecognizer.maximumNumberOfTouches = 1;
        objc_setAssociatedObject(self, _cmd, panGestureRecognizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return panGestureRecognizer;
}

- (BOOL)fd_viewControllerBasedNavigationBarAppearanceEnabled
{
   // 获取NSNumber对象,注意了,如果NSnumber的value为0的时候,
   // if条件也会判断为真,因为NSnumber是对象,对象空的时候为nil而不是0
    NSNumber *number = objc_getAssociatedObject(self, _cmd);
    if (number) {
       // 如果number为0,那么boolValue得到的结果就为NO,反之YES
        return number.boolValue;
    }
    // 代码如果执行到这,说明没设置该属性,默认为YES
    self.fd_viewControllerBasedNavigationBarAppearanceEnabled = YES;
    return YES;
}

- (void)setFd_viewControllerBasedNavigationBarAppearanceEnabled:(BOOL)enabled
{
   // 注意,这里@(enable)是将bool值包装成一个NSNumber类型的对象
    SEL key = @selector(fd_viewControllerBasedNavigationBarAppearanceEnabled);
    objc_setAssociatedObject(self, key, @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

4.这里添加了框架的功能属性:手势的触发位置、该控制器是否支持手势,导航栏是否隐藏

@implementation UIViewController (FDFullscreenPopGesture)

- (BOOL)fd_interactivePopDisabled
{
   // 滑动手势的触发条件,该属性设置为NO,这个控制器将不会触发手势
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}

- (void)setFd_interactivePopDisabled:(BOOL)disabled
{
    objc_setAssociatedObject(self, @selector(fd_interactivePopDisabled), @(disabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)fd_prefersNavigationBarHidden
{
    // 控制器在viewWillAppear: 及 viewWillDisappear:都会根据该属性决定是否需要隐藏导航栏
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}

- (void)setFd_prefersNavigationBarHidden:(BOOL)hidden
{
    objc_setAssociatedObject(self, @selector(fd_prefersNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CGFloat)fd_interactivePopMaxAllowedInitialDistanceToLeftEdge
{
// 手势起始点的最大距离,超过该距离,不触发手势
// 64位系统下,CGFLOAT是double类型,32位系统下是float类型
#if CGFLOAT_IS_DOUBLE
    return [objc_getAssociatedObject(self, _cmd) doubleValue];
#else
    return [objc_getAssociatedObject(self, _cmd) floatValue];
#endif
}

- (void)setFd_interactivePopMaxAllowedInitialDistanceToLeftEdge:(CGFloat)distance
{
// 起点距离,过滤负数
    SEL key = @selector(fd_interactivePopMaxAllowedInitialDistanceToLeftEdge);
    objc_setAssociatedObject(self, key, @(MAX(0, distance)), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

· FDFullscreenPopGesture工作流程图

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • 邻居杨君,姑苏人士,儒雅谦恭又年轻有为,听说是单位里领导重点培养的青年骨干,家有贤妻,和美幸福得令人妒忌,因性情相...
    山阴路阅读 262评论 0 1
  • 昨晚,同事突然问我:你说到底嫁一个怎样的人才靠谱?我随即便问:你又犹豫了。她轻轻的点头,嘴角微翘:“嗯”。 我说,...
    爱读分享阅读 458评论 0 0
  • 可以随时记录想法吗
    华萌玫瑰王斌松阅读 110评论 0 0
  • 公司小王离职了,办完离职的所有手续,和办公室的人握手说再见,抱着自己的个人物品走出了办公室,如同窗外飘落的秋叶,形...
    行者静思录阅读 603评论 0 0