Flutter学习第十四天:Flutter照片墙的完整Demo,让你的界面更加美观灵活?


这个功能是主要是学习flutter视频的那个老师教给我的,感觉效果不错,就来分享记录一下,让我下次使用起来方便一点,废话少说,先看一下功能吧。

在这里插入图片描述

Demo使用的主要技术:
后端:json数据
前端:
1.TabBar+TabBarView实现Tab切换
2.使用flutter_staggered_grid_view插件实现照片墙功能
3.使用Card组件让你的界面更加美观。

代码结构如下:
在这里插入图片描述

1.把json数据转化为dart(model层)

travel_model.dart页面
这个页面主要是用来获取照片墙显示的数据,这里我就不细说,因为我主要说照片墙的实现,而且代码比较多,如果你用工具的话,就毕竟简单了,只需要把json数据粘贴进去就可以转化为dart了。json转dart工具
如果你想了解json数据是什么内容,可以看这个地址。https://www.devio.org/io/flutter_app/json/searchTrip/searchTrip_%E6%8E%A8%E8%8D%90.json。

///旅拍页模型
class TravelItemModel {
    
    
  int totalCount;
  List<TravelItem> resultList;

  TravelItemModel({
    
    this.totalCount, this.resultList});

  TravelItemModel.fromJson(Map<String, dynamic> json) {
    
    
    totalCount = json['totalCount'];
    if (json['resultList'] != null) {
    
    
      resultList = new List<TravelItem>();
      json['resultList'].forEach((v) {
    
    
        resultList.add(new TravelItem.fromJson(v));
      });
    }
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['totalCount'] = this.totalCount;
    if (this.resultList != null) {
    
    
      data['resultList'] = this.resultList.map((v) => v.toJson()).toList();
    }
    return data;
  }
}

class TravelItem {
    
    
  int type;
  Article article;

  TravelItem({
    
    this.type, this.article});

  TravelItem.fromJson(Map<String, dynamic> json) {
    
    
    type = json['type'];
    article =
    json['article'] != null ? new Article.fromJson(json['article']) : null;
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['type'] = this.type;
    if (this.article != null) {
    
    
      data['article'] = this.article.toJson();
    }
    return data;
  }
}

class Article {
    
    
  int articleId;
  String articleType;
  int productType;
  int sourceType;
  String articleTitle;
  Author author;
  List<Images> images;
  bool hasVideo;
  int readCount;
  int likeCount;
  int commentCount;
  List<Urls> urls;
  List<Null> tags;
  List<Topics> topics;
  List<Pois> pois;
  String publishTime;
  String publishTimeDisplay;
  String shootTime;
  String shootTimeDisplay;
  int level;
  String distanceText;
  bool isLike;
  int imageCounts;
  bool isCollected;
  int collectCount;
  int articleStatus;
  String poiName;

  Article(
      {
    
    this.articleId,
        this.articleType,
        this.productType,
        this.sourceType,
        this.articleTitle,
        this.author,
        this.images,
        this.hasVideo,
        this.readCount,
        this.likeCount,
        this.commentCount,
        this.urls,
        this.tags,
        this.topics,
        this.pois,
        this.publishTime,
        this.publishTimeDisplay,
        this.shootTime,
        this.shootTimeDisplay,
        this.level,
        this.distanceText,
        this.isLike,
        this.imageCounts,
        this.isCollected,
        this.collectCount,
        this.articleStatus,
        this.poiName});

  Article.fromJson(Map<String, dynamic> json) {
    
    
    articleId = json['articleId'];
    articleType = json['articleType'];
    productType = json['productType'];
    sourceType = json['sourceType'];
    articleTitle = json['articleTitle'];
    author =
    json['author'] != null ? new Author.fromJson(json['author']) : null;
    if (json['images'] != null) {
    
    
      images = new List<Images>();
      json['images'].forEach((v) {
    
    
        images.add(new Images.fromJson(v));
      });
    }
    hasVideo = json['hasVideo'];
    readCount = json['readCount'];
    likeCount = json['likeCount'];
    commentCount = json['commentCount'];
    if (json['urls'] != null) {
    
    
      urls = new List<Urls>();
      json['urls'].forEach((v) {
    
    
        urls.add(new Urls.fromJson(v));
      });
    }
    if (json['topics'] != null) {
    
    
      topics = new List<Topics>();
      json['topics'].forEach((v) {
    
    
        topics.add(new Topics.fromJson(v));
      });
    }
    if (json['pois'] != null) {
    
    
      pois = new List<Pois>();
      json['pois'].forEach((v) {
    
    
        pois.add(new Pois.fromJson(v));
      });
    }
    publishTime = json['publishTime'];
    publishTimeDisplay = json['publishTimeDisplay'];
    shootTime = json['shootTime'];
    shootTimeDisplay = json['shootTimeDisplay'];
    level = json['level'];
    distanceText = json['distanceText'];
    isLike = json['isLike'];
    imageCounts = json['imageCounts'];
    isCollected = json['isCollected'];
    collectCount = json['collectCount'];
    articleStatus = json['articleStatus'];
    poiName = json['poiName'];
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['articleId'] = this.articleId;
    data['articleType'] = this.articleType;
    data['productType'] = this.productType;
    data['sourceType'] = this.sourceType;
    data['articleTitle'] = this.articleTitle;
    if (this.author != null ) {
    
    
      data['author'] = this.author.toJson();
    }
    if (this.images != null) {
    
    
      data['images'] = this.images.map((v) => v.toJson()).toList();
      print("图片"+data['images']);
    }
    data['hasVideo'] = this.hasVideo;
    data['readCount'] = this.readCount;
    data['likeCount'] = this.likeCount;
    data['commentCount'] = this.commentCount;
    if (this.urls != null) {
    
    
      data['urls'] = this.urls.map((v) => v.toJson()).toList();
    }
    if (this.topics != null) {
    
    
      data['topics'] = this.topics.map((v) => v.toJson()).toList();
    }
    if (this.pois != null) {
    
    
      data['pois'] = this.pois.map((v) => v.toJson()).toList();
    }
    data['publishTime'] = this.publishTime;
    data['publishTimeDisplay'] = this.publishTimeDisplay;
    data['shootTime'] = this.shootTime;
    data['shootTimeDisplay'] = this.shootTimeDisplay;
    data['level'] = this.level;
    data['distanceText'] = this.distanceText;
    data['isLike'] = this.isLike;
    data['imageCounts'] = this.imageCounts;
    data['isCollected'] = this.isCollected;
    data['collectCount'] = this.collectCount;
    data['articleStatus'] = this.articleStatus;
    data['poiName'] = this.poiName;
    return data;
  }
}

class Author {
    
    
  int authorId;
  String nickName;
  String clientAuth;
  String jumpUrl;
  CoverImage coverImage;
  int identityType;
  String tag;

  Author(
      {
    
    this.authorId,
        this.nickName,
        this.clientAuth,
        this.jumpUrl,
        this.coverImage,
        this.identityType,
        this.tag});

  Author.fromJson(Map<String, dynamic> json) {
    
    
    authorId = json['authorId'];
    nickName = json['nickName'];
    clientAuth = json['clientAuth'];
    jumpUrl = json['jumpUrl'];
    coverImage = json['coverImage'] != null
        ? new CoverImage.fromJson(json['coverImage'])
        : null;
    identityType = json['identityType'];
    tag = json['tag'];
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['authorId'] = this.authorId;
    data['nickName'] = this.nickName;
    data['clientAuth'] = this.clientAuth;
    data['jumpUrl'] = this.jumpUrl;
    if (this.coverImage != null) {
    
    
      data['coverImage'] = this.coverImage.toJson();
    }
    data['identityType'] = this.identityType;
    data['tag'] = this.tag;
    return data;
  }
}

class CoverImage {
    
    
  String dynamicUrl;
  String originalUrl;

  CoverImage({
    
    this.dynamicUrl, this.originalUrl});

  CoverImage.fromJson(Map<String, dynamic> json) {
    
    
    dynamicUrl = json['dynamicUrl'];
    originalUrl = json['originalUrl'];
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['dynamicUrl'] = this.dynamicUrl;
    data['originalUrl'] = this.originalUrl;
    return data;
  }
}

class Images {
    
    
  int imageId;
  String dynamicUrl;
  String originalUrl;
  double width;
  double height;
  int mediaType;
  bool isWaterMarked;

  Images(
      {
    
    this.imageId,
        this.dynamicUrl,
        this.originalUrl,
        this.width,
        this.height,
        this.mediaType,
        this.isWaterMarked});

  Images.fromJson(Map<String, dynamic> json) {
    
    
    imageId = json['imageId'];
    dynamicUrl = json['dynamicUrl'];
    originalUrl = json['originalUrl'];
    width = json['width'];
    height = json['height'];
    mediaType = json['mediaType'];
    isWaterMarked = json['isWaterMarked'];
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['imageId'] = this.imageId;
    data['dynamicUrl'] = this.dynamicUrl;
    data['originalUrl'] = this.originalUrl;
    data['width'] = this.width;
    data['height'] = this.height;
    data['mediaType'] = this.mediaType;
    data['isWaterMarked'] = this.isWaterMarked;
    return data;
  }
}

class Urls {
    
    
  String version;
  String appUrl;
  String h5Url;
  String wxUrl;

  Urls({
    
    this.version, this.appUrl, this.h5Url, this.wxUrl});

  Urls.fromJson(Map<String, dynamic> json) {
    
    
    version = json['version'];
    appUrl = json['appUrl'];
    h5Url = json['h5Url'];
    wxUrl = json['wxUrl'];
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['version'] = this.version;
    data['appUrl'] = this.appUrl;
    data['h5Url'] = this.h5Url;
    data['wxUrl'] = this.wxUrl;
    return data;
  }
}

class Topics {
    
    
  int topicId;
  String topicName;
  int level;

  Topics({
    
    this.topicId, this.topicName, this.level});

  Topics.fromJson(Map<String, dynamic> json) {
    
    
    topicId = json['topicId'];
    topicName = json['topicName'];
    level = json['level'];
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['topicId'] = this.topicId;
    data['topicName'] = this.topicName;
    data['level'] = this.level;
    return data;
  }
}

class Pois {
    
    
  int poiType;
  int poiId;
  String poiName;
  int businessId;
  int districtId;
  PoiExt poiExt;
  int source;
  int isMain;
  bool isInChina;
  String countryName;

  Pois(
      {
    
    this.poiType,
        this.poiId,
        this.poiName,
        this.businessId,
        this.districtId,
        this.poiExt,
        this.source,
        this.isMain,
        this.isInChina,
        this.countryName});

  Pois.fromJson(Map<String, dynamic> json) {
    
    
    poiType = json['poiType'];
    poiId = json['poiId'];
    poiName = json['poiName'];
    businessId = json['businessId'];
    districtId = json['districtId'];
    poiExt =
    json['poiExt'] != null ? new PoiExt.fromJson(json['poiExt']) : null;
    source = json['source'];
    isMain = json['isMain'];
    isInChina = json['isInChina'];
    countryName = json['countryName'];
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['poiType'] = this.poiType;
    data['poiId'] = this.poiId;
    data['poiName'] = this.poiName;
    data['businessId'] = this.businessId;
    data['districtId'] = this.districtId;
    if (this.poiExt != null) {
    
    
      data['poiExt'] = this.poiExt.toJson();
    }
    data['source'] = this.source;
    data['isMain'] = this.isMain;
    data['isInChina'] = this.isInChina;
    data['countryName'] = this.countryName;
    return data;
  }
}

class PoiExt {
    
    
  String h5Url;
  String appUrl;

  PoiExt({
    
    this.h5Url, this.appUrl});

  PoiExt.fromJson(Map<String, dynamic> json) {
    
    
    h5Url = json['h5Url'];
    appUrl = json['appUrl'];
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['h5Url'] = this.h5Url;
    data['appUrl'] = this.appUrl;
    return data;
  }
}

travel_tab_model.dart页面
这个主要是用来获取tabBar数据来显示,例如:

///旅拍类别模型
class TravelTabModel {
    
    
  Map params;
  String url;
  List<TravelTab> tabs;

  TravelTabModel({
    
    this.url, this.tabs});

  TravelTabModel.fromJson(Map<String, dynamic> json) {
    
    
    url = json['url'];
    params = json['params'];
    if (json['tabs'] != null) {
    
    
      tabs = new List<TravelTab>();
      json['tabs'].forEach((v) {
    
    
        tabs.add(new TravelTab.fromJson(v));
      });
    }
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['url'] = this.url;
    if (this.tabs != null) {
    
    
      data['tabs'] = this.tabs.map((v) => v.toJson()).toList();
    }
    return data;
  }
}

class TravelTab {
    
    
  String labelName;
  String groupChannelCode;

  TravelTab({
    
    this.labelName, this.groupChannelCode});

  TravelTab.fromJson(Map<String, dynamic> json) {
    
    
    labelName = json['labelName'];
    groupChannelCode = json['groupChannelCode'];
  }

  Map<String, dynamic> toJson() {
    
    
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['labelName'] = this.labelName;
    data['groupChannelCode'] = this.groupChannelCode;
    return data;
  }
}

2.通过http请求获取json数据(dao层)

travel_dao.dart页面
首先我必须导入http请求的依赖:

http: ^0.12.0+1

其中 Utf8Decoder utf8decoder = Utf8Decoder(); 主要是用来避免中文乱码问题的,其中下面的Params中的参数主要使用到防爬功能,因为数据是进行动态变化的

import 'dart:async';
import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:trip_demo/model/travel_model.dart';

///旅拍页接口

var Params = {
    
    
  "districtId": -1,
  "groupChannelCode": "RX-OMF",
  "type": null,
  "lat": -180,
  "lon": -180,
  "locatedDistrictId": 0,
  "pagePara": {
    
    
    "pageIndex": 1,
    "pageSize": 10,
    "sortType": 9,
    "sortDirection": 0
  },
  "imageCutType": 1,
  "head": {
    
    'cid': "09031014111431397988"},
  "contentType": "json"
};

class TravelDao {
    
    
  static Future<TravelItemModel> fetch(
      String url,Map params, String groupChannelCode, int pageIndex, int pageSize) async {
    
    
    Map paramsMap = params['pagePara'];
    paramsMap['pageIndex'] = pageIndex;
    paramsMap['pageSize'] = pageSize;
    params['groupChannelCode'] = groupChannelCode;
    final response = await http.post(url, body: jsonEncode(params));
    if (response.statusCode == 200) {
    
    
      Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码
      var result = json.decode(utf8decoder.convert(response.bodyBytes));
      return TravelItemModel.fromJson(result);
    } else {
    
    
      throw Exception('Failed to load travel');
    }
  }
}

travel_tab_dao页面

import 'dart:async';
import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:trip_demo/model/travel_tab_model.dart';

///旅拍类别接口
class TravelTabDao {
    
    
  static Future<TravelTabModel> fetch() async {
    
    
    final response = await http
        .get('http://www.devio.org/io/flutter_app/json/travel_page.json');
    if (response.statusCode == 200) {
    
    
      Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文乱码
      var result = json.decode(utf8decoder.convert(response.bodyBytes));
      return TravelTabModel.fromJson(result);
    } else {
    
    
      throw Exception('Failed to load travel_page.json');
    }
  }
}

3.实现demo界面的基础功能(view层)

首先看一下main.dart界面的结构:

在这里插入图片描述

组件 功能
TabBar 实现tab主题 的切换
UnderlineIndicator 主要实现tab切换时显示在主题下方的进度条一样的东西,让滑动起来好看一点,因为不是flutter自带的必须要导入插件underline_indicator: ^0.0.2
Flexible 一般在row和column布局内使用,功能和ExPanded类似,区别在于Flexible不会把空白区域自动填充
TabBarView 用来存放切换的页面
TravelTabPage 这个我们自定义的组件,下面我们会详细说明
import 'package:flutter/material.dart';
import 'package:underline_indicator/underline_indicator.dart';

import 'dao/travel_tab_dao.dart';
import 'model/travel_tab_model.dart';
import 'travel_tab_page.dart';

void main(){
    
    
  runApp(MaterialApp(
        home: TravelPage(),
  ));
}

class TravelPage extends StatefulWidget {
    
    
  @override
  _TravelPageState createState() => _TravelPageState();
}

class _TravelPageState extends State<TravelPage>
    with TickerProviderStateMixin {
    
    
  TabController _controller;
  List<TravelTab> tabs = [];
  TravelTabModel travelTabModel;

  @override
  void initState() {
    
    
    _controller = TabController(length: 0, vsync: this);
    TravelTabDao.fetch().then((TravelTabModel model) {
    
    
      _controller = TabController(
          length: model.tabs.length, vsync: this); //fix tab label 空白问题
      setState(() {
    
    
        tabs = model.tabs;
        travelTabModel = model;
      });
    }).catchError((e) {
    
    
      print(e);
    });
    super.initState();
  }

  @override
  void dispose() {
    
    
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    
    
    return Scaffold(
        body: Column(
          children: <Widget>[
            Container(
              color: Colors.white,
              padding: EdgeInsets.only(top: 30),
              child: TabBar(
                  controller: _controller,
                  isScrollable: true,
                  labelColor: Colors.black,
                  labelPadding: EdgeInsets.fromLTRB(20, 0, 10, 5),
                  indicator: UnderlineIndicator(
                      strokeCap: StrokeCap.round,
                      borderSide: BorderSide(
                        color: Color(0xff2fcfbb),
                        width: 3,
                      ),
                      insets: EdgeInsets.only(bottom: 10)),
                  tabs: tabs.map<Tab>((TravelTab tab) {
    
    
                    return Tab(
                      text: tab.labelName,
                    );
                  }).toList()),
            ),
            Flexible(
                child: TabBarView(
                    controller: _controller,
                    children: tabs.map((TravelTab tab) {
    
    
                      return TravelTabPage(
                        travelUrl: travelTabModel.url,
                        params: travelTabModel.params,
                        groupChannelCode: tab.groupChannelCode,
                      );
                    }).toList()))
          ],
        ));
  }
}

travel_tab_page页面

在这里插入图片描述

组件 功能
LoadingContainer 这个为我们自定义的方法,主要用于在数据未加载完,设置一个转圈圈加载中的特效
RefreshIndicator 下拉重新加载的组件
MediaQuery.removePadding 移除组件直接的空白区域,让UI看起来更加好看
StaggeredGridView.countBuilder 用来实现照片墙的功能,需要导入flutter_staggered_grid_view: ^0.3.4插件才能实现这个功能
_TravelItem 自定义组件下面会细说

在这里插入图片描述
在这里插入图片描述
这些组件都毕竟基础,我就不细说了,如果想去了解基础组件的,可以去看我的下面两篇文章:
Flutter学习第四天:StatelessWidget常用组件总结,撑起Flutter的半边天?

Flutter学习第五天:StatefulWidget常用组件总结,撑起Flutter的另外半边天?

import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:transparent_image/transparent_image.dart';

import 'dao/travel_dao.dart';
import 'method/loading_contain.dart';
import 'model/travel_model.dart';

const _TRAVEL_URL =
    'https://m.ctrip.com/restapi/soa2/16189/json/searchTripShootListForHomePageV2?_fxpcqlniredt=09031014111431397988&__gw_appid=99999999&__gw_ver=1.0&__gw_from=10650013707&__gw_platform=H5';

const PAGE_SIZE = 10;

class TravelTabPage extends StatefulWidget {
    
    
  final String travelUrl;
  final Map params;
  final String groupChannelCode;

  const TravelTabPage(
      {
    
    Key key, this.travelUrl, this.params, this.groupChannelCode})
      : super(key: key);

  @override
  _TravelTabPageState createState() => _TravelTabPageState();
}

class _TravelTabPageState extends State<TravelTabPage>
    with AutomaticKeepAliveClientMixin {
    
    
  List<TravelItem> travelItems;
  int pageIndex = 1;
  bool _loading = true;
  ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    
    
    _loadData();
    _scrollController.addListener(() {
    
    
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
    
    
        _loadData(loadMore: true);
      }
    });
    super.initState();
  }

  @override
  void dispose() {
    
    
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    
    
    super.build(context);
    return Scaffold(
      body: LoadingContainer(
        isLoading: _loading,
        child: RefreshIndicator(
          onRefresh: _handleRefresh,
          child: MediaQuery.removePadding(
              removeTop: true,
              context: context,
              child: StaggeredGridView.countBuilder(
                controller: _scrollController,
                crossAxisCount: 4,
                itemCount: travelItems?.length ?? 0,
                itemBuilder: (BuildContext context, int index) => _TravelItem(
                  index: index,
                  item: travelItems[index],
                ),
                staggeredTileBuilder: (int index) => new StaggeredTile.fit(2),
              )),
        ),
      ),
    );
  }

  void _loadData({
    
    loadMore = false}) {
    
    
    if (loadMore) {
    
    
      pageIndex++;
    } else {
    
    
      pageIndex = 1;
    }

    TravelDao.fetch(widget.travelUrl ?? _TRAVEL_URL, widget.params,
        widget.groupChannelCode, pageIndex, PAGE_SIZE)
        .then((TravelItemModel model) {
    
    
      _loading = false;
      setState(() {
    
    
        List<TravelItem> items = _filterItems(model.resultList);
        if (travelItems != null) {
    
    
          travelItems.addAll(items);
        } else {
    
    
          travelItems = items;
        }
      });
    }).catchError((e) {
    
    
      _loading = false;
      print(e);
    });
  }

  List<TravelItem> _filterItems(List<TravelItem> resultList) {
    
    
    if (resultList == null) {
    
    
      return [];
    }
    List<TravelItem> filterItems = [];
    resultList.forEach((item) {
    
    
      if (item.article != null) {
    
    
        //移除article为空的模型
        filterItems.add(item);
      }
    });
    return filterItems;
  }

  @override
  bool get wantKeepAlive => true;

  Future<Null> _handleRefresh() async {
    
    
    _loadData();
    return null;
  }
}

class _TravelItem extends StatelessWidget {
    
    
  final TravelItem item;
  final int index;

  const _TravelItem({
    
    Key key, this.item, this.index}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    
    
    return GestureDetector(
      child: Card(
        child: PhysicalModel(
          color: Colors.transparent,
          clipBehavior: Clip.antiAlias,
          borderRadius: BorderRadius.circular(5),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              _itemImage(context),
              Container(
                padding: EdgeInsets.all(4),
                child: Text(
                  item.article.articleTitle,
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                  style: TextStyle(fontSize: 14, color: Colors.black87),
                ),
              ),
              _infoText()
            ],
          ),
        ),
      ),
    );
  }

  _itemImage(BuildContext context) {
    
    
    final size = MediaQuery.of(context).size;
    return Stack(
      children: <Widget>[
        Container(
          //设置最小初始高度,防止动态图片高度时的抖动
          constraints: BoxConstraints(
            minHeight: size.width / 2 - 10,
          ),
          child: FadeInImage.memoryNetwork(
            placeholder: kTransparentImage,
            image: item.article.images[0]?.dynamicUrl??"https://youimg1.c-ctrip.com/target/1A0n12000000sei6395D2_R_800_10000_Q50.jpg",
            fit: BoxFit.cover,
          ),
         // child: Image.network(item.article.images[0]?.dynamicUrl??"https://youimg1.c-ctrip.com/target/1A0n12000000sei6395D2_R_800_10000_Q50.jpg"),
        ),
        Positioned(
            bottom: 8,
            left: 8,
            child: Container(
              padding: EdgeInsets.fromLTRB(5, 1, 5, 1),
              decoration: BoxDecoration(
                  color: Colors.black54,
                  borderRadius: BorderRadius.circular(10)),
              child: Row(
                children: <Widget>[
                  Padding(
                      padding: EdgeInsets.only(right: 3),
                      child: Icon(
                        Icons.location_on,
                        color: Colors.white,
                        size: 12,
                      )),
                  LimitedBox(
                    maxWidth: 130,
                    child: Text(
                      _poiName(),
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      style: TextStyle(color: Colors.white, fontSize: 12),
                    ),
                  )
                ],
              ),
            ))
      ],
    );
  }

  String _poiName() {
    
    
    return item.article.pois == null || item.article.pois.length == 0
        ? '未知'
        : item.article.pois[0]?.poiName ?? '未知';
  }

  _infoText() {
    
    
    return Container(
      padding: EdgeInsets.fromLTRB(6, 0, 6, 10),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          Row(
            children: <Widget>[
              PhysicalModel(
                color: Colors.transparent,
                clipBehavior: Clip.antiAlias,
                borderRadius: BorderRadius.circular(12),
                child: Image.network(
                  item.article.author?.coverImage?.dynamicUrl,
                  width: 24,
                  height: 24,
                ),
              ),
              Container(
                padding: EdgeInsets.all(5),
                width: 90,
                child: Text(
                  item.article.author?.nickName,
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: TextStyle(fontSize: 12),
                ),
              )
            ],
          ),
          Row(
            children: <Widget>[
              Icon(
                Icons.thumb_up,
                size: 14,
                color: Colors.grey,
              ),
              Padding(
                padding: EdgeInsets.only(left: 3),
                child: Text(
                  item.article.likeCount.toString(),
                  style: TextStyle(fontSize: 10),
                ),
              )
            ],
          )
        ],
      ),
    );
  }
}

loading_contain.dart页面

import 'package:flutter/material.dart';

class LoadingContainer extends StatelessWidget {
    
    
  final Widget child;
  final bool isLoading;
  final bool cover;

  const LoadingContainer(
      {
    
    Key key,
        @required this.isLoading,
        this.cover = false,
        @required this.child})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    
    
    return !cover
        ? !isLoading ? child : _loadingView
        : Stack(
      children: <Widget>[child, isLoading ? _loadingView : Container()],
    );
  }

  Widget get _loadingView {
    
    
    return Center(
      child: CircularProgressIndicator(),
    );
  }
}

猜你喜欢

转载自blog.csdn.net/qq_45137584/article/details/115273316