借鑒系統(tǒng)UITableView的設(shè)計(jì)思想,自定義一個(gè)"花瓣"菜單

下個(gè)周又要投入到公司項(xiàng)目的開發(fā)中去了,今天抽空寫一個(gè)類似于桌面懸停的菜單.當(dāng)移到底部的時(shí)候效果看起來有點(diǎn)像一個(gè)小烏龜哦!O(∩_∩)O~.還是"花瓣"菜單好聽些.

先來看一下效果


XLCircleMenu.gif

是不是覺得挺好玩的呀.

通過這篇文章你可以學(xué)到:

  • 1.系統(tǒng)UITableView的部分設(shè)計(jì)思想
  • 2.自定義控件常用設(shè)計(jì)思路
  • 3.動(dòng)畫的具體使用
  • 4.手勢(shì)的具體使用
  • 4.裝逼一點(diǎn),良好的代碼風(fēng)格
  • 5......

開始碼

  • 隨機(jī)顏色
    為了快速區(qū)分視圖,這里用了隨機(jī)顏色來區(qū)分,生成隨機(jī)顏色的方式比較多.
    常見的獲取方法為如下:
#define RandomColor [UIColor colorWithRed:arc4random_uniform(255)/255.0 green:arc4random_uniform(255)/255.0 blue:arc4random_uniform(255)/255.0 alpha:1]

通過類方法實(shí)現(xiàn):

+ (UIColor *)randomColor{
    static BOOL seed = NO;
    if (!seed) {
        seed = YES;
        srandom((uint)time(NULL));
    }
    CGFloat red = (CGFloat)random()/(CGFloat)RAND_MAX;
    CGFloat green = (CGFloat)random()/(CGFloat)RAND_MAX;
    CGFloat blue = (CGFloat)random()/(CGFloat)RAND_MAX;
    return [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];//alpha為1.0,顏色完全不透明
}

基本設(shè)計(jì)

我們?cè)谧龉部丶臅r(shí)候,可以把要做的部分捋一捋.其實(shí)我們?cè)谧隹蛻舳碎_發(fā)可以類比網(wǎng)頁(yè)的開發(fā).做的事情無非就是拿到服務(wù)端給的數(shù)據(jù),通過不同的方式展示出來.其中就涉及到:

  • 1.數(shù)據(jù):從客戶端來看一般就是服務(wù)端給的json格式的數(shù)據(jù)
  • 2.樣式:從客戶端開發(fā)來看就是設(shè)置各個(gè)控件的各種屬性
  • 3.交互:
    我暫且把這三樣映射到UITableView上
    數(shù)據(jù)對(duì)應(yīng)著DataSource代理,樣式對(duì)應(yīng)著我們拿到數(shù)據(jù)之后自定義的cell不同類型(其實(shí)就是設(shè)置不同屬性為不同值),交互對(duì)應(yīng)著Delegate代理.
    接下來我們也仿照則TabelView的代理寫

系統(tǒng)TableView的DataSource代理


@protocol UITableViewDataSource<NSObject>

@required

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

@optional

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;              // Default is 1 if not implemented

- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;    // fixed font style. use custom view (UILabel) if you want something different
- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;

// Editing

// Individual rows can opt out of having the -editing property set for them. If not implemented, all rows are assumed to be editable.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;

// Moving/reordering

// Allows the reorder accessory view to optionally be shown for a particular row. By default, the reorder control will be shown only if the datasource implements -tableView:moveRowAtIndexPath:toIndexPath:
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;

// Index

- (nullable NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView __TVOS_PROHIBITED;                                                    // return list of section titles to display in section index view (e.g. "ABCD...Z#")
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index __TVOS_PROHIBITED;  // tell table which section corresponds to section title/index (e.g. "B",1))

// Data manipulation - insert and delete support

// After a row has the minus or plus button invoked (based on the UITableViewCellEditingStyle for the cell), the dataSource must commit the change
// Not called for edit actions using UITableViewRowAction - the action's handler will be invoked instead
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;

// Data manipulation - reorder / moving support

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;

@end

當(dāng)然我們也沒必要把系統(tǒng)的代理一個(gè)一個(gè)仿照則寫完,只要自己能夠理解到如何根據(jù)系統(tǒng)API的設(shè)計(jì)思想來設(shè)計(jì)自己寫的代碼就行了.

自己設(shè)計(jì)的DataSource代理


@protocol XLCircleMenuDataSource <NSObject>

@required
- (NSInteger)numberOfCircleViewForCircleMenu:(XLCircleMenu *)circleMenu;
- (UIButton *)circleMenu:(XLCircleMenu *)circleMenu circleViewAtIndex:(NSInteger)index;

@optional
- (CGFloat)lengthForCircleMenu:(XLCircleMenu *)circleMenu;
- (UIView *)centerViewForCircleMenu:(XLCircleMenu *)circleMenu;

@end

@protocol XLCircleMenuDelegate <NSObject>

@optional
- (void)circleMenu:(XLCircleMenu *)circleMenu didClickCircleView:(UIButton *)circleView;

@end

注釋我就沒有加了,因?yàn)镺C最好的就是見名知意.

設(shè)計(jì)類

我們?cè)谠O(shè)計(jì)類的時(shí)候,做得比較好的,需要考慮屬性的讀寫情況,一般只把需要暴露給外部知道的才暴露出去.

然后在為類添加屬性的時(shí)候,需要考慮界面和功能,界面和功能需要在寫代碼之前就應(yīng)該清楚的.舉個(gè)例子:

  • 1.具體有多少個(gè)可點(diǎn)的小圓,應(yīng)該通過代理來傳遞的,并且小圓的個(gè)數(shù)應(yīng)該不止在一個(gè)地方用到,所以可以定義為屬性,而且中間有一個(gè)大圓也是通過代理傳遞的,也需要定義一個(gè)屬性來接收.于是可以定義出兩個(gè)屬性.

有哪些屬性我們還可以直接從功能和界面上直接去思考.

  • 2.根據(jù)上面的分析依次考慮我們界面上的元素和我們需要控制的屬性.大致定義出了如下屬性(實(shí)現(xiàn)的思路很多,不一定非要這樣定義)
@property (nonatomic, weak) id<XLCircleMenuDataSource> dataSource;
@property (nonatomic, weak) id<XLCircleMenuDelegate> delegate;

@property (nonatomic, assign, readonly) CGPoint centerPoint;
@property (nonatomic, assign, readonly) CGFloat menuLength;
@property (nonatomic, assign, readonly) NSInteger numberOfCircleView;
@property (nonatomic, strong, readonly) UIView *centerCircleView;
@property (nonatomic, strong, readonly) UIView *circleMenuView;
  • 2.來看一下需要進(jìn)行哪些操作吧
    首先肯定是顯示和隱藏了,如果考慮得多一點(diǎn),我們可以在顯示或者隱藏之后做一個(gè)回調(diào)給使用則
    者.
    然后就是點(diǎn)擊的各種處理,在定義代理的時(shí)候,我們已經(jīng)仿照系統(tǒng)的TableView的Delegate寫了一個(gè)代理了.所以點(diǎn)擊操作可以直接通過代理去處理

簡(jiǎn)單一點(diǎn)來說初始化的話,我們就讓使用者把需要的參數(shù)都傳入進(jìn)來吧.最終設(shè)計(jì)出的方法如下:

- (instancetype)initFromPoint:(CGPoint)centerPoint
               withDataSource:(id<XLCircleMenuDataSource>)dataSource
                  andDelegate:(id<XLCircleMenuDelegate>)delegate;

- (void)showMenu;
- (void)showMenuWithCompletion:(void(^)()) completion;

- (void)closeMenu;
- (void)closeMenuWithCompletion:(void(^)()) completion;

到目前為止整個(gè)類的架子基本就打好了.

類的實(shí)現(xiàn)

現(xiàn)在該去具體實(shí)現(xiàn)我們的設(shè)計(jì)了
第一步定義屬于的私有屬性
第二步開始寫方法吧

  • 初始化方法
  • 子視圖的創(chuàng)建
  • 手勢(shì)添加
  • 實(shí)現(xiàn)動(dòng)畫

接下來把用到的主要技術(shù)和方式


拖拽的是實(shí)現(xiàn)

視圖的拖拽是通過UITapGestureRecognizer實(shí)現(xiàn)的這一章關(guān)于iOS手勢(shì)相關(guān)的介紹可以參考一下這篇文章:
iOS手勢(shì)識(shí)別

  1. 添加手勢(shì)到指定視圖,設(shè)置手勢(shì)代理,根據(jù)需要特殊處理
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(closeCircelMenu:)];
        [self addGestureRecognizer:tapGesture];
        tapGesture.delegate = self;

這里判斷如果點(diǎn)擊的是button,則不用接收了

#pragma mark - UIGestureRecognizerDelegate
-(BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch{
    BOOL should = YES;
    if([touch.view isKindOfClass:[UIButton class]]){
        should = NO;
    }
    return should;
}

下面是就是拖拽部分的代碼,用到的是transform(放射變換)
一旦移動(dòng),就改變視圖的frame

 if ((panGesture.state == UIGestureRecognizerStateChanged) || (panGesture.state == UIGestureRecognizerStateEnded)) {
        CGPoint translation = [panGesture translationInView:self];
       
        CGRect radialMenuRect = self.circleMenuView.frame;
        radialMenuRect.origin.x += translation.x;
        radialMenuRect.origin.y += translation.y;
        
        self.circleMenuView.frame = radialMenuRect;
        
        [self placeRadialMenuElementsAnimated:NO];
        
        [panGesture setTranslation:CGPointZero inView:self];
    }
移動(dòng).gif

調(diào)用代理的時(shí)間

一般在設(shè)計(jì)代理返回參數(shù)的時(shí)候都會(huì)設(shè)計(jì)一個(gè)屬性用來保存代理返回的參數(shù),比如:


    _menuLength = 50;
    if(self.dataSource && [self.dataSource respondsToSelector:@selector(lengthForCircleMenu:)]){
        _menuLength = [self.dataSource lengthForCircleMenu:self];
    }
    
    _numberOfCircleView = [self.dataSource numberOfCircleViewForCircleMenu:self];

這里就通過是否有代理來確定屬性的值,當(dāng)然如果代理是必須的就沒必要去判斷了(respondsToSelector),相當(dāng)于通過代理來給屬性賦值.
當(dāng)我們想傳遞事件給代理的時(shí)候,可以通過添加事件給子視圖,然后代理出去,如下:

  UIButton *element = [self.dataSource circleMenu:self circleViewAtIndex:i];
        
        if(self.maxW < element.frame.size.width) {
            self.maxW = element.frame.size.width;
        }else {
            
        }
        
        element.userInteractionEnabled = YES;
        element.alpha = 0;
        element.tag = i;
        
        [element addTarget:self
                    action:@selector(didTapButton:)
          forControlEvents:UIControlEventTouchUpInside];
        
        [self.elementsArray addObject:element];

在處理事件的時(shí)候調(diào)用代理

-(void)didTapButton:(UIButton *)sender {
    [self.delegate circleMenu:self didClickCircleView:sender];
}

布局和創(chuàng)建視圖分開

由于視圖的布局和拖動(dòng)的效果是相關(guān),所以布局和創(chuàng)建應(yīng)該獨(dú)立出來.其實(shí)我們實(shí)際開發(fā)中也應(yīng)該這樣做.在用frame布局的時(shí)候,我一般習(xí)慣把布局的操作放在layoutSubview里面,是的創(chuàng)建要不在初始化的時(shí)候創(chuàng)建完成,要不用懶加載額形式創(chuàng)建.

先來看看如果不把布局和手勢(shì)關(guān)聯(lián)是怎樣的效果.

僵硬的感覺.gif

看起來是不是特別的僵硬,下面就詳細(xì)講一講使用到的布局和動(dòng)畫

布局和動(dòng)畫

這種花瓣形的布局是當(dāng)時(shí)比較頭疼的,牽涉到了角度計(jì)算(asinf:逆正弦函數(shù),acosf:逆余弦函數(shù)),長(zhǎng)度百分比換成角度百分比
先看圖:

逆正弦函數(shù)
逆余弦函數(shù).png

當(dāng)時(shí)搞這個(gè)的時(shí)候,反正我是基本把這些東西還給了初中老師.

為了實(shí)現(xiàn)能夠當(dāng)菜單靠邊的時(shí)候,小圓能夠適應(yīng)自動(dòng)旋轉(zhuǎn)角度,我們需要考慮當(dāng)前邊緣是哪個(gè)方向.類似于:

具體思路:

  • 根據(jù)當(dāng)前菜單的x,y的正,負(fù)決定是在哪個(gè)方向上的邊緣.
  • 根據(jù)x,y負(fù)數(shù)的絕對(duì)值能夠知道當(dāng)前偏移了屏幕多少
  • 根據(jù)x,y偏移的程度改變整個(gè)可見的弧度,得到可變的弧度范圍
  • 遍歷小圓,改變各個(gè)小圓的中心點(diǎn)

上代碼吧:

 // 頂部邊緣
    if(self.circleMenuView.frame.origin.y < 0 &&
       self.circleMenuView.frame.origin.x > 0 &&
       CGRectGetMaxX(self.circleMenuView.frame) < self.frame.size.width){
        // 部分顯示
        fullCircle = NO;
        
        // 得到頂部偏移多少
        CGFloat d = -(self.circleMenuView.frame.origin.y +  self.menuLength);
        // 獲得起始角度的位置
        startingAngle = asinf((d + (self.maxW / 2.0) + 5) / (self.menuLength+radiusToAdd));
        // 獲取總共顯示的晚飯
        usableAngle = M_PI - (2 * startingAngle);
        
    }
    
    // 左邊
    if(self.circleMenuView.frame.origin.x < 0){
        fullCircle = NO;
        
        // 開始的角度
        if(self.circleMenuView.frame.origin.y > 0){
            CGFloat d = -(self.circleMenuView.frame.origin.x + self.menuLength);
            startingAngle = -acosf((d + 5) / (self.menuLength + radiusToAdd));
        } else {
            
            CGFloat d = -(self.circleMenuView.frame.origin.y + self.menuLength);
            startingAngle = asinf((d + self.maxW / 2.0+ 5) / (self.menuLength + radiusToAdd));
        }
        
        // 結(jié)束角度
        if(CGRectGetMaxY(self.circleMenuView.frame) <= self.frame.size.height){
            if(self.circleMenuView.frame.origin.y > 0){
                usableAngle = -2 * startingAngle;
            } else {
                CGFloat d = -(self.circleMenuView.frame.origin.x + self.menuLength);
                CGFloat virtualAngle = acosf((d + 5) / (self.menuLength + radiusToAdd));
                usableAngle = 2 * virtualAngle -(virtualAngle+startingAngle);
            }
        } else {
            CGFloat d = (CGRectGetMaxY(self.circleMenuView.frame) - self.frame.size.height -self.menuLength);
            CGFloat virtualAngle = -asinf((d + 5) / (self.menuLength + radiusToAdd));
            usableAngle = -startingAngle+virtualAngle;
        }
    }

底部和右邊的實(shí)現(xiàn)方法同頂部和左邊的思路是一樣的

最后開始布局各個(gè)小圓

for(int i = 0; i < [self.elementsArray count]; i++){
        UIButton *element = [self.elementsArray objectAtIndex:i];
        element.center = CGPointMake(self.circleMenuView.frame.size.width / 2.0, self.circleMenuView.frame.size.height / 2.0);
        double delayInSeconds = 0.025*i;
     
        void (^elementPositionBlock)(void) = ^{
            element.alpha = 1;
            [self.circleMenuView bringSubviewToFront:element];
            // 這一段比較復(fù)雜,參考的了別人寫的
            CGPoint endPoint = CGPointMake(self.circleMenuView.frame.size.width/2.0+(_menuLength+radiusToAdd)*(cos(startingAngle+usableAngle/(self.numberOfCircleView-(fullCircle ? 0 :1))*(float)i)), self.circleMenuView.frame.size.height/2.0+(_menuLength+radiusToAdd)*(sin(startingAngle+usableAngle/(self.numberOfCircleView-(fullCircle ? 0 :1))*(float)i)));
            
            element.center = endPoint;
        };
        
        if(animated) {
            dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
            dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// 延遲一下做動(dòng)畫的時(shí)間
                [UIView animateWithDuration:0.25 animations:elementPositionBlock];
            });
        } else {
            elementPositionBlock();
        };
    }

消失動(dòng)畫

消息動(dòng)畫比較簡(jiǎn)單,就是改變各個(gè)子視圖的center.和透明度,然后漸變消失.動(dòng)畫做完之后再里面移除視圖就可以了


for(int i = 0; i < [self.elementsArray count]; i++){
        UIButton *element = [self.elementsArray objectAtIndex:i];
        double delayInSeconds = 0.025*i;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            [UIView animateWithDuration:0.25 animations:^{
                element.alpha = 0;
                element.center = CGPointMake(self.centerCircleView.frame.size.width/2.0, self.centerCircleView.frame.size.height/2.0);
            }];
        });
    }
    
    double delayInSeconds = 0.25+0.025*[self.elementsArray count];
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        [UIView animateWithDuration:0.25 animations:^{
            self.centerCircleView.alpha = 0;
            self.alpha = 0;
        } completion:^(BOOL finished) {
            [self.centerCircleView removeFromSuperview];
            [self removeFromSuperview];
            
            if(completion) completion();
        }];
    });

參考項(xiàng)目:
AwesomeMenu

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末脐往,一起剝皮案震驚了整個(gè)濱河市甚淡,隨后出現(xiàn)的幾起案子敢艰,更是在濱河造成了極大的恐慌掏婶,老刑警劉巖呜笑,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宰闰,死亡現(xiàn)場(chǎng)離奇詭異存捺,居然都是意外死亡贼邓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門热鞍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葫慎,“玉大人,你說我怎么就攤上這事薇宠⊥蛋欤” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵澄港,是天一觀的道長(zhǎng)爽篷。 經(jīng)常有香客問我,道長(zhǎng)慢睡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任铡溪,我火速辦了婚禮漂辐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棕硫。我一直安慰自己髓涯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布哈扮。 她就那樣靜靜地躺著纬纪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滑肉。 梳的紋絲不亂的頭發(fā)上包各,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音靶庙,去河邊找鬼问畅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛六荒,可吹牛的內(nèi)容都是我干的护姆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼掏击,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼卵皂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起砚亭,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤灯变,失蹤者是張志新(化名)和其女友劉穎殴玛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柒凉,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡族阅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了膝捞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坦刀。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蔬咬,靈堂內(nèi)的尸體忽然破棺而出鲤遥,到底是詐尸還是另有隱情,我是刑警寧澤林艘,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布盖奈,位于F島的核電站,受9級(jí)特大地震影響狐援,放射性物質(zhì)發(fā)生泄漏钢坦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一啥酱、第九天 我趴在偏房一處隱蔽的房頂上張望爹凹。 院中可真熱鬧,春花似錦镶殷、人聲如沸禾酱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颤陶。三九已至,卻和暖如春陷遮,著一層夾襖步出監(jiān)牢的瞬間滓走,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工帽馋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闲坎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓茬斧,卻偏偏與公主長(zhǎng)得像腰懂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子项秉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,501評(píng)論 25 707
  • 翻譯自“Collection View Programming Guide for iOS” 0 關(guān)于iOS集合視...
    lakerszhy閱讀 3,827評(píng)論 1 22
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)绣溜、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,022評(píng)論 4 62
  • 前面說到矮人們不相信Tirian是納尼亞國(guó)王娄蔼,而且也不再相信阿斯蘭的存在怖喻,他們變得只相信自己底哗,這讓Tirian很沮...
    友邦保險(xiǎn)范麗娟Yvonne閱讀 341評(píng)論 0 0
  • 線程安全 1.線程安全的概念多條線程同時(shí)工作的情況下,通過運(yùn)用線程鎖,原子性等方法避免多條線程因?yàn)橥瑫r(shí)訪問同一快內(nèi)...
    IreneWu閱讀 10,449評(píng)論 9 22