iOS-自定义控件制作及封装

在开发过程中由于系统的控件有时候不能满足开发者的需求,学会制作自定义控件是很有必要的。这篇文章主要总结整理开发基本的自定义控件的步骤以及一些知识点的梳理。

目标

制作一个自定义控件,能够实现如下效果。


selectButtonShow.gif

制作思路及分析

  • 对这个控件的布局及结构进行分析。对这整个控件的封装实际上就是对一个View的封装,这个View的大小和屏幕大小相同,在它的顶部有一个子视图(topView),这个topView上面承载了我们需要的两个button控件,而imageView以及titleLabel是button中的子属性,图文为上下布局方式。

  • 对控件展现的逻辑以及需求进行分析。在触发该控件(如在demo中通过点击导航栏中的按钮触发)后,控件中的topView部分整体从导航栏上方滑到导航栏下方,直至topview顶部与导航栏底部接合。与此同时,控件的其他部分变成30%不透明度的背景。在这整个过程中涉及到三个简单动画
    1、topView的位置从导航栏上方-->导航栏下方
    2、topView的不透明度从0-->1
    3、控件的背景色从 clearColor --> blackColor,0.3
    (这里有个做的不好的地方就是在改变控件背景色的时候,由于导航栏是带透明的,导致导航栏的颜色也被影响,但这个不是重点)

在点击button之后收回控件,然后跳转button的逻辑,或者点击其他地方收回控件。

实现过程

  • 对UIView进行封装
    1、新建一个继承UIView的类
    2、在新建的类中添加子控件的属性(weak)
    3、在initWithFrame方法中添加子控件
    4、在layoutSubViews方法中设置子控件的frame
    5、提供一个model属性,将我们要制作的控件抽象为这个model,负责数据的交互,重写model的构造方法
    6、重写UIView类的setModel方法,并在其中为子控件赋值。

SelectButtonView代码如下

//1、新建一个继承UIView的类
#import <UIKit/UIKit.h>

@class SelectButtonModel;

@interface SelectButtonView : UIView
//5、提供一个model属性,将我们要制作的控件抽象为这个model,负责数据的交互,重写model的构造方法
@property (nonatomic,strong) SelectButtonModel *model;
@property (nonatomic, copy) void (^leftButtonAction)(BOOL shouldResponse);
@property (nonatomic, copy) void (^rightButtonAction)(BOOL shouldResponse);
@end

@interface SelectButtonMenu : UIControl
+ (void) showMenuInView:(UIView *)view
               leftName:(NSString *)leftName
              rightName:(NSString *)rightName
               leftIcon:(NSString *)leftIcon
              rightIcon:(NSString *)rightIcon
  leftButtonClickAction:(void(^)(BOOL shoudResponse))leftButtonAction
 rightButtonClickAction:(void(^)(BOOL shoudResponse))rightButtonAction
          dismissAction:(void(^)())dismissAction;

@end
#import "SelectButtonView.h"
#import "SelectButtonModel.h"
#import "UIButton+Composing.h"
const CGFloat separateLineWidth = 0.5f;
//const CGFloat orgialY = 64.f;

#define orgialY ({([UIApplication sharedApplication].isStatusBarHidden)? 44.f : 64.f; })
const CGFloat viewHeight = 75.f;
@interface SelectButtonView()
//2、在新建的类中添加子控件的属性(weak)
@property (nonatomic, weak) UIButton *leftButton;
@property (nonatomic, weak) UIButton *rightButton;
@property (nonatomic, weak) UIView *topView;

@end

@implementation SelectButtonView

//3、在initWithFrame方法中添加子控件
- (instancetype) initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        //// 注意:该处不要给子控件设置frame与数据,可以在这里初始化子控件的属性
        [self setBackgroundColor:[UIColor clearColor]];
        self.opaque = NO;

//单击手势,用于实现单击屏幕其他地方收回控件
        UITapGestureRecognizer *gestureRecognizer;
        gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                    action:@selector(singleTap:)];
        [self addGestureRecognizer:gestureRecognizer];
        
        UIView *topView = [[UIView alloc] init];
        [topView setBackgroundColor:[UIColor lightGrayColor]];
        self.topView = topView;
        [self addSubview:topView];
        
        UIButton *leftButton = [[UIButton alloc] init];
        [leftButton setBackgroundColor:[UIColor whiteColor]];
        [leftButton.titleLabel setFont:[UIFont systemFontOfSize:13.f]];
        [leftButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [leftButton setBackgroundImage:[self imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateHighlighted];

        [leftButton addTarget:self action:@selector(leftButtonClicked) forControlEvents:UIControlEventTouchUpInside];
        self.leftButton = leftButton;
        [self.topView addSubview:leftButton];
        
        UIButton *rightButton = [[UIButton alloc] init];
        [rightButton setBackgroundColor:[UIColor whiteColor]];
        [rightButton.titleLabel setFont:[UIFont systemFontOfSize:13.f]];
        [rightButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [rightButton setBackgroundImage:[self imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateHighlighted];
        [rightButton addTarget:self action:@selector(rightButtonClicked) forControlEvents:UIControlEventTouchUpInside];
        self.rightButton = rightButton;
        [self.topView addSubview:rightButton];
    }
    return self;
}

/*4、在layoutSubViews方法中设置子控件的frame
      以下情况会触发layoutSubViews方法
     1)addSubView
     2)改变View的Frame
     3)滚动ScrollView
     4)旋转Screen
     5)改变子UIView的大小会触发父UIView上的layoutSubview事件
*/
- (void)layoutSubviews
{
    [super layoutSubviews];
    CGFloat topViewX = 0;
    CGFloat topViewH = viewHeight;
    CGFloat topViewY = orgialY - topViewH;
    CGFloat topViewW = self.bounds.size.width;
    self.topView.frame = CGRectMake(topViewX, topViewY, topViewW, topViewH);
       
    CGFloat leftButtonX = 0;
    CGFloat leftButtonY = separateLineWidth;
    CGFloat leftButtonW = (topViewW - separateLineWidth)/2;
    CGFloat leftButtonH = topViewH - separateLineWidth;
    self.leftButton.frame = CGRectMake(leftButtonX, leftButtonY, leftButtonW, leftButtonH);
    self.leftButton.contentEdgeInsets = UIEdgeInsetsMake(8.f, 0, -11.f, 0);
    
    
    CGFloat rightButtonW = (topViewW - separateLineWidth)/2;
    CGFloat rightButtonX = topViewW - rightButtonW;
    CGFloat rightButtonY = separateLineWidth;
    CGFloat rightButtonH = topViewH - separateLineWidth;
    self.rightButton.frame = CGRectMake(rightButtonX, rightButtonY, rightButtonW, rightButtonH);
    self.rightButton.contentEdgeInsets = UIEdgeInsetsMake(8.f, 0, -11.f, 0);
    
}
//6、重写UIView类的setModel方法,并在其中为子控件赋值
- (void)setModel:(SelectButtonModel *)model
{
    _model = model;
    
    [self.leftButton setTitle:model.leftButtonName forState:UIControlStateNormal];
    [self.leftButton setImage:[UIImage imageNamed:model.leftButtonIcon] forState:UIControlStateNormal];
//composingWithStyle是对UIButton的一个扩展方法,用于设置UIButton内imageView与titleLabel的布局,在最后会给出
    [self.leftButton composingWithStyle:GGImageComposingTop spacing:5.f];
    [self.rightButton setTitle:model.rightButtonName forState:UIControlStateNormal];
    [self.rightButton setImage:[UIImage imageNamed:model.rightButtonIcon] forState:UIControlStateNormal];
    [self.rightButton composingWithStyle:GGImageComposingTop spacing:5.f];
}

model代码如下

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface SelectButtonModel : NSObject

@property (nonatomic,copy) NSString * leftButtonName;
@property (nonatomic,copy) NSString * rightButtonName;
@property (nonatomic,copy) NSString * leftButtonIcon;
@property (nonatomic,copy) NSString * rightButtonIcon;
@property (nonatomic,assign) CGFloat topViewHeight;

+ (instancetype)modelWithLeftName:(NSString *)leftName leftIcon:(NSString *)leftIcon rightName:(NSString *)rightName rightIcon:(NSString *)righticon;
- (instancetype)initWithLeftName:(NSString *)leftName leftIcon:(NSString *)leftIcon rightName:(NSString *)rightName rightIcon:(NSString *)righticon;

@end
#import "SelectButtonModel.h"

@implementation SelectButtonModel

+ (instancetype)modelWithLeftName:(NSString *)leftName leftIcon:(NSString *)leftIcon rightName:(NSString *)rightName rightIcon:(NSString *)righticon
{
    return [[self alloc] initWithLeftName:leftName leftIcon:leftIcon rightName:rightName rightIcon:righticon];
}

- (instancetype)initWithLeftName:(NSString *)leftName leftIcon:(NSString *)leftIcon rightName:(NSString *)rightName rightIcon:(NSString *)righticon
{
    if (self = [super init]) {
        self.leftButtonName = leftName;
        self.leftButtonIcon = leftIcon;
        self.rightButtonName = rightName;
        self.rightButtonIcon = righticon;
    }
    return self;
}
@end
  • 实现动画
    在对SelectButtonView做好UI上面的封装之后,接下来我们对其添加需要的动画效果,具体代码还是在SelectButtonView的layoutSubViews方法中实现。
    现在我们将layoutSubViews方法修改如下
- (void)layoutSubviews
{
    [super layoutSubviews];
    CGFloat topViewX = 0;
    CGFloat topViewH = viewHeight;
    CGFloat topViewY = orgialY - topViewH;
    CGFloat topViewW = self.bounds.size.width;
    self.topView.frame = CGRectMake(topViewX, topViewY, topViewW, topViewH);
    //其实就是一个简单的动画。。
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDelay:0.0f];
    [UIView setAnimationDuration:0.5f];
    [self setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.3f]];
    self.topView.frame = CGRectMake(0, orgialY, self.bounds.size.width, viewHeight);
    [UIView commitAnimations];
    
    CGFloat leftButtonX = 0;
    CGFloat leftButtonY = separateLineWidth;
    CGFloat leftButtonW = (topViewW - separateLineWidth)/2;
    CGFloat leftButtonH = topViewH - separateLineWidth;
    self.leftButton.frame = CGRectMake(leftButtonX, leftButtonY, leftButtonW, leftButtonH);
    self.leftButton.contentEdgeInsets = UIEdgeInsetsMake(8.f, 0, -11.f, 0);
    
    
    CGFloat rightButtonW = (topViewW - separateLineWidth)/2;
    CGFloat rightButtonX = topViewW - rightButtonW;
    CGFloat rightButtonY = separateLineWidth;
    CGFloat rightButtonH = topViewH - separateLineWidth;
    self.rightButton.frame = CGRectMake(rightButtonX, rightButtonY, rightButtonW, rightButtonH);
    self.rightButton.contentEdgeInsets = UIEdgeInsetsMake(8.f, 0, -11.f, 0);
    
}

增加收回控件的dismiss方法

- (void)dismissMenu:(BOOL)animated
{
    if(animated)
    {
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDelay:0.0f];
        [UIView setAnimationDuration:0.5f];
\\在这里设置动画的代理是为了在之后调用animationDidStop方法
        [UIView setAnimationDelegate:self];
        [UIView setAnimationWillStartSelector:@selector(animationDidStart:)];
        [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:)];
        self.topView.frame = CGRectMake(0, orgialY - viewHeight, self.bounds.size.width, viewHeight);
        self.backgroundColor = [UIColor clearColor];
        [UIView commitAnimations];
    }
    else
    {
        [self removeFromSuperview];
    }
}

-(void)animationDidStart:(CAAnimation *)anim
{
    NSLog(@"animation is start ...");
}
//确保在动画播放结束之后再调用removeFromSuperview移除控件
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    [self removeFromSuperview];
}
//单击屏幕其他地方收回控件
- (void)singleTap:(UITapGestureRecognizer *)recognizer
{
    [self dismissMenu:YES];
}
  • 实现回调
    注意到SelectView的.h文件中一下两个属性:
@property (nonatomic, copy) void (^leftButtonAction)(BOOL shouldResponse);
@property (nonatomic, copy) void (^rightButtonAction)(BOOL shouldResponse);

分别是左右按钮点击之后的block回调。
在代码中添加一下方法,也就是button的action方法

- (void)leftButtonClicked
{
    BOOL shouldResponse = YES;
    [self dismissMenu:YES];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.05f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (self.leftButtonAction) {
            self.leftButtonAction(shouldResponse);
        }
    });
    
}

-(void)rightButtonClicked
{
    BOOL shouldResponse = YES;
    [self dismissMenu:YES];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.05f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (self.rightButtonAction) {
            self.rightButtonAction(shouldResponse);
        }
    });
}

shouldResponse变量可以去掉,是之前考虑同一个按钮多次点击的情况增加的变量,但是对单纯实现控件没有影响。

其实到这里对SelectButtonView控件的制作以及封装已经算是完成了,但是处于其他原因考虑,我又在SelectButtonView外进行了一层封装(其实什么也没做),封装为一个叫SelectButtonMenu的类,在实际使用的时候只需要调用SelectButtonMenu的类方法showMenuInView...即可

代码如下

@interface SelectButtonMenu : UIControl
+ (void) showMenuInView:(UIView *)view
               leftName:(NSString *)leftName
              rightName:(NSString *)rightName
               leftIcon:(NSString *)leftIcon
              rightIcon:(NSString *)rightIcon
  leftButtonClickAction:(void(^)(BOOL shoudResponse))leftButtonAction
 rightButtonClickAction:(void(^)(BOOL shoudResponse))rightButtonAction
          dismissAction:(void(^)())dismissAction;

+ (void) dismissMenu;


@end
static SelectButtonMenu * selectMenu;

@interface SelectButtonMenu()
@property (nonatomic, copy) void (^dismissAction)();
@end

@implementation SelectButtonMenu{
    SelectButtonView *_selectButtonView;
}

+ (instancetype) shareMenu
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,^{
        selectMenu = [[SelectButtonMenu alloc] init];
    });
    return selectMenu;
}


+ (void) showMenuInView:(UIView *)view
               leftName:(NSString *)leftName
              rightName:(NSString *)rightName
               leftIcon:(NSString *)leftIcon
              rightIcon:(NSString *)rightIcon
  leftButtonClickAction:(void(^)(BOOL shoudResponse))leftButtonAction
 rightButtonClickAction:(void(^)(BOOL shoudResponse))rightButtonAction
          dismissAction:(void(^)())dismissAction
{
    [[self shareMenu] showMenuInView:view leftName:leftName rightName:rightName leftIcon:leftIcon rightIcon:rightIcon leftButtonClickAction:leftButtonAction rightButtonClickAction:rightButtonAction];
}


- (void) showMenuInView:(UIView *)view
               leftName:(NSString *)leftName
              rightName:(NSString *)rightName
               leftIcon:(NSString *)leftIcon
              rightIcon:(NSString *)rightIcon
  leftButtonClickAction:(void(^)(BOOL shoudResponse))leftButtonAction
 rightButtonClickAction:(void(^)(BOOL shoudResponse))rightButtonAction
{
    NSParameterAssert(view);
    
    if (_selectButtonView) {
        [_selectButtonView dismissMenu:NO];
        _selectButtonView = nil;
    }
    _selectButtonView = [[SelectButtonView alloc] initWithFrame:CGRectMake(0,0, view.bounds.size.width, view.bounds.size.height)];
    SelectButtonModel *model = [SelectButtonModel modelWithLeftName:leftName leftIcon:leftIcon rightName:rightName rightIcon:rightIcon];
    model.topViewHeight = viewHeight;
    _selectButtonView.model = model;
    [view addSubview:_selectButtonView];
    [_selectButtonView showMenuInView:view];
    [_selectButtonView setLeftButtonAction:leftButtonAction];
    [_selectButtonView setRightButtonAction:rightButtonAction];
}


+ (void)dismissMenu
{
    [[self shareMenu] dismissMenu];
}

- (void)dismissMenu {
    if (_selectButtonView) {
        [_selectButtonView dismissMenu:NO];
        _selectButtonView = nil;
    }
}
@end

最后!
在我们需要的controller中只需要写类似以下的一句代码

- (IBAction)action:(id)sender {
    [SelectButtonMenu showMenuInView:self.view leftName:@"左按钮" rightName:@"右按钮" leftIcon:@"keyword" rightIcon:@"promotion" leftButtonClickAction:^(BOOL shoudResponse) {
        //Do anything here..
    } rightButtonClickAction:^(BOOL shoudResponse) {
        //Do anything here..
    } dismissAction:nil];
}

整个控件的制作和封装过程大致就是这样,不管是制作多么复杂的控件,都可以将其分解然后一步一步去实现它

最后给出UIButton的图文布局扩展代码

#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, GGComposingStyle)
{
    GGImageComposingLeft   = 0,        //> 图左字右 默认
    GGImageComposingRight  = 1,        //> 图右字左
    GGImageComposingTop    = 2,        //> 图上字下
    GGImageComposingBottom = 3,        //> 图下字上
};

@interface UIButton (Composing)

- (void)composingWithStyle:(GGComposingStyle)style spacing:(CGFloat)spacing;
@end

#import "UIButton+Composing.h"

@implementation UIButton (Composing)

- (void)composingWithStyle:(GGComposingStyle)style spacing:(CGFloat)spacing
{
    // 图为空 或 文本为空, 不做处理
    if (!self.imageView.image || !self.titleLabel.text.length) {
        return;
    }
    
    CGFloat imageOffsetX = (self.imageView.image.size.width + [self.titleLabel.text sizeWithFont:self.titleLabel.font].width) / 2 - self.imageView.image.size.width / 2;
    CGFloat imageOffsetY = self.imageView.image.size.height / 2 + spacing / 2;
    CGFloat labelOffsetX = (self.imageView.image.size.width + [self.titleLabel.text sizeWithFont:self.titleLabel.font].width / 2) - (self.imageView.image.size.width + [self.titleLabel.text sizeWithFont:self.titleLabel.font].width) / 2;
    CGFloat labelOffsetY = [self.titleLabel.text sizeWithFont:self.titleLabel.font].height / 2 + spacing / 2;
    
    switch (style) {
        case GGImageComposingLeft:
            self.imageEdgeInsets = UIEdgeInsetsMake(0, -spacing / 2, 0, spacing / 2);
            self.titleEdgeInsets = UIEdgeInsetsMake(0, spacing / 2, 0, -spacing / 2);
            break;
            
        case GGImageComposingRight:
            self.imageEdgeInsets = UIEdgeInsetsMake(0, [self.titleLabel.text sizeWithFont:self.titleLabel.font].width + spacing / 2, 0, -([self.titleLabel.text sizeWithFont:self.titleLabel.font].width + spacing / 2));
            self.titleEdgeInsets = UIEdgeInsetsMake(0, -(self.imageView.image.size.width + spacing / 2), 0, self.imageView.image.size.width + spacing / 2);
            break;
            
        case GGImageComposingTop:
            self.imageEdgeInsets = UIEdgeInsetsMake(-imageOffsetY, imageOffsetX, imageOffsetY, -imageOffsetX);
            self.titleEdgeInsets = UIEdgeInsetsMake(labelOffsetY, -labelOffsetX, -labelOffsetY, labelOffsetX);
            break;
            
        case GGImageComposingBottom:
            self.imageEdgeInsets = UIEdgeInsetsMake(imageOffsetY, imageOffsetX, -imageOffsetY, -imageOffsetX);
            self.titleEdgeInsets = UIEdgeInsetsMake(-labelOffsetY, -labelOffsetX, labelOffsetY, labelOffsetX);
            break;
            
        default:
            break;
    }
}
@end

结束

最后给出github上 Demo 的地址。

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

推荐阅读更多精彩内容