15-豆瓣首页的实现

Catalogue
  1. 1. 一, 底部tabbar的实现
    1. 1.0.1. 1.1, 首页代码
    2. 1.0.2. 1.2,初始化项封装
  • 2. 二,通用请求工具的封装
    1. 2.0.1. 2.1, 请求工具的实现
    2. 2.0.2. 2.1, 配置类
  • 3. 三,首页请求工具封装
  • 4. 四,首页Item的实现
  • 先来看一下,豆瓣首页的效果

    图1

    通过上边的页面,我们可以通过下边的步骤,进行实现:

    • 1, 需要实现底部的tabbar
    • 2,通用请求工具封装
    • 3,页面请求工具封装
    • 4,首页item项的封装

    一, 底部tabbar的实现

    1.1, 首页代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:learn_flutter/douban/pages/main/bottom_bar_item.dart';
    import 'package:learn_flutter/douban/pages/main/initialize_items.dart';
    import 'package:learn_flutter/douban/widgets/dashed_line.dart';
    import 'package:learn_flutter/douban/widgets/star_rating.dart';

    void main(){
    runApp(MyApp());
    }

    class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
    primaryColor: Colors.green,
    primaryColorLight: Colors.transparent,
    splashColor: Colors.transparent
    ),
    home: HYMainPage()
    );
    }
    }

    class HYMainPage extends StatefulWidget {
    @override
    _HYMainPageState createState() => _HYMainPageState();
    }

    class _HYMainPageState extends State<HYMainPage> {
    int _currentIndex = 0;

    @override
    Widget build(BuildContext context) {
    return Scaffold(
    body: IndexedStack(
    index: _currentIndex,
    children: pages
    ),
    bottomNavigationBar: BottomNavigationBar(
    currentIndex: _currentIndex,
    type: BottomNavigationBarType.fixed,
    selectedFontSize: 12, //选中的字体大小
    unselectedFontSize: 12, //未选中的字体大小
    selectedItemColor: Colors.green, //选中的颜色
    unselectedItemColor: Colors.black, //未选中的颜色
    items: items,
    onTap: (index){
    setState(() {
    _currentIndex = index;
    });
    },
    ),
    );
    }
    }

    1.2,初始化项封装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    import 'package:flutter/material.dart';

    import 'bottom_bar_item.dart';
    import '../home/home_page.dart';
    import '../subject/subject_page.dart';


    List<HYBottomBarItem> items = [
    HYBottomBarItem("home","首页"),
    HYBottomBarItem("subject","书音影"),
    HYBottomBarItem("group","小组"),
    HYBottomBarItem("mall","市集"),
    HYBottomBarItem("profile","我的"),
    ];

    List<Widget> pages = [
    HYHomePage(),
    HYSubjectPage(),
    HYSubjectPage(),
    HYSubjectPage(),
    HYSubjectPage(),
    ];
    1.3,底部tabbarItem的封装
    import 'package:flutter/material.dart';

    class HYBottomBarItem extends BottomNavigationBarItem {
    HYBottomBarItem(String iconName, String title): super(
    title: Text(title),
    icon: Image.asset("assets/images/tabbar/${iconName}.png",width: 30,height: 30,),
    activeIcon: Image.asset("assets/images/tabbar/${iconName}_active.png" ,width: 30,height: 30,),
    );
    }

    二,通用请求工具的封装

    2.1, 请求工具的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    import 'package:dio/dio.dart';
    import 'http_config.dart';
    class HttpRequest{
    static BaseOptions baseOptions = BaseOptions(baseUrl: HttpConfig.baseURL, connectTimeout: HttpConfig.timeout);
    static Dio dio = Dio(baseOptions);
    static Future request(String url,
    {String method = "get", Map<String,dynamic> params, Interceptor inter}) async {

    //1.创建单独配置
    final options = Options(method: method);

    //全局拦截器
    //创建默认的全局拦截器
    Interceptor defaultInter = InterceptorsWrapper(
    onRequest: (options){
    print("请求拦截");
    return options;
    },
    onResponse: (response){
    print("响应拦截");
    return response;
    },
    onError: (err){
    print("错误拦截");
    return err;
    }
    );

    List<Interceptor> inters = [defaultInter];
    //请求单独的拦截器(传过来的)
    if(inter != null){
    inters.add(inter);
    }
    dio.interceptors.addAll(inters);

    //2.发送网络请求
    try{
    Response response = await dio.request(url,queryParameters: params,options: options);
    return response.data;
    } on DioError catch(e){
    return Future.error(e);
    }
    }
    }

    2.1, 配置类

    1
    2
    3
    4
    5
    6
    7
    8
    class HttpConfig{
    static const String baseURL = "https://douban-api.uieee.com/v2";
    static const int timeout = 10000;
    }

    class HomeConfig{
    static const int count = 20;
    }

    三,首页请求工具封装

    图1

    四,首页Item的实现

    图1

    首页item, 我们可以分为上中下三块,可以使用Column包裹

    图1

    中间部分,我们可以通过Row包括, 电影信息这块,我们可以通过Column包裹
    实现代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    import 'package:flutter/material.dart';
    import 'package:learn_flutter/douban/model/home_model.dart';
    import 'package:learn_flutter/douban/utils/log.dart';
    import 'package:learn_flutter/douban/widgets/dashed_line.dart';
    import 'package:learn_flutter/douban/widgets/star_rating.dart';

    class HYHomeMovieItem extends StatelessWidget {
    final MovieItem item;

    HYHomeMovieItem(this.item);

    @override
    Widget build(BuildContext context) {
    return Container(
    padding: EdgeInsets.all(8),
    decoration: BoxDecoration(
    color: Colors.white,
    border: Border(
    bottom: BorderSide(width: 10,color: Color(0xffe2e2e2))
    )
    ),
    child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
    buildHeader(),
    SizedBox(height:8),
    buildContent(),
    SizedBox(height:8),
    buildFooter(),
    ],
    ),
    );
    }

    //1.构建header
    Widget buildHeader(){
    return Container(
    padding: EdgeInsets.fromLTRB(9, 4, 9, 4),
    decoration: BoxDecoration(
    color: Color.fromARGB(255, 238, 205, 144),
    borderRadius: BorderRadius.circular(3),
    ),
    child: Text(
    "No.${item.rank}",
    style: TextStyle(
    fontSize: 18,
    color: Color.fromARGB(255, 131, 95, 36)
    ),
    ),
    );
    }

    //2.构建内容
    Widget buildContent(){
    return Container(
    height: 150,
    child: Row(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
    buildContentImage(),
    SizedBox(width: 8,),
    buildContentInfo(),
    SizedBox(width: 8,),
    buildContentDashedLine(),
    SizedBox(width: 8,),
    buildContentWish()
    ],
    ),
    );
    }

    //2.1 构建内容 - 图片
    Widget buildContentImage() {
    return ClipRRect(
    borderRadius: BorderRadius.circular(5),
    child: Image.network(item.imageURL, width: 100,),
    );
    }

    //2.2 构建内容信息
    Widget buildContentInfo(){
    return Expanded(
    child: Container(
    child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
    buildContentInfoTitle(),
    SizedBox(height: 5,),
    buildContentInfoRating(),
    SizedBox(height: 5,),
    buildContentInfoDesc(),
    ],
    ),
    ),
    );
    }

    //2.2.1 构建内容信息 - 标题
    Widget buildContentInfoTitle(){
    // hyLog("hylog test",StackTrace.current);
    return Text.rich(
    TextSpan(
    children: [
    WidgetSpan(
    child: Icon(Icons.play_circle_outline,color: Colors.pink, size: 30,),
    baseline: TextBaseline.ideographic,
    alignment: PlaceholderAlignment.middle
    ),
    ...item.title.runes.map((rune){
    return WidgetSpan(
    baseline: TextBaseline.ideographic,
    alignment: PlaceholderAlignment.middle,
    child: Text(
    new String.fromCharCode(rune),
    style: TextStyle(fontSize: 16,fontWeight: FontWeight.bold)
    ));
    }).toList(),
    WidgetSpan(
    baseline: TextBaseline.ideographic,
    alignment: PlaceholderAlignment.middle,
    child: Text(
    "(${item.playDate})",
    style: TextStyle(fontSize: 12,color: Color(0xffbbbbbb))
    )
    ),
    ]
    )
    );
    }

    //2.2.2 构建内容信息 - 评分
    Widget buildContentInfoRating(){
    return FittedBox(
    child: Row(
    children: <Widget>[
    HYStarRating(
    rating: item.rating,
    size: 20,
    ),
    SizedBox(width: 5,),
    Text("${item.rating}",style: TextStyle(color: Color(0xffbbbbbb)),)
    ],
    ),
    );
    }

    //2.2.3 构建内容信息 - 描述
    Widget buildContentInfoDesc(){
    final genresString = item.genres.join(" ");
    final directorString = item.director.name;
    final actorString = item.casts.map((item) => item.name).join(" ");

    return Text(
    "${genresString} / ${directorString} / ${actorString}",
    maxLines: 2,
    overflow: TextOverflow.ellipsis,
    style: TextStyle(fontSize: 16),
    );
    }

    //2.3 构建分割线
    Widget buildContentDashedLine(){
    return Container(
    width: 10,
    height: 100,
    child: HYDashedLine(
    axis: Axis.vertical,
    dashedWidth: 1,
    dashedHeight: 5,
    color: Colors.red
    ),
    );
    }

    //2.4 构建想看
    Widget buildContentWish(){
    return Container(
    width: 60,
    child: Column(
    children: <Widget>[
    SizedBox(height: 20,),
    Image.asset("assets/images/home/wish.png",width: 30,height: 30,),
    Text("想看",style: TextStyle(fontSize: 16,color:Color.fromARGB(255, 235, 170, 60)),)
    ],
    ),
    );
    }

    //3. 构建电影简介(原生名称)
    Widget buildFooter(){
    return Container(
    padding: EdgeInsets.all(12),
    width: double.infinity,
    decoration: BoxDecoration(
    color: Color(0xfff2f2f2),
    borderRadius: BorderRadius.circular(5),
    ),
    child: Text(item.origianlTitle,style: TextStyle(fontSize: 18,color: Colors.black54),),
    );
    }
    }