Table Views(下)Grouped and Indexed Sections

在前面2篇關於Table View的介紹中,我們使用的Style都是Plain,沒有分組,沒有index,這次學習的Table View和iphone中的通訊錄很像,有一個個以字符爲分割的組,最右邊有一列小字符作爲index,最頂端有一個搜索欄可以進行搜索,好了,下面開始這次的學習。

1)創建一個新的項目,template選擇Single View Application,命名爲Sections

2)添加Table View,連接delegate和data source到File's Owner
選中BIDViewController.xib文件,從Object Library中拖一個Table View到view上,然後選中view中的table view,打開Connections Inspector,拖動dataSource和delegate右邊的小圓圈到File's Owner上

3)改變Table View style
還是選中view中的table view,然後選擇Attributes inspector,將Style改成“Grouped”

可以看到,table view的樣子發生了改變,展現的形式和iphone中的Setting有點類似,一塊一塊的,這樣的table view我們可以叫它Grouped table(之前2篇中所呈現的table view的樣式我們稱之爲Plained table,如果在Plained table的右邊加一列索引,我們就稱之爲Indexed table,indexed table是我們這張重點要學習的

3)導入數據
我們這篇所要學習的是數據的分組和索引的內容,因此我們需要大量的數據來做實驗,下載下面的文件,然後拖入到項目中,放置在Sections文件夾下
sortednames.plist.zip

選中sortednames.plist,在右邊可以看到文件的內容,文件的格式應該很熟悉了,我們已經多次使用過,sortednames.plist以26個英文字母爲key進行分組,各組中的單詞都是以該組的key開頭的。

4)寫代碼
打開BIDViewController.h,添加如下代碼

複製代碼
#import <UIKit/UIKit.h>

@interface BIDViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

@property (strong, nonatomic) NSDictionary *names;
@property (strong, nonatomic) NSArray *keys;

@end
複製代碼

NSDictionary用於存放sortednames.plist中的數據,用key來對應,NSArray用來存放key

打開BIDViewController.m,添加如下代碼

複製代碼
#import "BIDViewController.h"

@implementation BIDViewController
@synthesize names;
@synthesize keys;

......

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSString *path = [[NSBundle mainBundle] pathForResource:@"sortednames" ofType:@"plist"];
    NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
    self.names = dict;
    
    NSArray *array = [[names allKeys] sortedArrayUsingSelector:@selector(compare:)];
    self.keys = array;
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.names = nil;
    self.keys = nil;
}
複製代碼

上面的code現在讀起來應該很容易了,唯一可以稍微解釋一下的就是在viewDidLoad中,使用NSBundle來找到sortednames.plist文件,NSDictionary使用initWithContentsOfFile的方法來讀取數據。其他的應該不用解釋了。下面繼續在BIDViewController.m中添加代碼

複製代碼
#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [self.keys count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSString *key = [keys objectAtIndex:section];
    NSArray *nameSection = [names objectForKey:key];
    return [nameSection count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger section = [indexPath section];
    NSUInteger row = [indexPath row];
    
    NSString *key = [keys objectAtIndex:section];
    NSArray *nameSection = [names objectForKey:key];
    
    static NSString *SectionsTableIdentifier = @"SectionsTableIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SectionsTableIdentifier];
    if(cell == nil) {
        cell = [[UITableViewCell alloc]
                initWithStyle:UITableViewCellStyleDefault
                reuseIdentifier:SectionsTableIdentifier];
    }
    
    cell.textLabel.text = [nameSection objectAtIndex:row];
    return cell;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    NSString *key = [keys objectAtIndex:section];
    return key;
}
複製代碼

這裏多了2個新的方法,解釋如下:
numberOfSectionsInTableView:tableView,這個方法是返回table view中有多少個section(group)的,因爲之前的2篇中section只有一個,因此也就無需這個方法來返回section的數量了
tableView:titleForHeaderInSection,這個方法用來設置每個section的title的文字
其他的2個方法都很熟悉,一個用來返回每個section中row的數量,另一個用來創建UITableViewCell。
一般來說,table view都是分section的,很少有單一的section的情況,所以在這裏每次都要先確定在那個section中,然後在進行鍼對cell的操作,上面的code還是很直觀的,應該很容易理解。

5)編譯運行
編譯運行程序,效果如下

每個section一個group,每個section上都有自己的title。如果你將剛纔我們設置的table view的style改成Plain,在此運行,效果會變成一下的樣子


這個就是Plain和Grouped的區別

6)添加Index
上面雖然已經實現的table view,但是用起來還是非常的不方便,例如我們想尋找Z開頭的名字,我們需要花半天時間,從A一直滑動到Z,這樣子手是會抽筋的,因此,我們需要一個index,能夠幫我們進行快速定位(根據key值進行索引),在BIDViewController.m中,添加如下代碼

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return keys;
}

sectionIndexTitlesForTableView用於設置index,它返回index的集合,在這個例子中,我們的keys是26個英文字母,我們把這個作爲index,在index中,每一個字母和一個section對應,第1個字母對應第一個section,第2個字母對應第二個section,以此類推。是不是比想象的要簡單的多?編譯運行程序,table view的右邊出現一列index,選中index中的“Z”,我們可以快速定位到Z對應的section

現在我們可以很方便的定位到需要的section,然後在該section下尋找相關的name,但是還不是最最方便,很多情況下,我們需要一個搜索功能,搜索那些包含特定字母的name,這個可以更快的尋找到我們需要的name,下面我們就爲實現table view的搜素功能。

7)實現搜索欄(Search Bar)
我們先添加一些文件和代碼,爲等會添加search bar做一些準備,選中Project navigator中的Sections文件,然後command+N,添加一個新的文件。在彈出的對話框中,左邊選擇Cocoa Touch,右邊選擇Objective-C category,然後點擊Next

在下一個界面中設置Category和Category on如下,單擊Next

點擊Create,完成文件創建

如果你學過Objective-C,那麼你一定知道什麼是Category,如果你對Objective-C不熟悉,那你肯定會有疑問,爲什麼文件名有些詭異(文件名的中間有個加號),這個是Objective-C中獨有的一種特性、語法現象叫做“Category”,就是爲一個已有的類添加一個方法而不需要生成其子類,簡單的理解就是在特定的情況下,單獨爲已存在的類添加一個方法,從上面的名叫命名可以看出NSDictionary+MutableDeepCopy,NSDictionary是已有的類,MutableDeepCopy是爲已有的類添加方法,好吧,基本原理就是這些,如果還是有疑惑,那就google、百度一下吧,很快就會找到答案的。

打開NSDictionary+MutableDeepCopy.h,添加如下代碼

#import <Foundation/Foundation.h>

@interface NSDictionary (MutableDeepCopy)
- (NSMutableDictionary *)mutableDeepCopy;
@end

這裏爲NSDictionary添加了一個mutableDeepCopy的方法,再打開NSDictionary+MutableDeepCopy.m,添加如下代碼

複製代碼
@implementation NSDictionary (MutableDeepCopy)

- (NSMutableDictionary *)mutableDeepCopy {
    NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
    NSArray *keys = [self allKeys];
    for(id key in keys) {
        id oneValue = [self valueForKey:key];
        id oneCopy = nil;
        
        if([oneValue respondsToSelector:@selector(mutableDeepCopy)])
            oneCopy = [oneValue mutableDeepCopy];
        else if([oneValue respondsToSelector:@selector(mutableCopy)])
            oneCopy = [oneValue mutableCopy];
        if (oneCopy == nil)
            oneCopy = [oneValue copy];
        [returnDict setValue:oneCopy forKey:key];
            
    }
    return returnDict;
}

@end
複製代碼

這裏是對mutableDeepCopy方法進行實現,從字面意思就可以清楚的知道是對一個NSDictionary對象進行深拷貝(這個概念在C#中也有,是類似的東西),實現深拷貝的原因再後面會說到。

打開BIDViewController.h,添加如下代碼

複製代碼
#import <UIKit/UIKit.h>

@interface BIDViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate>

@property (strong, nonatomic) NSDictionary *names;
@property (strong, nonatomic) NSArray *keys;
@property (strong, nonatomic) NSMutableDictionary *names;
@property (strong, nonatomic) NSMutableArray *keys;
@property (strong, nonatomic) IBOutlet UITableView *table;
@property (strong, nonatomic) IBOutlet UISearchBar *search;
@property (strong, nonatomic) NSDictionary *allNames;


- (void)resetSearch;
- (void)handleSearchForTeam:(NSString *)searchTerm;

@end
複製代碼

引入UISearchBarDelegate,之後的searchbar需要這個協議來實現方法
刪除之前聲明的names和keys,替換成mutable(可修改的)的Dictionary和Array,因爲之後有對names和keys的刪除操作,NSDictionary和NSArray都是immutable(不可修改的),我們的搜索結果是比較所有的names,把不符合的names從dictionary中刪除,把不符合的key從array中刪除,所以dictionary和array必須是可修改的,因此我們聲明NSMutableDictionary和NSMutableArray
聲明2個outlet分別控制table view和searchbar
聲明一個NSDictionary,用於存放完整的names

8)添加Search Bar
選中BIDViewController.xib,在Object library中找到Search Bar,把Search Bar放在view的最頂端,searchbar會自動拉伸以適應屏幕的寬度,table view會自動空出頂部searchbar的位置

選中searchbar,打開attributes inspector,找到Options下的“Shows Cancel Button”,這樣一個Cancel按鈕會出現在search欄的右邊

ok,此時我們先build一下我們的project,當然會有很多警告出現,我們暫時無視,然後運行程序,一個bug出現了

最右邊的index和searchbar上的Cancel按鈕重疊了,好吧,我們必須解決這個問題,解決的方法可以說比較笨拙但也可以認爲比較巧妙,大致的方法是在searchbar下面放一個墊背的View控件,然後這個墊背的View控件上放2個其他的控件,一個是searchbar,另一個是navigation bar。View控件的作用有2個,一是佔據table view上的空間,當往table view上拖一個View控件時,table view會自動讓出頂部的hearder section;View控件的第二個作用是它可以容納其他的控件,可以在它上面放置searchbar和navigation bar(爲什麼要使用navigation bar,這個後面會解釋)。

好了,下面我們進行具體的操作,先將searchbar從table view上面刪除,然後在Object library中找到View控件

將View控件拖到table view的頂部,table view上會有一個藍色的小框框出現(table view的hearder section),將View放置在其中

再從Object library中拖一個Search Bar到View上,調整Search Bar的寬度到295(選中searchbar的情況下,打開size inspector,設置Width=295)

最後一步是拖一個navigation bar,在Object library中找到Navigation Bar

默認的Navigation Bar會充滿整個View控件

先雙擊navigation bar上的Title,然後將Title刪除,我們這裏不需要使用到文字內容,然後調整navigation bar的寬度,在dock中選中“Navigation Bar”

拖動左邊的點,使其的寬度縮小爲25px,這樣navigation bar的寬度正好填充了剛纔search bar空出的寬度,而且search bar從見天日了。

選中searchbar,打開attributes inspector,在Options下的選中“Shows Cancel Button”,在Placeholder中填入“Search”,再編譯運行一次,最終的效果如下

怎麼樣,很完美吧,完全看不出來是2個控件,這裏可以解釋一下選擇Navigation Bar的原因了,因爲Navigation Bar刪除title後,其背景顏色和Search Bar是一樣的,這樣我們就可以使2個控件看上去“合二爲一”,最終達到我們需要的效果。但是爲什麼不直接拖一個Navigation Bar呢?因爲Navigation Bar無法讓table view爲其留出頂部的header section,所有需要View控件來代勞。

9)連接
選中File's Owner,control-drag到table view,在彈出的框中選擇“table”

同樣的方法,control-drag到search bar,選擇“search”

這樣就將剛纔在BIDViewController.h中定義的2個outlet分類連接到了它們負責的對象上面。

選中search bar,打開connections inspector,將delegate拖動到File's Owner上,我們在BIDViewController.h中引入了UISearchBarDelegate,因此就讓這個search bar到BIDViewController中去尋找方法。

10)寫代碼
打開BIDViewController.m,添加如下代碼

複製代碼
#import "BIDViewController.h"
#import "NSDictionary+MutableDeepCopy.h"

@implementation BIDViewController
@synthesize names;
@synthesize keys;
@synthesize table;
@synthesize search;
@synthesize allNames;

#pragma mark -
#pragma mark Custom Methods
- (void)resetSearch {
    self.names = [self.allNames mutableDeepCopy];
    NSMutableArray *keyArray = [[NSMutableArray alloc] init];
    [keyArray addObjectsFromArray:[[self.allNames allKeys] sortedArrayUsingSelector:@selector(compare:)]];
    self.keys = keyArray;
}

- (void)handleSearchForTeam:(NSString *)searchTerm {
    
    NSMutableArray *sectionsToRemove = [[NSMutableArray alloc] init];
    [self resetSearch];
    
    for (NSString *key in self.keys) {
        NSMutableArray *array = [names valueForKey:key];
        NSMutableArray *toRemove = [[NSMutableArray alloc] init];
        for (NSString *name in array) {
            if ([name rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location == NSNotFound) {
                [toRemove addObject:name];
            }
        }
        if ([array count] == [toRemove count]) {
            [sectionsToRemove addObject:key];
        }
        
        [array removeObjectsInArray:toRemove];
    }
    [self.keys removeObjectsInArray:sectionsToRemove];
    [table reloadData];
}
複製代碼

引入NSDictionary+MutableDeepCopy.h,就可以調用我們定義的mutableDeepCopy方法了

resetSearch:是一個SearchBar的delegate,用於初始化搜索內容,將完整的names和keys複製到NSMutableDictionary和NSMutableArray中,之後的搜索就搜索這2個對象中的內容,可以對他們進行刪除操作。這個方法會在點擊searchbar上的Cancel按鈕和當搜索的關鍵詞發生改變時進行調用。

handleSearchForTeam:searchTerm:SearchBar的另一個delegate,根據搜索詞searchTerm來進行搜索

NSMutableArray *sectionsToRemove = [[NSMutableArray alloc] init]; //聲明一個NSMutableArray,用於記錄哪些section可以整個的被刪除,即某個index item下的所有name都不符合搜索的要求,這個index item就可以被刪除了
[self resetSearch]; //初始化搜索對象,一個是NSMutableDictionary,另一個是NSMutableArray
for (NSString *key in self.keys) { //循環遍歷所有的key
    NSMutableArray *array = [names valueForKey:key]; //根據key取得對應的name
    NSMutableArray *toRemove = [[NSMutableArray alloc] init]; //初始化一個NSMutableArray,用於記錄可刪除的name
    for (NSString *name in array) { //循環遍歷當前key下的name
        if ([name rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location == NSNotFound) { //如果當前的name中不包含搜索詞searchTerm,那這個name是可以被刪除的
            [toRemove addObject:name]; //將name添加到toRemove array中
        }
    }
    if ([array count] == [toRemove count]) { //判斷是不是可以刪除的name個數和當前section(index item)下的name個數一樣,一樣的話,真個的section就可以被刪除了
        [sectionsToRemove addObject:key]; //將可以刪除的section添加到array中
    }
    [array removeObjectsInArray:toRemove]; //刪除name
}
[self.keys removeObjectsInArray:sectionsToRemove]; //刪除section
[table reloadData]; //重新載入數據

下面繼續添加code

複製代碼
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSString *path = [[NSBundle mainBundle] pathForResource:@"sortednames" ofType:@"plist"];
    NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
    self.names = dict;
    self.allNames = dict;
    
    NSArray *array = [[names allKeys] sortedArrayUsingSelector:@selector(compare:)];
    self.keys = array;
    
    [self resetSearch];
    [table reloadData];
    [table setContentOffset:CGPointMake(0.0, 44.0) animated:NO];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.names = nil;
    self.keys = nil;
    self.table = nil;
    self.search = nil;
    self.allNames = nil;
}
複製代碼

這裏需要解釋的就是[table setContentOffset:CGPointMake(0.0, 44.0) animated:NO];,將table view上移動44px,這樣當初次載入程序的時候,搜索欄不會顯示,只用向下拖動table view,searchbar纔會出現。

繼續添加code

複製代碼
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [self.keys count];
    return ([keys count] > 0) ? [keys count] : 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if([keys count] == 0)
        return 0;
    
    NSString *key = [keys objectAtIndex:section];
    NSArray *nameSection = [names objectForKey:key];
    return [nameSection count];
}

......

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if([keys count] == 0)
        return nil;
    
    NSString *key = [keys objectAtIndex:section];
    return key;
}
複製代碼

增加的幾個if語句都是用於判斷search結果的,調用完handleSearchForTeam:searchTerm後,看還有多少個key和name

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [search resignFirstResponder];
    return indexPath;
}

table view的delegate方法,當點擊table view上的一行是,將彈出的鍵盤隱藏

複製代碼
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    NSString *searchTeam = [searchBar text];
    [self handleSearchForTeam:searchTeam];
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchTerm {
    if ([searchTerm length] == 0) {
        [self resetSearch];
        [table reloadData];
        return;
    }
    [self handleSearchForTeam:searchTerm];
}

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
    search.text = @"";
    [self resetSearch];
    [table reloadData];
    [searchBar resignFirstResponder];
}
複製代碼

三個Search Bar的delegate方法
searchBarSearchButtonClicked:searchBar當點擊虛擬鍵盤上的search按鈕時,調用該方法
searchBar:textDidChange:當searchBar上的輸入發生改變時,調用該方法
searchBarCancelButtonClicked:searchBar當點擊searchbar上的Cancel按鈕時,調用該方法

好了,所有的code添加完畢,編譯運行,效果如下

當程序啓動後,searchbar是看不見的,因爲我們將table view上移了44px,將table view向下拖動後,searchbar出現了

點擊search bar的輸入欄,調出鍵盤,然後我們輸入“ab”,table view總的內容會隨之改變,只顯示含有關鍵詞“ab”的name,index也會改變

點擊table view中的一行,鍵盤會隱藏

11)隱藏index
如果我們希望在搜索的時候隱藏index,添加下面的code

在BIDViewController.h中添加一個property

複製代碼
@interface BIDViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate>

@property (strong, nonatomic) NSMutableDictionary *names;
@property (strong, nonatomic) NSMutableArray *keys;
@property (strong, nonatomic) IBOutlet UITableView *table;
@property (strong, nonatomic) IBOutlet UISearchBar *search;
@property (strong, nonatomic) NSDictionary *allNames;
@property (assign, nonatomic) BOOL isSearching;
複製代碼

注意,這裏用的是assign(不好意思,我還沒有弄清楚爲什麼要用assign,因此無法解釋,瞭解的同學們幫忙指導一下,謝謝)

在BIDViewController.m中添加code

複製代碼
#import "BIDViewController.h"
#import "NSDictionary+MutableDeepCopy.h"

@implementation BIDViewController
@synthesize names;
@synthesize keys;
@synthesize table;
@synthesize search;
@synthesize allNames;
@synthesize isSearching;

......

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    if(isSearching)
        return nil;
    return keys;
}

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [search resignFirstResponder];
    isSearching = NO;
    search.text = @"";
    [table reloadData];
    return indexPath;
}

......

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
    isSearching = NO;
    search.text = @"";
    [self resetSearch];
    [table reloadData];
    [searchBar resignFirstResponder];
}

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
    isSearching = YES;
    [table reloadData];
}
複製代碼

添加了一個新的Search Bar的delegate:searchBarTextDidBeginEditing,當開始鍵入搜索詞的時候,設置標識符isSearching爲YES,然後重新載入table,當重新載入table的時候,就會判斷isSearching是否爲YES,如果是,就不顯示index。其他的代碼修改應該都可以看懂吧,就不解釋了,編譯運行程序,點擊搜索欄,index消失了,效果如下

當結束搜索(點擊search bar的Cancel按鈕或者選中table view中的一行),index就會顯示出來

12)添加index中的放大鏡
現在的index定位,只能定位到某個section,如果想直接定位到search bar還是不行的,我們需要添加額外的代碼,使其可以定位到search bar,基本思路如下:
添加一個特殊的值到keys中,然後點擊這個特別的key,就可以定位到search bar
阻止iOS爲這個特殊的key生成一個section hearder,其他的keys(A、B、C.....Z)每個都一個section header
當這個特殊的key被選中時,定位到search bar

複製代碼
- (void)resetSearch {
    self.names = [self.allNames mutableDeepCopy];
    NSMutableArray *keyArray = [[NSMutableArray alloc] init];
    [keyArray addObject:UITableViewIndexSearch];
    [keyArray addObjectsFromArray:[[self.allNames allKeys] sortedArrayUsingSelector:@selector(compare:)]];
    self.keys = keyArray;
}

......

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if([keys count] == 0)
        return nil;
    
    NSString *key = [keys objectAtIndex:section];
    if (key == UITableViewIndexSearch) {
        return nil;
    }
    return key;
}

......

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    NSString *key = [keys objectAtIndex:index];
    if(key == UITableViewIndexSearch) {
        [tableView setContentOffset:CGPointZero animated:NO];
        return NSNotFound;
    }
    else
        return index;
}
複製代碼

resetSearch方法中爲key添加了一個iOS自帶的對象[keyArray addObject:UITableViewIndexSearch],這個就是放大鏡。
titleForHeaderInSection中,判斷key是不是UITableViewIndexSearch,如果是,就返回nil,不顯示section header
最後添加了一個新的delegate,用於定位到search bar

編譯運行,放大鏡出現了
點擊放大鏡,就可以定位到search bar了

13)總結
終於要完結這篇了,花了好長時間,內容比較多,比較複雜,不過很實用,很多app程序都用到這些基礎的東西,其實也不難,很多方法都是iOS自帶的,我們只需要自己調用一下即可,總的來說,收穫還是很大的,加油吧!

Sections

發佈了19 篇原創文章 · 獲贊 17 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章