iOS CollectionView 的那些事

UICollectionView是開發(fā)中用的比較多的一個控件,本文記錄UICollectionView在開發(fā)中常用的方法總結(jié),包括使用UICollectionViewFlowLayout實現(xiàn)Grid布局、添加Header/Footer、自定義layout布局、UICollectionView的其它方面比如添加Cell的點擊效果等等

本文Demo: CollectionViewDemo

UICollectionView重要的概念

UICollectionView中有幾個重要的概念,理解這幾個重要的概念對于使用UICollectionView有很大的幫助,這個幾個概念從用戶的數(shù)據(jù)、布局展示的數(shù)據(jù)、視圖展示的View、UICollectionView充當(dāng)?shù)慕巧?/strong>這幾個維度來展開講解,這部分講解的是偏概念的東西,如果你是一個實用主義者,那么可以直接跳到下一部分“UICollectionView和UICollectionViewFlowLayout”查看UICollectionView的簡單實用,然后再回過頭來回顧下這些概念,這樣也是一個比較好的方式

用戶的數(shù)據(jù)

用戶的數(shù)據(jù)是UICollectionView中的DataSource,DataSource告訴UICollectionView有幾個section、每個section中有幾個元素需要展示,這點和UITableView中的DataSource是類似的

布局展示的數(shù)據(jù)

布局展示的數(shù)據(jù)是UICollectionView中的Layout,Layout告訴UICollectionView每個section中元素展示的大小和位置,每個元素展示的位置大小信息是保存在一個UICollectionViewLayoutAttributes類的對象中,Layout對象會管理一個數(shù)組包含了多個UICollectionViewLayoutAttributes的對象。Layout對應(yīng)的具體類是UICollectionViewLayoutUICollectionViewFlowLayout,UICollectionViewFlowLayout可以直接使用,最簡單的通過設(shè)置每個元素的大小就可以實現(xiàn)Grid布局。如果需要更多了定制設(shè)置其他屬性比如minimumLineSpacing、minimumInteritemSpacing來設(shè)置元素之間的間距。

視圖展示的View

DataSource中每個數(shù)據(jù)展示需要使用到的是UICollectionViewCell類對象,一般的通過創(chuàng)建UICollectionViewCell的子類,添加需要的UI元素進行自定義的布局。可以使用registerClass:forCellReuseIdentifier:方法或者registerNib:forCellReuseIdentifier:方法注冊,然后在UICollectionView的DataSource方法collectionView: cellForItemAtIndexPath:中使用方法dequeueReusableCellWithIdentifier:獲取到前面注冊的Cell,使用item設(shè)置急需要展示的數(shù)據(jù)。

另外如果有特殊的Header/Footer需求,需要使用到的是UICollectionReusableView類,一般也是通過創(chuàng)建子類進行設(shè)置自定義的UI??梢允褂?code>registerClass:forSupplementaryViewOfKind:withReuseIdentifier:方法或者registerNib:forSupplementaryViewOfKind:withReuseIdentifier:方法注冊,然后在UICollectionView的DataSource方法collectionView: viewForSupplementaryElementOfKind: atIndexPath:中使用方法dequeueReusableSupplementaryViewOfKind: withReuseIdentifier: forIndexPath:獲取到前面注冊的reusableView,然后設(shè)置需要展示的數(shù)據(jù)。

UICollectionView充當(dāng)?shù)慕巧?/h4>

UICollectionView在這里面充當(dāng)?shù)慕巧且粋€容器類,是一個中間者,他用于連接DataSource、Layout、UI之間的關(guān)系,起到一個協(xié)調(diào)的作用,CollectionView的角色可以使用下面的這張圖來標(biāo)識。

UICollectionView充當(dāng)?shù)慕巧?/div>

UICollectionView和UICollectionViewFlowLayout

UICollectionView已經(jīng)為我們準(zhǔn)備好了一個開箱即用的Layout類,就是UICollectionViewFlowLayout,使用UICollectionViewFlowLayout可以實現(xiàn)經(jīng)常使用到的Grid表格布局,下面了解下UICollectionViewFlowLayout中常用的幾個屬性的意思以及如何使用和定制UICollectionViewFlowLayout。

UICollectionViewFlowLayout頭文件中定義的屬性如下:

@property (nonatomic) CGFloat minimumLineSpacing;
@property (nonatomic) CGFloat minimumInteritemSpacing;
@property (nonatomic) CGSize itemSize;
@property (nonatomic) UICollectionViewScrollDirection scrollDirection;
@property (nonatomic) UIEdgeInsets sectionInset;
  • minimumLineSpacing 如果itemSize的大小是一樣的,那么真實的LineSpacing就是minimumLineSpacing,如果高度不一樣,那么這個值回事上一行中Y軸值最大者和當(dāng)前行中Y軸值最小者之間得高度,行中其它元素的LineSpacing會大于minimumLineSpacing

    minimumLineSpacing

  • minimumInteritemSpacing 如下圖所示,定義的是元素水平之間的間距,這個間距會大于等于我們設(shè)置的值,因為有可能有可能一行容納不下只能容納下N個元素,還有M個單位的空間,這些剩余的空間會被平局分配到元素的間距,那么真實的IteritemSpacing值其實是(minimumInteritemSpacing + M / (N - 1))

    minimumInteritemSpacing

  • itemSize itemSize表示的是Cell的大小

  • scrollDirection 如下圖所示,表示UICollectionView的滾動方向,可以設(shè)置垂直方向UICollectionViewScrollDirectionVertical和水平方向UICollectionViewScrollDirectionHorizontal

    scrollDirection

  • sectionInset 定義的是Cell區(qū)域相對于UICollectionView區(qū)域的上下左右之間的內(nèi)邊距,如下圖所示

    sectionInset

在了解了UICollectionViewFlowLayout的一些概念之后,我們實現(xiàn)一個如下的表格布局效果

表格布局效果

1. UICollectionViewFlowLayout初始化和UICollectionView的初始化

首先使用UICollectionViewFlowLayout對象初始化UICollectionView對象,UICollectionViewFlowLayout對象設(shè)置item元素顯示的大小,滾動方向,內(nèi)邊距,行間距,元素間距,使得一行剛好顯示兩個元素,并且元素內(nèi)邊距為5,元素的間距為10,行間距為20,也就是上圖的效果。 這邊還有一個重要的操作是使用registerClass:forCellWithReuseIdentifier:方法注冊Cell,以備后面的使用。

- (UICollectionView *)collectionView {
    if (_collectionView == nil) {
        
        UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
        CGFloat itemW = (SCREEN_WIDTH - 20) / 2;
        CGFloat itemH = itemW * 256 / 180;
        layout.itemSize = CGSizeMake(itemW, itemH);
        layout.sectionInset = UIEdgeInsetsMake(5, 5, 5, 5);
        layout.scrollDirection = UICollectionViewScrollDirectionVertical;
        layout.minimumLineSpacing = 20;
        layout.minimumInteritemSpacing = 10;
        
        _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
        _collectionView.backgroundColor = [UIColor whiteColor];
        _collectionView.delegate = self;
        _collectionView.dataSource = self;
        [_collectionView registerClass:[TTQVideoListCell class] forCellWithReuseIdentifier:@"TTQVideoListCell"];
    }
    return _collectionView;
}

2. UICollectionViewDataSource處理

  • 重寫collectionView: numberOfItemsInSection:返回元素個數(shù)
  • 重寫collectionView: cellForItemAtIndexPath:,使用dequeueReusableCellWithReuseIdentifier:獲取重用的Cell,設(shè)置Cell的數(shù)據(jù),返回Cell
  • 重寫collectionView: didSelectItemAtIndexPath:,處理Cell的點擊事件,這一步是非必須的,但是絕大多數(shù)場景是需要交互的,點擊Cell需要執(zhí)行一些處理,所以這里也添加上這個方法,在這里做一個取消選擇狀態(tài)的處理
// MARK: - UICollectionViewDataSource

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.dataSource.count;
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    TTQVideoListCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TTQVideoListCell" forIndexPath:indexPath];
    TTQVideoListItemModel *data = self.dataSource[indexPath.item];
    [cell setupData:data];
    return cell;
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    TTQVideoListItemModel *data = self.dataSource[indexPath.item];
    [collectionView deselectItemAtIndexPath:indexPath animated:YES];
    // FIXME: ZYT 處理跳轉(zhuǎn)
}

3.數(shù)據(jù)源
數(shù)據(jù)源是一個簡單的一維數(shù)組,如下

- (NSMutableArray *)dataSource {
    if (!_dataSource) {
        _dataSource = [NSMutableArray array];
        
        // FIXME: ZYT TEST
        for (int i = 0; i < 10; i++) {
            TTQVideoListItemModel *data = [TTQVideoListItemModel new];
            data.images = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1534329621698&di=60249b63257061ddc1f922bf55dfa0f4&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fimgad%2Fpic%2Fitem%2Fd009b3de9c82d158e0bd1d998b0a19d8bc3e42de.jpg";
            [_dataSource addObject:data];
        }
    }
    return _dataSource;
}

4.Cell實現(xiàn)
在這個演示項目中,Cell是通過代碼的方式繼承UICollectionViewCell實現(xiàn)的

頭文件:

@interface TTQVideoListCell : UICollectionViewCell

- (void)setupData:(TTQVideoListItemModel *)data;

@end

實現(xiàn)文件:

@interface TTQVideoListCell()
@property (nonatomic, strong) UIImageView *coverImageView;
@property (nonatomic, strong) UIView *titleLabelBgView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *playCountLabel;
@property (nonatomic, strong) UILabel *praiseCountLabel;
@property (nonatomic, strong) UILabel *statusLabel;
@property (nonatomic, strong) UILabel *tagLabel;
@property (nonatomic, strong) TTQVerticalGradientView *bottomGradientView;
@property (nonatomic, strong) TTQVerticalGradientView *topGradientView;
@property (strong, nonatomic) UIView *highlightView;
@end

@implementation TTQVideoListCell

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setupUI];
    }
    return self;
}

- (void)setHighlighted:(BOOL)highlighted {
    [super setHighlighted:highlighted];
    if (highlighted) {
        self.highlightView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
    } else {
        self.highlightView.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
    }
}

- (void)setupUI {
    self.contentView.layer.cornerRadius = 4;
    self.contentView.layer.masksToBounds = YES;
    [self.contentView addSubview:self.coverImageView];
    [self.contentView addSubview:self.topGradientView];
    [self.contentView addSubview:self.bottomGradientView];
    [self.contentView addSubview:self.titleLabelBgView];
    [self.titleLabelBgView addSubview:self.titleLabel];
    [self.contentView addSubview:self.playCountLabel];
    [self.contentView addSubview:self.praiseCountLabel];
    [self.contentView addSubview:self.statusLabel];
    [self addSubview:self.tagLabel];
    [self addSubview:self.highlightView];
    // 布局省略了,具體可以查看git倉庫中的代碼
}

- (void)setupData:(TTQVideoListItemModel *)data {
   
    self.titleLabel.text = data.title;
    self.playCountLabel.text = @"播放次數(shù)";
    self.praiseCountLabel.text = @"點贊次數(shù)";
    [self.coverImageView sd_setImageWithURL:[NSURL URLWithString:data.images]];
    
    if (data.status == TTQVideoItemStatusReviewRecommend) {
        self.tagLabel.hidden = NO;
        self.statusLabel.hidden = YES;
        self.tagLabel.text = data.status_desc;
    } else {
        self.tagLabel.hidden = YES;
        self.statusLabel.hidden = NO;
        self.statusLabel.text = data.status_desc;
    }
}

只要以上幾個步驟,我們就能實現(xiàn)一個Grid的表格布局了,如果有其它的Header/Footer的需求,其實也只要增加三個小步驟就可以實現(xiàn),下面就來實現(xiàn)一個帶有Header/Footer效果的CollectionView

UICollectionViewFlowLayout的Header和Footer

UICollectionView中的Header和Footer也是會經(jīng)常使用到的,下面通過三個步驟來實現(xiàn),這三個步驟其實和Cell的步驟是相似的,所以十分簡單

Header和Footer

**1.注冊Header/Footer **
使用registerClass:forSupplementaryViewOfKind:withReuseIdentifier:方法或者registerNib:forSupplementaryViewOfKind:withReuseIdentifier:方法注冊

        [_collectionView registerClass:SimpleCollectionHeaderView.class forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"SimpleCollectionHeaderView"];
        [_collectionView registerClass:SimpleCollectionFooterView.class forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"SimpleCollectionFooterView"];

**2.獲取Header/Footer **

  • 重寫collectionView: layout: referenceSizeForHeaderInSection:返回header的高度
  • 重寫collectionView: layout: referenceSizeForFooterInSection:返回footer的高度
  • 重寫collectionView: viewForSupplementaryElementOfKind: atIndexPath:方法,使用方法dequeueReusableSupplementaryViewOfKind: withReuseIdentifier: forIndexPath:獲取到前面注冊的reusableView,然后設(shè)置需要展示的數(shù)據(jù)。該方法中的kind參數(shù)可以使用UICollectionElementKindSectionHeaderUICollectionElementKindSectionFooter兩個常量來判斷是footer還是header
// MARK: 處理Header/Footer

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
    return CGSizeMake(SCREEN_WIDTH, 40);
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {
    return CGSizeMake(SCREEN_WIDTH, 24);
}

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    UICollectionReusableView *supplementaryView = nil;
    SectionDataModel *sectionData = self.dataSource[indexPath.section];
    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
        SimpleCollectionHeaderView* header = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"SimpleCollectionHeaderView" forIndexPath:indexPath];
        header.descLabel.text = sectionData.title;
        supplementaryView = header;
    } else {
        SimpleCollectionFooterView* footer = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"SimpleCollectionFooterView" forIndexPath:indexPath];
        footer.descLabel.text = [NSString stringWithFormat:@"%@條數(shù)據(jù)", @(sectionData.items.count)];
        supplementaryView = footer;
    }
    return supplementaryView;
}

3.Header/Footer類實現(xiàn) **
繼承UICollectionReusableView類,然后進行自定義的UI布局即可,下面實現(xiàn)一個簡單的Header,只有一個Label顯示分類的標(biāo)題,
注意需要使用UICollectionReusableView子類,才能利用CollectionView中的重用機制**
頭文件

@interface SimpleCollectionHeaderView : UICollectionReusableView

@property (nonatomic, strong) UILabel *descLabel;

@end

實現(xiàn)文件

@implementation SimpleCollectionHeaderView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self addSubview:self.descLabel];
        self.backgroundColor = [UIColor colorWithWhite:0.95 alpha:0.6];;
    }
    return self;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    self.descLabel.frame = CGRectMake(15, 0, self.bounds.size.width - 30, self.bounds.size.height);
}

- (UILabel *)descLabel {
    if (!_descLabel) {
        _descLabel = [UILabel new];
        _descLabel.font = [UIFont systemFontOfSize:18];
        _descLabel.textColor = [UIColor colorWithWhite:0.7 alpha:1];
    }
    return _descLabel;
}
@end

自定義Layout

自定義Layout為CollectionView的布局提供了最大的靈活性,使用自定義的Layout可以實現(xiàn)復(fù)雜的布局視圖,下面會通過一個簡單的例子來了解下自定義Layout,更加深入的內(nèi)容可以查看ClassHierarchicalTree這個開源項目的代碼進行學(xué)習(xí),Demo項目中自定義布局實現(xiàn)的效果如下:

自定義Layout效果

自定義Layout需要經(jīng)過以下的幾個步驟

  • 預(yù)處理,該步驟是可選的,為了提高性能可以在這個方法中做預(yù)處理
  • 提供ContentSize
  • 提供LayoutAttributes,是一個數(shù)組,表示的是在UICollectionView可見范圍內(nèi)的item顯示的Cell的布局參數(shù)
  • 提供單獨的Attributes,與IndexPath相關(guān)的的布局參數(shù)

作為一個最簡單的實踐,本文不做預(yù)處理,所以步驟只有后面三個,接下來逐個的展開來說
下面的代碼中會使用到下面的幾個宏定義的值得意思說明如下:

/**
 Cell外邊距
 */
#define VideoListCellMargin 5
/**
 Cell寬度
 */
#define VideoListCellWidth ((SCREEN_WIDTH - VideoListCellMargin * 3) / 2)
/**
 Cell高度
 */
#define VideoListCellHeight (VideoListCellWidth * 265 / 180)

下面的代碼中會使用到headerHeight表示的是頭部視圖的高度,datas表示的是數(shù)據(jù)源

@interface TTQVideoListLayout : UICollectionViewLayout

@property (nonatomic, strong) NSArray<TTQVideoListItemModel *> *datas;
/**
 頭部視圖的高度
 */
@property (nonatomic, assign) CGFloat headerHeight;

@end

提供ContentSize

ContentSize的概念和ScrollView中contentSize的概念類似,表示的是所有內(nèi)容占用的大小,下面的代碼會根據(jù)DataSource數(shù)組的大小和headerHeight的值計算最終需要顯示的大小

- (CGSize)collectionViewContentSize {
    return CGSizeMake(SCREEN_WIDTH, ceil((CGFloat)self.datas.count / (CGFloat)2) * (VideoListCellHeight + VideoListCellMargin) + self.headerHeight + VideoListCellMargin);
}

提供LayoutAttributes

返回值是一個數(shù)組,表示的是在UICollectionView可見范圍內(nèi)的item顯示的Cell的布局參數(shù),如下圖的Visible rect標(biāo)識的位置中所有元素的布局屬性

Visible rect

實現(xiàn)的方式很簡單,通過對全部內(nèi)容的布局屬性的遍歷,判斷是否和顯示區(qū)域的rect有交集,如果有交集,就把該布局屬性對象添加到數(shù)組中,最后返回這個數(shù)組。

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSMutableArray *array = [[NSMutableArray alloc] init];
    for (NSInteger i = 0; i < self.datas.count; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
        if (!CGRectEqualToRect(attributes.frame, CGRectZero)) {
            if (CGRectIntersectsRect(rect, attributes.frame)) {
                [array addObject:attributes];
            }
        }
    }
    return array;
}

提供單獨的Attributes

這個方法用于返回和單獨的IndexPath相關(guān)的布局屬性對象,根據(jù)indexPath中的row參數(shù)可以知道元素的位置,然后可以計算出相應(yīng)所在的位置大小,然后初始化一個UICollectionViewLayoutAttributes對象,設(shè)置參數(shù)值,返回UICollectionViewLayoutAttributes對象即可

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    if (indexPath.row < self.datas.count) {
        id item = self.datas[indexPath.row];
        if ([item isKindOfClass:[TTQVideoListItemModel class]]) {
            CGFloat originX = (indexPath.row % 2 == 0) ? (VideoListCellMargin) : (VideoListCellMargin * 2 + VideoListCellWidth);
            CGFloat originY = indexPath.row/ 2 * (VideoListCellMargin + VideoListCellHeight) + VideoListCellMargin + self.headerHeight;
            attributes.frame = CGRectMake(originX, originY, VideoListCellWidth, VideoListCellHeight);
        } else {
            attributes.frame = CGRectZero;
        }
    } else {
        attributes.frame = CGRectZero;
    }
    return attributes;
}

其它

Cell點擊效果是很經(jīng)常使用到的,這邊主要講下兩種Cell點擊效果的實現(xiàn)方式

Cell點擊效果

有兩種方法可以實現(xiàn)CollectionViewCell的點擊效果,一種是設(shè)置CollectionViewCell的屬性selectedBackgroundViewbackgroundView;另一種是重寫setHighlighted方法設(shè)置自定義的背景View的高亮狀態(tài)

設(shè)置selectedBackgroundView和backgroundView

下圖中的左邊是點擊效果,右邊是普通的狀態(tài)

selectedBackgroundView和backgroundView

    UIView *selectedBackgroundView = [UIView new];
    selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
    self.selectedBackgroundView = selectedBackgroundView;
    
    UIView *backgroundView = [UIView new];
    backgroundView.backgroundColor = [UIColor clearColor];
    self.backgroundView = backgroundView;


這種方式有一個局限性,如下圖所示,設(shè)置的selectedBackgroundViewbackgroundView是位于Cell的最底層,如果上面有自定義的圖層會覆蓋住selectedBackgroundViewbackgroundView,比如Cell中設(shè)置了一個充滿Cell視圖的ImageView,點擊的效果將會不可見。

`selectedBackgroundView`和`backgroundView`是位于Cell的最底層

重寫setHighlighted方法

重寫setHighlighted方法相對來說是一種靈活性比較高的方法,這種方式和自定義UITableViewCell的高亮狀態(tài)很類似,setHighlighted方法中通過判斷不同的狀態(tài)進行設(shè)置任意的UI元素的樣式,我們可以在Cell的最上層添加一個自定義的高亮狀態(tài)的View,這樣高亮的效果就不會因為充滿Cell的UI而導(dǎo)致看不見了,代碼如下

- (void)setupUI {
    // ......
    [self addSubview:self.highlightView];
    [self.highlightView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self);
    }];
}

- (UIView *)highlightView {
    if (!_highlightView) {
        _highlightView = [UIView new];
        _highlightView.backgroundColor = [UIColor clearColor];
        _highlightView.layer.cornerRadius = 3;
    }
    return _highlightView;
}

- (void)setHighlighted:(BOOL)highlighted {
    [super setHighlighted:highlighted];
    if (highlighted) {
        self.highlightView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
    } else {
        self.highlightView.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
    }
}

效果如下圖:


setHighlighted效果

參考

Collection View Programming Guide for iOS
自定義 Collection View 布局

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容