幾句代碼快速集成自定義轉場效果+ 全手勢驅動

原文出處:wazrxphp

寫在前面

在簡書寫完第一篇的自定義轉場文章後,已經好久沒有碰過轉場了,畢竟在公司,功能實現纔是最重要的,這些轉場的動效,只能是點睛之筆,不太容易被重視,不過個人第一篇文章仍是不少人的喜歡和討論,不少人還提出些建議,很是感謝你們,這是我第一篇文章的地址自定義轉場動畫,裏面包含了一些轉場的基礎知識,這篇文章我就再也不討論這些基礎知識了。git


爲何會有這第二篇文章,主要緣由有以下幾點:github

 

一、能不能更簡單?當我好久沒有使用轉場的時候,再次來使用它,感受仍是比較煩瑣,有一大堆記不住的長長的代理方法,都要去copy,長長的代理方法也把控制器弄得有點亂,雖然蘋果已經將整個過程充分解耦了,我在想,要是能簡單的一兩句話就能集成轉場效果多好,或者經過繼承和複寫一兩個方法就能輕鬆實現本身的轉場效果,無需關注轉場邏輯,只需關注動畫邏輯數組

 

二、閃爍和生硬?在第一篇文章中有人提到的部分的bug,好比小圓點擴散效果,若是手勢在中途取消,不會有取消動畫,很是生硬,並且會有閃爍的bug,我在想能不能解決這兩個問題,強迫症接受不了o(╯□╰)o,我如今找到了一個比較好的方式來解決問題,原理和對比圖會在後面給出網絡

 

三、能不能多添加一些效果?因此我把本身寫的效果封裝,再參照網絡一些效果,總過添加了將近20個效果ide

 

四、手勢萬歲!任何效果我都想可以手勢驅動oop

效果圖(圖比較多,請手機用戶慎重,可下載demo真機運行效果更好)

截圖中,右上角的switch開關表明push和present,全部效果都支持手勢,我就不一一演示了佈局

 

一、CircleSpreadTransition 小圓點擴散測試

CircleSpreadTransition.gif優化

 

二、MagicMoveTransition 神奇移動

MagicMoveTransition1.gif

MagicMoveTransition2.gif

 

三、XWDrawerAnimator 抽屜效果,仿照QQ和淘寶

XWDrawerAnimator1.gif

 

XWDrawerAnimator2.gif

 

四、XWCoolAnimator 自定義一些效果

XWCoolAnimator2.gif

XWCoolAnimator1.gif

XWCoolAnimator3.gif

XWCoolAnimator4.gif

XWCoolAnimator5.gif

XWCoolAnimator6.gif

XWCoolAnimator7.gif

 

五、XWFilterAnimator 經過CIFilter濾鏡自定義一些效果,請在真機上運行

XWFilterAnimator1.gif

 

XWFilterAnimator6.gif

 

XWFilterAnimator5.gif

XWFilterAnimator4.gif

 

XWFilterAnimator3.gif

XWFilterAnimator2.gif

XWFilterAnimator8.gif

XWFilterAnimator7.gif

如何使用

一、git地址:幾句代碼快速集成自定義轉場效果+ 全手勢驅動,clone後將整個XWTranstion文件夾導入工程

二、導入UINavigationController+XWTransition.h或者UIViewController+XWTransition.h兩個分類

三、選擇你須要的效果器進行根據初始化方法進行初始化,好比下面的小圓點擴散,初始化指定開始圓心和半徑

1

XWCircleSpreadAnimator *animator = [XWCircleSpreadAnimator xw_animatorWithStartCenter:self.button.center radius:20];

四、經過初始化的效果器轉場,根據分類提供的方法進行push或者present,就完成了!

1

2

3

[self.navigationController xw_pushViewController:toVC withAnimator:animator];

或者

[self xw_presentViewController:toVC withAnimator:animator];

手勢驅動

一、在UIViewController+XWTransition.h分類中提供了兩個方法,用來註冊手勢驅動,在viewDidLoad的時候調用註冊手勢就能夠了,詳見demo,注意避免循環引用,手勢支持邊緣屬性

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

/**

 *  註冊to手勢(push或者Present手勢)

 *

 *  @param direction       手勢方向

 *  @param tansitionConfig 手勢觸發的block,block中須要包含你的push或者Present的邏輯代碼,注意避免循環引用問題

 *  @param edgeSpacing     手勢觸發的邊緣距離,該值爲0,表示在整個控制器視圖上都有效,否者這在邊緣的edgeSpacing之類有效

 */

 

- (void)xw_registerToInteractiveTransitionWithDirection:(XWInteractiveTransitionGestureDirection)direction transitonBlock:(dispatch_block_t)tansitionConfig edgeSpacing:(CGFloat)edgeSpacing;

 

/**

 *  註冊back手勢(pop或者dismiss手勢)

 *

 *  @param direction       手勢方向

 *  @param tansitionConfig 手勢觸發的block,block中須要包含你的pop或者dismiss的邏輯代碼,注意避免循環引用問題

 *  @param edgeSpacing     手勢觸發的邊緣距離,該值爲0,表示在整個控制器視圖上都有效,否者這在邊緣的edgeSpacing之類有效

 */

 

- (void)xw_registerBackInteractiveTransitionWithDirection:(XWInteractiveTransitionGestureDirection)direction transitonBlock:(dispatch_block_t)tansitionConfig edgeSpacing:(CGFloat)edgeSpacing;

 

二、事例代碼

1

2

3

4

    __weak typeof(self)weakSelf = self;    //註冊一個全屏的back轉場

    [self xw_registerBackInteractiveTransitionWithDirection:XWInteractiveTransitionGestureDirectionDown transitonBlock:^{    //pop或者dismiss操做

        [weakSelf xw_transiton];

    } edgeSpacing:0];

關於神奇移動效果

一、在UIViewController+XWTransition.h分類中提供了三個關於神奇移動的方法,你須要在轉場前和轉場後的控制器中分別註冊神奇移動先後的視圖(用來告知神奇移動先後的frame),而後經過神奇移動效果器就能夠觸發神奇移動轉場了

1

2

3

4

5

6

7

8

9

10

11

12

13

/**

 * 註冊神奇移動起始視圖

 *

 * @param group 神奇移動起始視圖數組

 */- (void)xw_addMagicMoveStartViewGroup:(NSArray<UIView *> *)group;/**

 * 註冊神奇移動終止視圖

 *

 * @param group 神奇移動終止視圖數組,注意起始視圖數組和終止視圖數組的視圖須要一一對應纔能有正確的效果

 */- (void)xw_addMagicMoveEndViewGroup:(NSArray<UIView *> *)group;/**

 * 改變神奇移動起始視圖,由於在back的時候,有可能不須要再回到原來起始的位置,須要去一個新的視圖位置,因此在back前須要調用該方法改變起始視圖數組

 *

 * @param group 新的起始視圖數組

 */- (void)xw_changeMagicMoveStartViewGroup:(NSArray<UIView *> *)group;

二、事例代碼

1

2

3

4

5

    //fromVC轉場前控制器中註冊神奇移動前視圖

    [self xw_addMagicMoveStartViewGroup:@[imgView, view1, view2]];    //toVC轉場後控制器中註冊神奇移動前視圖

    [self xw_addMagicMoveEndViewGroup:@[imgView, view1, view2]];    //初始化神奇移動效果器轉場

    XWMagicMoveToController *toVC = [XWMagicMoveToController new];

    [self xw_presentViewController:toVC withAnimator:animator];

 

三、轉場中存在cell,因爲在轉場過程當中cell尚未加載,因此沒法註冊cell爲神奇移動視圖,這種狀況須要生產一個零時視圖註冊爲轉場視圖來使用,具體請參考demo中的九宮格例子

 

四、關於提供的imageMode屬性:在神奇移動中,有個問題,就是移動中的臨時視圖通常都是用截圖大法截圖而來的,可是若是從從小圖變成大圖,因爲截圖爲小圖截圖,變大過程當中會有模糊的現象,若是設置了該屬性,我會對神奇移動視圖中的包含了image的視圖進行檢測,若是能檢測到image則直接取image,而不截圖,就能解決模糊的問題,代碼以下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

- (UIView *)_xw_snapshotView:(UIView *)view{    CALayer *layer = view.layer;    UIView *snapView = [UIView new];

    snapView.frame = view.frame;    BOOL imgMode = [objc_getAssociatedObject(view, &kXWMagicMovePropertyInViewKey) boolValue] || _imageMode;    UIImage *img = nil;    if (imgMode) {//若是開啓imgMode,優先直接獲取圖片,避免截圖時時從小到大形成的模糊

        if ([view isKindOfClass:[UIImageView class]]) {//取imageView中的image

            img = [(UIImageView *)view image];

        }else if ([view isKindOfClass:[UIButton class]]){//取button中的image

            img = [(UIButton *)view currentImage];

        }        if (!img && [view isKindOfClass:[UIView class]]) {//沒取到嘗試取content

            img = [UIImage imageWithCGImage:(__bridge CGImageRef)view.layer.contents];

        }

    }    //若都沒有取到,則截圖

    if (!img) {        UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.opaque, 0);        CGContextRef context = UIGraphicsGetCurrentContext();

        [layer renderInContext:context];

        img = UIGraphicsGetImageFromCurrentImageContext();        UIGraphicsEndImageContext();

    }

    snapView.layer.contents = (__bridge id)img.CGImage;    return snapView;

}

關於抽屜效果的全屏拖動

一、抽屜效果因爲註冊的手勢都是在控制器的的視圖上,若是作QQ設置界面的效果,不可能在toVC以外點擊和拖動可以back,個人思路是會在toVC沒有覆蓋的區域添加一個透明視圖,給透明視圖加上點擊和拖動手勢,具體代碼以下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

//首先須要設置點擊和拖動的back操做,block中應該包含你的dismiss或者pop邏輯/**

 *  開啓邊緣(就是屏幕除開toView所佔用的部分)back手勢和邊緣點擊返回效果,相似於QQ設置界面的返回效果

 *

 *  @param backConfig 返回操做,您的dismiss或者pop操做

 */- (void)xw_enableEdgeGestureAndBackTapWithConfig:(dispatch_block_t)backConfig;//添加全屏手勢代碼以下/**

 *  添加全局手勢和點擊視圖

 */- (void)_xw_addFullGestureAndTapBackViewInContainerView:(UIView *)containerView toView:(UIView *)toView distance:(CGFloat)distance{    CGFloat width = _vertical ? containerView.frame.size.width : containerView.frame.size.width - fabs(distance);    CGFloat height = _vertical ? containerView.frame.size.height - fabs(distance) : containerView.frame.size.height;    //若是toVC是全屏鋪滿則無需添加全局手勢,直接使用toVC的view的手勢就行了

    if (width == 0 || height == 0)return;    if (!_backConfig) return;    //若是toView註冊過手勢,咱們直接獲取這個手勢

    NSArray<UIGestureRecognizer *> *gestures = toView.gestureRecognizers;

    __block id target = nil;

    [gestures enumerateObjectsUsingBlock:^(UIGestureRecognizer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {        NSString *panType = objc_getAssociatedObject(obj, "xw_interactivePanKey");        if ([panType isEqualToString:@"xw_interactiveBackPan"] && obj.delegate) {

            target = obj.delegate;

            *stop = YES;

        }

    }];    CGFloat x = _vertical || _direction == XWDrawerAnimatorDirectionRight ? 0 : -distance;    CGFloat y = !_vertical || _direction == XWDrawerAnimatorDirectionBottom ? 0 : -distance;    UIControl *gestureView = [UIControl new];    //添加點擊事件

    [gestureView addTarget:self action:@selector(_xw_backConfig) forControlEvents:UIControlEventTouchUpInside];

    gestureView.frame = CGRectMake(x, y, width, height);

    gestureView.backgroundColor = [UIColor clearColor];    //第一種狀況,toView已經添加了返回手勢,咱們直接拿到該手勢的target和action

    if (target) {        //給containerView添加全局手勢

        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:NSSelectorFromString(@"_xw_handleGesture:")];

        [containerView addGestureRecognizer:pan];

 

    }else{        //第二種狀況,toView沒有添加手勢,咱們須要建立一個

        __weak typeof(self)weakSelf = self;

        XWInteractiveTransition *backTransition = [XWInteractiveTransition xw_interactiveTransitionWithDirection:(XWInteractiveTransitionGestureDirection)_direction config:^{

            weakSelf.backConfig();

        } edgeSpacing:0];

        backTransition.panRatioBaseValue = _vertical ? containerView.frame.size.height : containerView.frame.size.width;

        [backTransition xw_addPanGestureForView:gestureView to:NO];//        [self xw_setBackInteractiveTransition:backTransition];

        [self setValue:backTransition forKey:@"backTransition"];

    }

    [containerView addSubview:gestureView];

}

解決動畫生硬

一、先看小圓點效果的例子,前面是解決前寫的,後面是如今的

未解決

小圓點未開啓timer.gif

解決後

小圓開啓timer.gif


二、問題緣由:在手勢結束後該效果不會動畫的過渡到成功或者失敗,而是整個轉場進度會直接update到0或者1,就木有動畫了

三、解決:在手指鬆開的時候,我會開啓一個CADisplayLink來不斷的刷新整個轉場進度到1或者0,來達到動畫的效果,具體代碼以下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

case UIGestureRecognizerStateEnded:{//轉場結束後

            //判斷是否須要timer

            if (!_timerEable) {

                _percent >= 0.5 ? [self _xw_finish] : [self _xw_cancle];                return;

            }            //判斷此時是否已經轉場完成,大於1或者小於0

            BOOL canEnd = [self _xw_canEndInteractiveTransitionWithPercent:_percent];            if (canEnd) return;            //開啓timer

            [self _xw_setEndAnimationTimerWithPercent:_percent];//設置開啓timer- (void)_xw_setEndAnimationTimerWithPercent:(CGFloat)percent{

    _percent = percent;    //根據失敗仍是成功設置刷新間隔

    if (percent > 0.5) {

        _timeDis = (1 - percent) / ((1 - percent) * 60);

    }else{

        _timeDis = percent / (percent * 60);

    }    //開啓timer

    [self _xw_startTimer];

}//開啓timer- (void)_xw_startTimer{    if (_timer) {        return;

    }

    _timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(_xw_timerEvent)];

    [_timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

}//timer 事件- (void)_xw_timerEvent{    if (_percent > 0.5) {

        _percent += _timeDis;

    }else{

        _percent -= _timeDis;

    }    //經過timer不斷刷新轉場進度,達到動畫效果

    [self _xw_updatingWithPercent:_percent];    //判斷進度是否達到0和1,達到則結束timer,結束轉場

    BOOL canEnd = [self _xw_canEndInteractiveTransitionWithPercent:_percent];    if (canEnd) {

        [self _xw_stopTimer];

    }

}

解決閃爍問題

一、閃爍緣由:在不使用UIView的動畫block時,咱們直接爲layer添加一個CAAnimtion,此時會先設置modelLayer爲轉場成功的狀態,好比小圓點效果會設置path爲大圓的path,可是若是轉場失敗,presentLayer依然會先變爲modelLayer設置的成功值,而後動畫才結束,走咱們的轉場失敗邏輯,因此就會閃爍

二、解決:我把手勢改變的一些關鍵狀態經過代理傳出來,在手勢結束前,咱們若是檢查到失敗,能夠先將modelLayer的值標記爲失敗時候的值,也就是初始值,就解決了該問題

三、事例代碼

1

2

3

4

//手勢轉場時的代理事件,animator默認爲爲其手勢的代理,複寫對應的代理事件可處理一些手勢失敗閃爍的狀況@protocol XWInteractiveTransitionDelegate <NSObject>@optional/**手勢轉場即將開始時調用*/- (void)xw_interactiveTransitionWillBegin:(XWInteractiveTransition *)interactiveTransition;/**手勢轉場中調用*/- (void)xw_interactiveTransition:(XWInteractiveTransition *)interactiveTransition isUpdating:(CGFloat)percent;/**若是開始了轉場手勢timer,會在鬆開手指,timer開始的時候調用*/- (void)xw_interactiveTransitionWillBeginTimerAnimation:(XWInteractiveTransition *)interactiveTransition;/**手勢轉場結束的時候調用*/- (void)xw_interactiveTransition:(XWInteractiveTransition *)interactiveTransition willEndWithSuccessFlag:(BOOL)flag percent:(CGFloat)percent;@end//我在小圓點擴散效果中處理的以下- (void)xw_interactiveTransition:(XWInteractiveTransition *)interactiveTransition willEndWithSuccessFlag:(BOOL)flag percent:(CGFloat)percent{    if (!flag) {        //防止失敗後的閃爍,若是失敗將遮罩的path設置爲其實的小圓path

        _maskLayer.path = _startPath.CGPath;

    }

    _containerView.userInteractionEnabled = YES;

關於coolTransiton

一、直接經過枚舉初始化就有已經集成的部分效果,具體以下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

typedef NS_ENUM(NSUInteger, XWCoolTransitionAnimatorType){    //全屏翻頁

    XWCoolTransitionAnimatorTypePageFlip,    //中間翻頁

    XWCoolTransitionAnimatorTypePageMiddleFlipFromLeft,

    XWCoolTransitionAnimatorTypePageMiddleFlipFromRight,

    XWCoolTransitionAnimatorTypePageMiddleFlipFromTop,

    XWCoolTransitionAnimatorTypePageMiddleFlipFromBottom,    //開窗

    XWCoolTransitionAnimatorTypePortal,    //摺疊

    XWCoolTransitionAnimatorTypeFoldFromLeft,

    XWCoolTransitionAnimatorTypeFoldFromRight,    //爆炸

    XWCoolTransitionAnimatorTypeExplode,    //酷炫線條效果

    XWCoolTransitionAnimatorTypeHorizontalLines,

    XWCoolTransitionAnimatorTypeVerticalLines,    //掃描效果

    XWCoolTransitionAnimatorTypeScanningFromLeft,

    XWCoolTransitionAnimatorTypeScanningFromRight,

    XWCoolTransitionAnimatorTypeScanningFromTop,

    XWCoolTransitionAnimatorTypeScanningFromBottom,

};

二、 cool轉場效果中的Portal、Fold、Explode效果的部分代碼邏輯來源於ColinEberhardt/VCTransitionsLibrary,很是感謝做者,我只是將其進行了部分改動,以便對手勢的支持更加完善,裏面還有許多其餘效果,本人經歷有限就沒有再集成進來了,你們能夠自行查看;cool轉場效果的Lines的想法來自於cinkster/HUAnimator, 很是感謝做者,可是因爲做者在對toVC截圖採用了延遲的方式來處理,致使了很差處理的bug和一些手勢上的bug,對此我採用了另外一種方式來解決截圖的問題,使用了layer的contentRect屬性,解決了發現的問題,相關代碼請自行查看

關於FilterTransition

一、XWFilterAnimator 全都是基於不一樣的CIFilter產生的一些濾鏡效果,貌似在模擬器沒法運行這些效果,請在真機上測試,直接經過枚舉初始化就有已經集成的部分效果,具體以下:

1

2

3

4

5

6

7

8

9

10

typedef NS_ENUM(NSUInteger, XWFilterAnimatorType) {

    XWFilterAnimatorTypeBoxBlur,//模糊轉場,對應CIBoxBlur

    XWFilterAnimatorTypeSwipe,//滑動過渡轉場,對應CISwipeTranstion

    XWFilterAnimatorTypeBarSwipe,//對應CIBarSwipeTranstion

    XWFilterAnimatorTypeMask,//按指定遮罩圖片轉場,對應CIDisintegrateWithMaskTransition

    XWFilterAnimatorTypeFlash,//閃爍轉場,對應CIFlashTransition

    XWFilterAnimatorTypeMod,//條紋轉場 對應CIModTransition

    XWFilterAnimatorTypePageCurl,//翻頁轉場 對應CIPageCurlWithShadowTransition

    XWFilterAnimatorTypeRipple,//波紋轉場,對應CIRippleTransition

    XWFilterAnimatorTypeCopyMachine, //效果和XWCoolAnimator中的Scanning效果相似,對應CICopyMachineTransition};

 

二、若是想要添加其餘濾鏡轉場,能夠嘗試個人FilterTransition中書寫分類的方式,只須要指定CIFilter和相關邏輯便可

關於自定義轉場效果

一、你只須要繼承於XWTransitionAnimator,就像我上面全部的效果器同樣,而後複寫須要的屬性和兩個必須的方法便可,而後你就可使用你自定義的效果器轉場,XWTransitionAnimator頭文件以下:

1

2

3

4

5

@interface XWTransitionAnimator : NSObject<UIViewControllerTransitioningDelegateUINavigationControllerDelegateUITabBarControllerDelegate, XWInteractiveTransitionDelegate>//to轉場時間 默認0.5@property (nonatomic, assign) NSTimeInterval toDuration;//back轉場時間 默認0.5@property (nonatomic, assign) NSTimeInterval backDuration;//是否須要開啓手勢timer,某些轉場若是在轉成過程當中所開手指,不會有動畫過渡,顯得很生硬,開啓timer後,鬆開手指,會用timer不斷的刷新轉場百分比,消除生硬的缺點@property (nonatomic, assign) BOOL needInteractiveTimer;/**

 *  配置To過程動畫(push, present),自定義轉場動畫應該複寫該方法

 */- (void)xw_setToAnimation:(id<UIViewControllerContextTransitioning>)transitionContext;/**

 *  配置back過程動畫(pop, dismiss),自定義轉場動畫應該複寫該方法

 */- (void)xw_setBackAnimation:(id<UIViewControllerContextTransitioning>)transitionContext;@end

二、這樣就只須要關心動畫的邏輯,其他的事情就不用管了,不過若是遇到閃爍問題,你只須要複寫相關的手勢代理方法,就像我在小圓點轉場中同樣,由於XWTransitionAnimator默認是手勢管理者的代理,因此直接實現代理方法就行了

寫在最後

陸陸續續的就這些了,東西比較多,可能個人敘述也還有必定問題,某些內容可能描述的不太清楚,請你們多多參考demo,但願本文能讓你們之後再設計到自定義轉場的時候可以迅速解決問題,再次複習一下地址幾句代碼快速集成自定義轉場效果+ 全手勢驅動 ,若是對您有幫助歡迎給予star支持!

更新 2016-06-24

今天早上思考了一下,優化了一下DrawerAnimator,以前的toVC的frame不會隨着設置的distance改變,默認通常都是屏幕的寬和高,也就是說顯示以後,toVC的有一部分實際是在屏幕外面的,這對於後續的佈局是不太方便的,因此我修改了一下,如今toVC的frame是和設置的distance相關的,所看見的toVC的部分就是toVC的所有

更新 2016-07-05

一、今天發現了一個問題,就是在進行不一樣的效果屢次push的時候,在pop的時候,以前的效果會失效,我修復了這個問題,請看截圖,上面是修復前,下面是修復後

修正以前.gif

修正以後.gif

 

能夠看見,修復前,在最後一次back的時候,那個爆炸的效果已經失效了,
二、問題緣由:在每次push時我會切換navigationController的delegate爲當前效果器,從而能完成轉場效果的邏輯,因此屢次push後,代理始終是最後一個效果器,而在pop的時候那個效果器隨着對應的pop操做已經被銷燬了,而代理並無切換爲以前的爆炸效果器,因此自定義轉場就沒法觸發了
三、解決:因爲我每個效果器是和被push出的VC綁定的,因此當被pushVC被銷燬的時候,效果器就會銷燬,此刻,應該去檢測一下代理,若是上一個VC存在效果器,則須要切換回該效果器,因此須要在pushVC的dealloc方法中須要對代理進行檢測和切換,爲了達到目的,須要對VC的dealloc方法進行調劑,調劑的方法稍微有點複雜,具體請看我另外一篇簡書文章:一句代碼,更加優雅的調用KVO和通知中關於調劑dealloc方法的相關代碼,在dealloc中添加了代理檢測和切換的方法來達到目的~

 

 

原文:http://bbs.520it.com/forum.php?mod=viewthread&tid=2906

相關文章
相關標籤/搜索