iOS 自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)

引言

簡(jiǎn)單的模態(tài)化presentdismiss 的自定義轉(zhuǎn)場(chǎng)。

一荣堰、準(zhǔn)備工作

  1. 準(zhǔn)備一個(gè)繼承自NSObject并且遵守 UIViewControllerTransitioningDelegateUIViewControllerAnimatedTransitioning協(xié)議的一個(gè)類 - YSTrafficPopUpsAnimator褐桌。
  2. 準(zhǔn)備一個(gè)繼承自 UIViewController 的類 - YSTrafficPopUpsViewController,聲明并且創(chuàng)建 YSTrafficPopUpsAnimator 的一個(gè)屬性 - animator
  3. 重寫(xiě)或者自定義初始化方法览露,將YSTrafficPopUpsViewControllermodalPresentationStyle 屬性設(shè)置為.custom, 將 animator 賦值給 YSTrafficPopUpsViewControllertransitioningDelegate屬性所灸。

二丽惶、協(xié)議的介紹

  • 1. UIViewControllerTransitioningDelegate – 描述 ViewController 轉(zhuǎn)場(chǎng)的
// 返回一個(gè)處理 present 動(dòng)畫(huà)過(guò)渡的對(duì)象
optional func animationController(forPresented presented: UIViewController, 
                                              presenting: UIViewController, 
                                                     source: UIViewController) -> UIViewControllerAnimatedTransitioning?

// 返回一個(gè)處理 dismiss動(dòng)畫(huà)過(guò)渡的對(duì)象
optional func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?

// 返回一個(gè)處理 present 手勢(shì)過(guò)渡的對(duì)象
optional func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

// 返回一個(gè)處理 dismiss手勢(shì)過(guò)渡的對(duì)像
optional func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

// 使用UIModalPresentationStyle.custom呈現(xiàn)樣式呈現(xiàn)視圖控制器時(shí),
// 系統(tǒng)將調(diào)用此方法爬立,并要求管理您的自定義樣式的呈現(xiàn)控制器钾唬。 
// 如果實(shí)現(xiàn)此方法,請(qǐng)使用它來(lái)創(chuàng)建并返回要用于管理演示過(guò)程的自定義演示控制器對(duì)象懦尝。
// 如果您未實(shí)現(xiàn)此方法知纷,或者此方法的實(shí)現(xiàn)返回nil,則系統(tǒng)將使用默認(rèn)的表示控制器對(duì)象陵霉。
 // 默認(rèn)的表示控制器不會(huì)向視圖層次結(jié)構(gòu)添加任何視圖或內(nèi)容琅轧。
optional func presentationController(forPresented presented: UIViewController,
                                                 presenting: UIViewController?, 
                                                     source: UIViewController) -> UIPresentationController?

2. UIViewControllerAnimatedTransitioning – 定義動(dòng)畫(huà)內(nèi)容的

// 返回動(dòng)畫(huà)過(guò)渡的時(shí)間
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval

// 所有的過(guò)渡動(dòng)畫(huà)事務(wù)都在這個(gè)方法里面完成
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)

// 當(dāng)您想使用可中斷的動(dòng)畫(huà)器對(duì)象(例如UIViewPropertyAnimator對(duì)象)執(zhí)行轉(zhuǎn)換時(shí),請(qǐng)實(shí)現(xiàn)此方法踊挠。 您必須在過(guò)渡期間返回相同的動(dòng)畫(huà)師對(duì)象乍桂。
optional func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating

//  過(guò)渡完成后冲杀,使用此方法執(zhí)行過(guò)渡動(dòng)畫(huà)師所需的任何最終清理操作。
optional func animationEnded(_ transitionCompleted: Bool)

3.UIViewControllerContextTransitioning – 表示動(dòng)畫(huà)上下文的

// 動(dòng)畫(huà)過(guò)渡應(yīng)在其中進(jìn)行的視圖睹酌。所有需要執(zhí)行動(dòng)畫(huà)的視圖必須在這個(gè)視圖里進(jìn)行权谁。
var containerView: UIView { get }

// 獲取轉(zhuǎn)場(chǎng)前后視圖控制器的方法
func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController?

// 獲取轉(zhuǎn)場(chǎng)前后視圖的方法
func view(forKey key: UITransitionContextViewKey) -> UIView?

// 通知系統(tǒng)過(guò)渡動(dòng)畫(huà)已完成。在動(dòng)畫(huà)之行完成之后憋沿,一定要調(diào)用此方法旺芽。
func completeTransition(_ didComplete: Bool)

注:這些是協(xié)議常用的屬性和方法,其他的暫不研究辐啄。

三采章、ViewController 和Animator 的關(guān)系以及上述三個(gè)協(xié)議之間的關(guān)系流程

1. ViewController不處理任何有關(guān)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的操作,跟平常使用的UIViewController 一樣壶辜。Animator 負(fù)責(zé)presentdismiss 時(shí)的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)悯舟。Animator對(duì)象是 賦值給viewControllertransitioningDelegate的屬性,用于自定義轉(zhuǎn)場(chǎng)砸民。
2. 先執(zhí)行UIViewControllerTransitioningDelegate 中的方法抵怎,再執(zhí)行 UIViewControllerAnimatedTransitioning中的方法,因此岭参,在animateTransition方法中可以知道什么時(shí)候是present反惕,什么時(shí)候是dismiss
3. UIViewControllerContextTransitioninganimateTransition 方法的參數(shù)冗荸,通過(guò)它承璃,可以獲取containerViewtoView蚌本,fromView盔粹, toViewControllerfromViewController等一系列需要的控件和方法程癌。
4. 在animateTransition 方法中就可以自定義 presentdismiss的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)舷嗡。

四、代碼的實(shí)現(xiàn)

需求:查看一組圖像嵌莉,可以左劃右劃查看进萄,可以點(diǎn)擊按鈕可以跳到下一張或者上一張。
1.控制器代碼:

import UIKit

private let imageCellId = "imageCellId"

class YSTrafficPopUpsViewController: UIViewController {
    // MARK: - 屬性
    private var maskView = UIView()
    private var collectionView: UICollectionView?
    private var pageControl: UIPageControl?
    private var nextButton: UIButton?
    private var previousButton: UIButton?
    
    private var imageURLStrings: [String]?
    private var animator: YSTrafficPopUpsAnimator?
    
    // MARK: - 初始化
    init(imageURLStrings: [String]?) {
        super.init(nibName: nil, bundle: nil)
        
        self.imageURLStrings = imageURLStrings
        animator = YSTrafficPopUpsAnimator()
        
        modalPresentationStyle = .custom
        transitioningDelegate = animator
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        configuraAnimator()
    }
    // 注意:我在這個(gè)地方配置了 animator對(duì)象需要的東西锐峭。
    func configuraAnimator() {
        animator?.maskView = maskView
        animator?.nextButton = nextButton
        animator?.previousButton = previousButton
        animator?.pageControl = pageControl
        animator?.collectionView = collectionView
    }
}

// MARK: - 監(jiān)聽(tīng)方法
@objc private extension YSTrafficPopUpsViewController {
    
    func closeViewController() {
        dismiss(animated: true, completion: nil)
    }
    
    func nextClick() {
        let currentPage = pageControl?.currentPage ?? 0
        let index = currentPage + 1
        
        if index >= imageURLStrings?.count ?? 0 {
            return
        }
        
        collectionView?.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
    }
    
    func previousClick() {
        let currentPage = pageControl?.currentPage ?? 0
        let index = currentPage - 1
        
        if index < 0 {
            return
        }
        
        collectionView?.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true)
    }
}

// MARK: - 設(shè)置 UI 界面
private extension YSTrafficPopUpsViewController {
    func setupUI() {
        setupMaskView()
        setupCollectionView()
        setupPageControl()
        setupButtons()
    }
    
    func setupMaskView() {
        let tapGes = UITapGestureRecognizer(target: self, action: #selector(closeViewController))
        maskView.addGestureRecognizer(tapGes)
        
        view.addSubview(maskView)
        maskView.frame = view.bounds
    }
    
    func setupButtons() {
        if imageURLStrings?.count ?? 0 <= 1 {
            return
        }
        
        nextButton = UIButton(target: self,
                              action: #selector(nextClick),
                              backImageName: "traffic_next_icon",
                              backSelectedImageName: "traffic_next_icon")
        previousButton = UIButton(target: self,
                                  action: #selector(previousClick),
                                  backImageName: "traffic_previous_icon",
                                  backSelectedImageName: "traffic_previous_icon")
        
        view.addSubview(nextButton!)
        view.addSubview(previousButton!)
        
        nextButton?.snp.makeConstraints({ (make) in
            make.centerY.equalTo(collectionView!)
            make.right.equalTo(collectionView!).offset(180)
        })
        
        previousButton?.snp.makeConstraints({ (make) in
            make.centerY.equalTo(collectionView!)
            make.left.equalTo(collectionView!).offset(-180)
        })
        
    }
    
    func setupPageControl() {
        if imageURLStrings?.count ?? 0 <= 1 {
            return
        }
        
        pageControl = UIPageControl()
        pageControl?.numberOfPages = imageURLStrings?.count ?? 0
        pageControl?.currentPage = 0
        pageControl?.currentPageIndicatorTintColor = UIColor(r: 238, g: 216, b: 171)
        pageControl?.pageIndicatorTintColor = .gray
        
        view.addSubview(pageControl!)
        pageControl?.snp.makeConstraints({ (make) in
            make.centerX.equalTo(view.snp.centerX)
            make.top.equalTo(collectionView!.snp.bottom).offset(-30)
        })
    }
    
    func setupCollectionView() {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView?.showsVerticalScrollIndicator = false
        collectionView?.showsHorizontalScrollIndicator = false
        collectionView?.isPagingEnabled = true
        collectionView?.scrollsToTop = false
        collectionView?.bounces = false
        collectionView?.delegate = self
        collectionView?.dataSource = self
        collectionView?.backgroundColor = .clear
        collectionView?.layer.cornerRadius = 15.5
        collectionView?.layer.masksToBounds = true
        collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: imageCellId)
        
        var imageSize: CGSize = .zero
        
        if imageURLStrings?.count != 0 {
            if let tempImage = UIImage(contentsOfFile: imageURLStrings![0]) {
                imageSize = tempImage.size
            }
        }
        
        var targerSize = CGSize.zero
        
        if imageSize != .zero {
            let ratio = imageSize.width / imageSize.height
            targerSize.height = view.bounds.height - kTabBarHeight * 2 - kTabBarHeight / 2
            targerSize.width = ratio * targerSize.height
        }
        
        view.addSubview(collectionView!)
        collectionView?.snp.makeConstraints({ (make) in
            make.centerX.equalTo(view)
            make.centerY.equalTo(view).offset((view.bounds.height + targerSize.height) / 2)
            make.size.equalTo(targerSize)
        })
    }
}

// MARK: - UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout
extension YSTrafficPopUpsViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        imageURLStrings?.count ?? 0
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: imageCellId, for: indexPath)
        
        var imageView = cell.contentView.subviews.last as? UIImageView
        
        if imageView == nil {
            imageView = UIImageView()
            cell.contentView.addSubview(imageView!)
            
            imageView?.snp.makeConstraints({ (make) in
                make.edges.equalTo(cell.contentView)
            })
        }
        
        imageView?.sd_setImage(with: URL(fileURLWithPath: imageURLStrings![indexPath.item]), placeholderImage: UIImage(named: "traffic_placeholder_icon"))
        
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        collectionView.bounds.size
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        0
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        0
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let width = scrollView.bounds.width
        let offsetX = scrollView.contentOffset.x
        
        let index = (offsetX + (width * 0.5)) / width
        pageControl?.currentPage = Int(index)
    }
}

2.Animator的代碼

import UIKit

class YSTrafficPopUpsAnimator: NSObject, UIViewControllerTransitioningDelegate {
    /**weak 修飾的屬性都是待會(huì)兒自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)時(shí)需要用到的控件中鼠,用 weak 修飾是為了避免產(chǎn)生循環(huán)引用。*/
    weak var maskView: UIView?
    weak var collectionView: UICollectionView?
    weak var pageControl: UIPageControl?
    weak var nextButton: UIButton?
    weak var previousButton: UIButton?
    
    var isPresenting: Bool = false
    
    // 實(shí)現(xiàn) UIViewControllerTransitioningDelegate 的方法
    // 注意:實(shí)現(xiàn)這兩個(gè)方法就可以知道什么時(shí)候做 present 操作沿癞,什么時(shí)候做 dismiss 操作援雇。
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresenting = true
        
        return self
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresenting = false
        
        return self
    }
}
// 遵守并實(shí)現(xiàn) UIViewControllerAnimatedTransitioning 協(xié)議
extension YSTrafficPopUpsAnimator: UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        0.5
    }
    
    // 在這個(gè)方法進(jìn)行轉(zhuǎn)場(chǎng)動(dòng)畫(huà)
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        isPresenting ? presentTransition(transitionContext) : dismissTransition(transitionContext)
    }
    
    func presentTransition(_ transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        let toView = transitionContext.view(forKey: .to)
        
        containerView.addSubview(toView!)
        
        maskView?.backgroundColor = UIColor(white: 0, alpha: 0)
        
        let duration = transitionDuration(using: transitionContext)
        
        UIView.animate(withDuration: duration, animations: {
            self.maskView?.backgroundColor = UIColor(white: 0, alpha: 0.5)
            
        }, completion: nil)
        
        toView?.layoutIfNeeded()
        self.nextButton?.alpha = 0
        self.previousButton?.alpha = 0
        
        self.collectionView?.snp.updateConstraints({ (make) in
            make.centerY.equalTo(toView!)
        })
        
        UIView.animate(withDuration: duration,
                       delay: 0,
                       usingSpringWithDamping: 0.7,
                       initialSpringVelocity: 0,
                       options: .curveEaseOut,
                       animations: {
                        toView?.layoutIfNeeded()
        }) { (_) in
            
            self.nextButton?.snp.updateConstraints({ (make) in
                make.right.equalTo(self.collectionView!)
            })
            
            self.previousButton?.snp.updateConstraints({ (make) in
                make.left.equalTo(self.collectionView!)
            })
            
            UIView.animate(withDuration: duration,
                           delay: 0,
                           usingSpringWithDamping: 0.7,
                           initialSpringVelocity: 0,
                           options: .curveEaseOut,
                           animations: {
                            
                            self.nextButton?.alpha = 1
                            self.previousButton?.alpha = 1
                            toView?.layoutIfNeeded()
            }) { (_) in
                transitionContext.completeTransition(true)
            }
        }
    }
    
    func dismissTransition(_ transitionContext: UIViewControllerContextTransitioning) {
        let fromView = transitionContext.view(forKey: .from)
        let duration = transitionDuration(using: transitionContext)
        
        self.nextButton?.snp.updateConstraints({ (make) in
            make.right.equalTo(self.collectionView!).offset(180)
        })
        self.previousButton?.snp.updateConstraints({ (make) in
            make.left.equalTo(self.collectionView!).offset(-180)
        })
        
        UIView.animate(withDuration: duration,
                       delay: 0,
                       usingSpringWithDamping: 0.7,
                       initialSpringVelocity: 0,
                       options: .curveEaseOut,
                       animations: {
                        
                        self.nextButton?.alpha = 0
                        self.previousButton?.alpha = 0
                        fromView?.layoutIfNeeded()
        }) { (_) in
            
            let offset = (fromView!.bounds.height + self.collectionView!.bounds.height) / 2
            self.collectionView?.snp.updateConstraints({ (make) in
                 make.centerY.equalTo(fromView!).offset(offset)
            })
            
            UIView.animate(withDuration: duration,
                           delay: 0,
                           usingSpringWithDamping: 0.7,
                           initialSpringVelocity: 0,
                           options: .curveEaseOut,
                           animations: {
                            fromView?.layoutIfNeeded()
            }) { (_) in
                transitionContext.completeTransition(true)
            }
            
            UIView.animate(withDuration: duration, animations: {
                self.maskView?.backgroundColor = UIColor(white: 0, alpha: 0)
            }, completion: nil)
        }
    }
    
}

五、使用

let vc = YSTrafficPopUpsViewController(imageURLStrings: imageURLStrings)
present(vc, animated: true, completion: nil)

六椎扬、總結(jié)

其實(shí)主要理解了第三點(diǎn)ViewController 和Animator 的關(guān)系以及上述三個(gè)協(xié)議之間的關(guān)系流程之后惫搏,就可以知道自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的大概實(shí)現(xiàn)思路具温。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市筐赔,隨后出現(xiàn)的幾起案子铣猩,更是在濱河造成了極大的恐慌,老刑警劉巖茴丰,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件达皿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡贿肩,警方通過(guò)查閱死者的電腦和手機(jī)鳞绕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)尸曼,“玉大人,你說(shuō)我怎么就攤上這事萄焦】亟危” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵拂封,是天一觀的道長(zhǎng)茬射。 經(jīng)常有香客問(wèn)我,道長(zhǎng)冒签,這世上最難降的妖魔是什么在抛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮萧恕,結(jié)果婚禮上刚梭,老公的妹妹穿的比我還像新娘。我一直安慰自己票唆,他們只是感情好朴读,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著走趋,像睡著了一般衅金。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上簿煌,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天氮唯,我揣著相機(jī)與錄音,去河邊找鬼姨伟。 笑死惩琉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的授滓。 我是一名探鬼主播琳水,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肆糕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了在孝?” 一聲冷哼從身側(cè)響起诚啃,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎私沮,沒(méi)想到半個(gè)月后始赎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仔燕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年造垛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晰搀。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡五辽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出外恕,到底是詐尸還是另有隱情杆逗,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布鳞疲,位于F島的核電站罪郊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏尚洽。R本人自食惡果不足惜悔橄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腺毫。 院中可真熱鬧癣疟,春花似錦、人聲如沸潮酒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)澈灼。三九已至竞川,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叁熔,已是汗流浹背委乌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荣回,地道東北人遭贸。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像心软,于是被迫代替她去往敵國(guó)和親壕吹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子著蛙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345