自定义转场动画非正式翻译

自定义转场动画

这张图是自己在翻译官方文档Customizing the Transition Animations的总结:

转场动画过程

接下来是漫长的翻译部分,英文水平有限,有些单词不知道怎么去表达(比如persentations,dismissals,chrome等等),各位看官多多指教,在下感激不尽。

自定义转场(过渡)动画

转场动画可提供应用程序界面变更的视觉反馈。UIKit提供了一套标准转场样式,以便在呈现视图控制器的时候使用,并且你可以使用自己的自定义转场来补充标准转场。

转场动画序列

转场动画将一个控制器的内容交换为另一个的内容。有两种类型的转场:presentationsdismissals。一个presentations转场会向应用程序的视图控制器层级添加一个新的视图控制器,而dismissal转场会从层级中移除一个或多个视图控制器。

实现转场动画需要许多对象。UIKit 提供转场中涉及的所有对象的默认版本,你可以自定义所有对象或者仅定义一个子集。如果你选择正确的对象集,你应该能够只用少量的代码创建你的动画。即使包含交互的动画,如果你利用UIKit提供的现有代码,也可以轻松地实现。

转场的代理

转场代理是转场动画和自定义presentations的起点。转场代理是一个你定义的并遵守UIViewControllerTransitioningDelegate协议的对象。它的工作是为UIKit提供以下对象:

  1. 动画对象。动画对象是负责创建用于显示或隐藏一个视图控制器的视图的动画。转场代理可以提供用于presentingdismissing视图控制器的单独的动画对象。动画对象遵守UIViewControllerAnimatedTransitioning协议。
  2. 交互式动画对象。交互式动画对象使用触摸事件或者手势识别器来驱动自定义动画的定时。交互式动画对象遵守UIViewControllerInteractiveTransitioning协议。创建交互式动画的最简单方法是将UIPercentDrivenInteractiveTransition子类化,并向你的子类添加事件处理代码。该类控制使用现有动画对象创建的动画的时间。如果你创建自己的交互式动画,你必须自己渲染动画的每个帧。
  3. 呈现的控制器(persentation controller)。在视图控制器在屏幕上时呈现控制器管理着呈现风格。系统提供了内置呈现样式的呈现控制器,你可以为你自己的呈现样式提供自定义呈现控制器。有关创建自定义的呈现控制器的更多信息,请看Creating Custom Presentations.

给视图控制器的transitioningDelegate赋值一个转场代理对象,告诉UIKit你想要执行自定义转场或呈现。你的代理可以选择它提供的哪些对象。如果你不提供动画对象,UIKit会在视图控制器的modalTransitionStyle属性中使用标准的转场动画。

图10-1显示转场代理和动画对象与被呈现视图控制器的关系。presentation controller仅在视图控制器的modalPresentationStyle的属性设置为UIModalPresentationCustom时使用。

图10-1

有关如何实现你的转场代理的更多信息,请看Implement the Transitioning Delegate.有关转场代理对象的方法的更多信息,请看UIViewControllerTransitioningDelegate Protocol Reference.

转场动画序列

当一个被呈现的视图控制器的transitioningDelegate属性包含一个有效的对象时,UIKit会使用你提供的自定义动画对象呈现那个视图控制器。在准备呈现时,UIKit会调用你的转场代理的animationControllerForPresentedController:presentingController:sourceController:方法来获取自定义动画对象。如果一个对象是有效的,UIKit会执行下面的步骤:

  • 如果一个交互式动画对象是有效的,UIKit会调用转场代理的interactionControllerForPresentation:方法。如果该方法返回nil,UIKit会执行没有用户交互的动画。
  • UIKit会调用动画对象的transitionDuration:方法来获取动画时长。
  • UIKit会调用恰当的方法来开始动画:
    • 对于非交互式动画,UIKit会调用动画对象的animateTransition:方法
    • 对于交互式动画,UIKit会调用交互式动画对象的startInteractiveTransition:方法。
  • UIKit等待一个动画对象调用上下文转场对象的completeTransition:方法。你的自定义动画对象在动画完成后调用这个方法,尤其是在动画的完成回调里。调用这个方法来结束转场,并且让UIKit知道它可以调用presentViewController:animated:completion的完成处理和调用动画对象自己的animationEnded:方法。

dismissing一个视图控制器时,UIKit调用你的转场代理的animationControllerForDismissedController:方法并执行下面的步骤:

  • 如果一个交互式动画对象时有效的,UIKit会调用转场代理的interactionControllerForDismissal:方法。如果方法返回nil,UIKit会执行没有用户交互的动画。
  • UIKit会调用动画对象的transitionDuration:方法来获取动画时长。
  • UIKit会调用恰当的方法来开始动画:
    • 对于非交互式动画,UIKit会调用动画对象的animateTransition:方法
    • 对于交互式动画,UIKit会调用交互式动画对象的startInteractiveTransition:方法。
  • UIKit等待一个动画对象调用上下文转场对象的completeTransition:方法。你的自定义动画对象会在他的动画完成后调用这个方法,尤其是在动画的完成回调里。调用这个方法来结束转场,并让UIKit知道它可以调用presentViewController:animated:completion:方法的完成处理回调和调用动画对象自己的animationEnded:方法。

注意:在你的动画结束时调用completeTransition:方法是必须的。UIKit不会结束转场过程,从而将控制权返回到你的应用程序,直到你调用了该方法。

转场上下文对象

在转场动画开始前,UIKit会创建一个转场上下文对象,并给它填充关于如何执行动画的信息。转场上下文兑现是你的代码中重要的一部分。它实现了UIViewControllerContextTransitioning协议,并储存着在转场中视图控制器和视图相关的引用。它也储存着一些信息,关于你应该如何执行转场,包括动画是否是交互的。你的动画对象需要所有的这些信息来设置和执行真实的动画。

注意:在设置自定义动画时,总是使用转场上下文对象中的对象和数据而不是使用你管理自己的缓存信息。转场在多种条件下会发生,有一些可能会改变动画的参数。转场上下文对象保证了你需要执行动画的正确信息,而你的缓存信息可能会在你调用动画的方法时失效。

图10-2显示转场上下文对象如何和其他对象交互。你的动画对象在它的animateTransition:方法中接受对象。你创建的动画应该在提供的容器视图中进行。例如,当正在呈现一个视图控制器时,添加它的视图作为这个容器视图的子视图。这个容器视图可能会是窗口或者一个有规律的视图,但是它总是被配置来运行你的动画。
图:

图10-2

有关转场上下文对象的更多信息,请看UIViewControllerContextTransitioning Protocol Reference.

转场协调器

对于内置的转场和你自定义的转场,UIKit会创建一个转场协调器对象来促进任何你可能需要执行的特别的动画。除了视图控制器的presentationdismissal外,转场会在界面发生旋转或视图控制器的frame改变时产生。所有这些转场代表着视图层级的改变。转场协调器是一种方式来跟踪这些变化,并同时使你的内容动画。访问转场协调器,从受影响的视图控制器的transitionCoordinator属性中获取该对象。转场协调器只在转场过程中存在。

图10-3显示转场协调器与有关presentation的视图控制器的关系。使用转场协调器来获取转场的信息,并注册要与转场动画同时执行的动画块。转场协调器对象遵守UIViewControllerTransitionCoordinatorContext协议,它提供定时信息,动画的当前状态信息,在转场中涉及的视图和视图控制器。当你的动画块被执行时,它一样会收到一个上下文对象带有相同的信息。

图10-3

有关转场协调器对象的更多信息,请看UIViewControllerTransitionCoordinator Protocol Reference。有关你可以用来配置你的动画的上下文信息,请看UIViewControllerTransitionCoordinatorContext Protocol Reference.

使用自定义的动画呈现一个视图控制器

使用自定义的动画来呈现一个视图控制器,需要做以下你的视图控制器的动作方法:

  • 创建一个你想要呈现的视图控制器
  • 创建你的自定义的转场代理对象,并把它赋值给视图控制器的transitioningDelegate属性。你的转场代理的方法应该当调用时创建和返回你的自定义动画对象。
  • 调用presentViewController:animated:completion:方法来呈现这个视图控制器。

当你调用presentViewController:animated:completion:方法时,UIkit会启动呈现过程。Presentations在下一个运行循环迭代期间开始并继续,直到你的自定义动画调用了completeTransition:方法。交互式转场允许你在转场正在进行时处理触摸事件,但非交互式转场在由动画对象指定的持续时间内运行。

实现转场代理

转场代理的作用是用来创建和返回你的自定义对象。表10-1显示了转场方法的实现可以如此简单。这个例子创建并返回了一个自定义动画对象。大多数实际工作都是由动画对象自己来处理。

//Listing 10-1Creating an animator object
- (id<UIViewControllerAnimatedTransitioning>)
    animationControllerForPresentedController:(UIViewController *)presented
                     presentingController:(UIViewController *)presenting
                         sourceController:(UIViewController *)source {
    MyAnimator* animator = [[MyAnimator alloc] init];
    return animator;
}

转场代理的其它方法可以像上面清单上的方法一样简单。你也可以结合自定义的逻辑,根据你的应用程序的当前状态返回不同的动画对象。有关转场代理的方法的更多信息,请看UIViewControllerTransitioningDelegate Protocol Reference.

实现你的动画对象

一个动画对象是任何遵守了UIViewControllerAnimatedTransitioning协议的对象。动画对象创建在固定时间段内执行的动画。动画对象的关键是它的animateTransition:方法,你用来创建真实的动画。动画的过程大致分为以下几个部分:

  • 获取动画参数
  • 使用CoreAnimationUIView的动画方法创建动画
  • 清理并完成转场
获取动画参数

传递给你的animateTransition:方法的上下文转场对象包含在执行你的动画时使用的数据。在你可以从上下文转场对象获取更多最新数据信息时,不要使用你自己的缓存信息或者从你的视图控制器请求到的信息。Presentingdismissing视图控制器有时候涉及到你的视图控制器之外的对象。例如,一个自定义的呈现控制器可能会添加一个背景视图作为呈现的一部分。上下文转场对象考虑了额外的视图和对象,并为您提供了动画的正确视图。

  • 调用两次viewControllerForKey:方法,以获取转场中涉及的”from”和”to”视图控制器。不要假设你知道哪些视图控制器参与了转场。UIKit可能会在适应新的特殊环境或响应你的应用程序请求时更换视图控制器。
  • 为了动画调用contrainerView方法来获取父视图。给这个视图添加关键的子视图。例如,在呈现期间,给这个视图添加被呈现的视图控制器的视图。
  • 调用viewForKey:方法来获取要添加或删除的视图。视图控制器的视图在转场过程中可能不是唯一的要被添加或被移除。presentation控制器可能插入视图到视图层级中,也必须被添加或移除。viewForKey:方法会返回包含所有你需要添加或移除的根视图。
  • 调用finalFrameForViewController:方法可以获取要添加或移除的视图的最终的frame矩形。

上下文转场对象使用”from”和”to”术语来标识视图控制器, 视图,和转场中涉及到的frame矩形。在转场开始的时候”from”视图控制器的视图总是在当前屏幕上,在转场结束的时候”to”视图控制器将会被看见。在图10-4中你可以看到,在presentationdismissal时“from”和”to"视图控制器交换了位置。

图10-4

交换值使它更容易写一个单个的动画来处理presentationsdismissals。当你设计你的动画时,你总是必须要做的是包含一个属性来知道它的动画是presentation还是dismissal。两者之间唯一需要的区别如下:

  • 对于presentation, 添加”to”视图到容器视图的层级。
  • 对于dismissal,从容器视图的层级移除”from”视图。
创建转场动画

在典型的presentation期间,属于被呈现的视图控制器的视图被动画化到位。其它的视图可能作为你的presentation的一部分动画,但是你的动画的主要目标总是被添加到视图层级中的视图。
当动画主要视图时,你配置你的动画的基本动作是相同的。你从转场上下文获取你需要的对象和数据,并使用这些信息来创建你的真实的动画。

  • Presentation动画:
    • 使用viewControllerForKey:viewForKey:方法来获取涉及到转场中的视图控制器和视图。
    • 设置”to”视图的开始位置。将任何其它属性设置为其初始值。
    • 从转场上下文的finalFrameForViewController:获取”to”视图的最终位置。
    • 把”to"视图作为子视图添加到容器视图中。
    • 创建动画
      • 在你的动画块中,将”to”视图动画到它在容器视图中的最终位置。将任何其它属性设置为其最终值。
      • 在完成块中,调用completeTransition:方法,并执行其它的清理。
  • Dismissal动画:
    • 使用viewControllerForKey:viewForKey:方法来获取涉及到转场中的视图控制器和视图。
    • 计算”from”视图的结束位置。属于被呈现的视图控制器的视图开始消失。
    • 将”to"视图作为子视图添加到容器视图中。
    • presentation期间,当转场完成时属于呈现的视图控制器的视图被移除。结果,你必须在dismissal操作期间将那个视图添加回容器中。
    • 创建动画
      • 在你的动画块中,将”from”视图动画到它在容器视图中的最终位置。将任何其它属性设置为其最终值。
      • 在完成块中,将”from”视图从你的视图层级中移除并调用completeTransition:方法。根据需要执行任何其它的清理。

图10-5显示了一个自定义的presentationdismissal转场,动画它的对角视图。在presentation期间,被呈现的视图空屏幕开始并向对角线向左上方动画直到它是可见的。在dismissal期间,视图反转方向,向右下方动画直到它再次离开屏幕。

图10-5

列表10-2显示你将如何实现图10-5上显示的转场。在获取到动画需要的对象后,animateTransition:方法计算被影响的视图的frame矩形。在presentation期间,被呈现的视图由“toView”表示。在dismissal期间,被消失的视图由”fromView”表示。presenting属性是动画对象自己的自定义属性,在创建动画对象的时候将转场代理设置为合适的值。

//Listing 10-2Animations for implementing a diagonal presentation and dismissal    
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// Get the set of relevant objects.
UIView *containerView = [transitionContext containerView];
UIViewController *fromVC = [transitionContext
        viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC   = [transitionContext
        viewControllerForKey:UITransitionContextToViewControllerKey];

UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];

// Set up some variables for the animation.
CGRect containerFrame = containerView.frame;
CGRect toViewStartFrame = [transitionContext initialFrameForViewController:toVC];
CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toVC];
CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromVC];

// Set up the animation parameters.
if (self.presenting) {
    // Modify the frame of the presented view so that it starts
    // offscreen at the lower-right corner of the container.
    toViewStartFrame.origin.x = containerFrame.size.width;
    toViewStartFrame.origin.y = containerFrame.size.height;
}
else {
    // Modify the frame of the dismissed view so it ends in
    // the lower-right corner of the container view.
    fromViewFinalFrame = CGRectMake(containerFrame.size.width,
                                  containerFrame.size.height,
                                  toView.frame.size.width,
                                  toView.frame.size.height);
}

// Always add the "to" view to the container.
// And it doesn't hurt to set its start frame.
[containerView addSubview:toView];
toView.frame = toViewStartFrame;

// Animate using the animator's own duration value.
[UIView animateWithDuration:[self transitionDuration:transitionContext]
                 animations:^{
                     if (self.presenting) {
                         // Move the presented view into position.
                         [toView setFrame:toViewFinalFrame];
                     }
                     else {
                         // Move the dismissed view offscreen.
                         [fromView setFrame:fromViewFinalFrame];
                     }
                 }
                 completion:^(BOOL finished){
                     BOOL success = ![transitionContext transitionWasCancelled];

                     // After a failed presentation or successful dismissal, remove the view.
                     if ((self.presenting && !success) || (!self.presenting && success)) {
                         [toView removeFromSuperview];
                     }

                     // Notify UIKit that the transition has finished
                     [transitionContext completeTransition:success];
                 }];
 }
动画结束后清理

在转场动画结束后,请务必调用completeTransition:方法。调用这个方法告诉UIKit转场完成了,用户可以开始使用被呈现的视图控制器。调用这个方法也会引发其它完成处理程序的级联,包括一个来自于presentViewController:animated:completion:方法和动画对象自己的animationEnded:方法。
调用completeTransition:方法最好的地方是在你的动画块的完成处理方法里。
因为转场能够被取消,你应该使用上下文对象的transitionWasCancelled方法的返回值来确定需要清理。当一个presentation被取消时,你的动画必须撤销对视图层级所做的任何修改。一个成功的dismissal需要相同的操作。

将交互式添加到你的转场

最容易的方法使你的动画交互是使用UIPercentDrivenInteractiveTransition对象。UIPercentDrivenInteractiveTransition对象与你现有的动画对象配合使用来控制其动画时间。它使用你提供的完成百分比值。你必须要做的是编写需要的事件处理的代码来计算完成百分比值并新的事件达到时更新它。
你可以使用带有子类或不带有子类的UIPercentDrivenInteractiveTransition类。如果你子类化了,使用你的子类的init方法(或startInteractiveTransition:方法)来执行一次创建你的事件处理代码。之后,使用你的自定义的事件处理代码来计算新的完成百分比值并调用updateInteractiveTransition:方法。当你的代码确定转场应该完成了,调用finishInteractiveTransition方法。
列表10-3显示UIPrecentDrivenInteractiveTransition子类的startInteractiveTransition:方法的自定义实现。这个方法设置一个平移手势识别器来跟踪触摸事件并将该手势识别器安装在动画的容器视图上。它还保存转场上下文的引用以供以后使用。

Listing 10-3Configuring a percent-driven interactive animator
- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
   // Always call super first.
   [super startInteractiveTransition:transitionContext];

   // Save the transition context for future reference.
   self.contextData = transitionContext;

   // Create a pan gesture recognizer to monitor events.
   self.panGesture = [[UIPanGestureRecognizer alloc]
                    initWithTarget:self action:@selector(handleSwipeUpdate:)];
   self.panGesture.maximumNumberOfTouches = 1;

   // Add the gesture recognizer to the container view.
   UIView* container = [transitionContext containerView];
   [container addGestureRecognizer:self.panGesture];
}

在任何新的事件达到时手势识别器会调用它的动作方法。你的动作方法的实现可以使用手势识别器的状态信息来确定手势是否成功,失败或一直在进行。同时,你可以使用最新的触摸事件信息来计算手势的新的百分比值。
列表10-4显示由列表10-3中配置的平移手势识别器调用的方法。当新的事件到达时,此方法使用垂直行程距离来计算动画的完成百分比值。当手势结束时,该方法结束转场。

// Listing 10-4Using events to update the animation progress
-(void)handleSwipeUpdate:(UIGestureRecognizer )gestureRecognizer {
UIView
container = [self.contextData containerView];

  if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
      // Reset the translation value at the beginning of the gesture.
      [self.panGesture setTranslation:CGPointMake(0, 0) inView:container];
  }
  else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) {
      // Get the current translation value.
      CGPoint translation = [self.panGesture translationInView:container];

      // Compute how far the gesture has travelled vertically,
      //  relative to the height of the container view.
      CGFloat percentage = fabs(translation.y / CGRectGetHeight(container.bounds));

      // Use the translation value to update the interactive animator.
      [self updateInteractiveTransition:percentage];
  }
  else if (gestureRecognizer.state >= UIGestureRecognizerStateEnded) {
      // Finish the transition and remove the gesture recognizer.
      [self finishInteractiveTransition];
      [[self.contextData containerView] removeGestureRecognizer:self.panGesture];
  }
}

注意:你计算的值表示动画的整个长度的完成百分比。对于交互式动画,你可能想要避免效应,例如动画本身的初始速度、阻尼值和非线性完成曲线。这种效应倾向于将事件的触摸位置与任何下层视图的移动分离。

创建与转场一起运行的动画

涉及转场的视图控制器可以在任何presentation或转场动画之上执行附加动画。例如,被呈现的视图控制器可能在转场期间动画它自己的视图层级,并在转场出现时添加移动效果或其它视觉反馈。任何对象能够创建动画,只要它能够访问被呈现或呈现的视图控制器的transitionCoordinator属性。转场协调器属性只在转场正在进行时存在。
为了创建动画,调用转场协调器的animateAlongsideTransition:completion:animateAlongsideTransitionInView:animation:completion:方法。这个你提供的块被存储直到转场动画开始,此时它们与其余的动画一起执行。

使用Presentation控制器与你的动画

对于自定义presentation,你可以提供你自己的presentation控制器来给被呈现的视图控制器一个自定义的外观。Presentation控制器管理着视图控制器和其内容分离的任何自定义的chrome.例如,位于视图控制器视图的后面的调光视图将由presentation控制器管理。事实上它没有管理一个特殊的视图控制器的视图意味着你可以在你的应用程序中使用相同的presentation控制器与任何视图控制器。

你提供一个自定义的presentation控制器来自于被呈现视图控制器的转场代理。(视图控制器的modalTransitionStyle属性必须设置为UIModalPresentationCustom) presentation控制器与任何动画对象并行操作。由于动画对象将视图控制器的视图动画到位置,presentation控制器会将任何附加视图动画到位置。转场结束时,presentation控制器有机会对视图层级执行任何最终调整。
关于如何创建一个自定义的presentation控制器的更多信息,请看Creating Custom Presentations

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

推荐阅读更多精彩内容