一步一步完成Flutter應用開發-掘金App首頁

一天清晨發表於2021-03-16

寫一個仿掘金app主頁的效果,這裡面不涉及到網路請求,直接是頁面的鋪設。

構建頁面

想要實現效果首先需要把頁面構建出來,然後在研究動畫的效果,

tutieshi_640x1343_1s.gif

上半部分

效果:

Simulator Screen Shot - iPhone 11 - 2021-03-15 at 10.33.01.png

把這個抽離成一個元件:home_top.dart檔案

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class HomeTopPage extends StatefulWidget {
  HomeTopPage({Key key, this.onSearchPress, this.onTagPress}) : super(key: key);

  final Function onTagPress;
  final Function onSearchPress;

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

class _HomeTopPageState extends State<HomeTopPage> {
  String currentString = '推薦';

  tagButton({title = '標籤'}) {
    return FlatButton(
        onPressed: () {
          setState(() {
            currentString = title;
          });
        },
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text(title,
                style: currentString == title
                    ? TextStyle(fontSize: 20, color: Colors.blue)
                    : TextStyle(fontSize: 18, color: Colors.grey)),
            Offstage(
              offstage: currentString != title,
              child: Container(
                height: 2,
                width: 40,
                decoration: BoxDecoration(color: Colors.blue),
              ),
            )
          ],
        ));
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(top: Get.context.mediaQueryPadding.top),
      decoration: BoxDecoration(color: Colors.white),
      child: Column(
        children: [
          Container(
            width: Get.width,
            height: 40,
            child: Row(
              children: [
                Expanded(
                    child: InkWell(
                  onTap: () {
                    widget.onSearchPress();
                  },
                  child: Container(
                    margin: EdgeInsets.only(left: 10),
                    height: 36,
                    decoration: BoxDecoration(
                      color: Color.fromRGBO(235, 242, 244, 1),
                      borderRadius: BorderRadius.all(Radius.circular(5)),
                    ),
                    child: Row(
                      children: [
                        Padding(padding: EdgeInsets.symmetric(horizontal: 5)),
                        Icon(
                          Icons.search,
                          size: 18,
                          color: Colors.grey,
                        ),
                        Padding(padding: EdgeInsets.symmetric(horizontal: 5)),
                        Text(
                          '搜尋掘金',
                          style: TextStyle(fontSize: 18, color: Colors.grey),
                        )
                      ],
                    ),
                  ),
                )),
                FlatButton(
                    minWidth: 80,
                    onPressed: () {
                      widget.onTagPress();
                    },
                    child: Row(
                      children: [
                        Icon(
                          Icons.settings,
                          size: 18,
                          color: Colors.grey,
                        ),
                        Text('標籤',
                            style: TextStyle(fontSize: 18, color: Colors.grey))
                      ],
                    ))
              ],
            ),
          ),
          Container(
            height: 50,
            width: Get.width,
            padding: EdgeInsets.only(top: 15),
            child: SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Row(
                children: [
                  '關注',
                  '推薦',
                  '熱榜',
                  '後端',
                  '前端',
                  'Android',
                  'iOS',
                  '人工智慧',
                  '開發工具',
                  '程式碼人生',
                  '閱讀'
                ].map<Widget>((e) => tagButton(title: e)).toList(),
              ),
            ),
          )
        ],
      ),
    );
  }
}

複製程式碼

主頁面引用 曝漏兩個方法onSearchPress,onTagPress, 故名思意,一個是搜尋點選方法,一個是標籤點選方法。

@override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Color.fromRGBO(235, 242, 244, 1),
        body: Column(
          children: [
            HomeTopPage(
              onSearchPress: () {},
              onTagPress: () {},
            )
          ],
        ));
  }
複製程式碼

列表部分

新建home_list.dart檔案。這裡直接使用之前封裝的ListView。傳送門

Simulator Screen Shot - iPhone 11 - 2021-03-15 at 13.37.17.png 程式碼如下:

import 'package:flutter/material.dart';
import 'package:flutter_common_app/widget/common_list.dart';

class HomeList extends StatefulWidget {
  HomeList({Key key}) : super(key: key);

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

class _HomeListState extends State<HomeList> {
  List dataList = ['1', '2', '3'];

  itemWidget(isContainUrl) {
    return Container(
      constraints: BoxConstraints(
        minHeight: 80,
      ),
      margin: EdgeInsets.only(top: 1),
      padding: EdgeInsets.symmetric(horizontal: 20),
      decoration: BoxDecoration(color: Colors.white),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Expanded(
              child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                '有新工具了,趕緊來看看來看看有新工具了,趕緊來看看來看看有新工具了,趕緊來看看來看看有新工具了,趕緊來看看來看看',
                style: TextStyle(fontSize: 18),
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
              ),
              Padding(
                padding: EdgeInsets.only(top: 5),
                child: Text(
                  '點贊89·閱讀123·一天清晨',
                  style: TextStyle(fontSize: 14, color: Colors.grey),
                ),
              )
            ],
          )),
          Offstage(
            offstage: isContainUrl,
            child: Container(
              height: 60,
              width: 60,
              decoration: BoxDecoration(color: Colors.red),
            ),
          )
        ],
      ),
    );
  }

  hotWidget() {
    return Container(
      margin: EdgeInsets.only(top: 10),
      child: Column(
        children: [
          Container(
            decoration: BoxDecoration(color: Colors.white),
            padding: EdgeInsets.only(left: 20),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Text('? 熱門推薦',
                    style: TextStyle(fontSize: 14, color: Colors.grey)),
                FlatButton(
                    onPressed: () {},
                    child: Row(
                      children: [
                        Text('文章榜',
                            style: TextStyle(fontSize: 14, color: Colors.grey)),
                        Icon(
                          Icons.keyboard_arrow_right,
                          color: Colors.grey,
                          size: 18,
                        )
                      ],
                    ))
              ],
            ),
          ),
          itemWidget(true),
          itemWidget(true),
          itemWidget(false),
        ],
      ),
    );
  }

  listItemWidget() {
    return Container(
      constraints: BoxConstraints(
        minHeight: 80,
      ),
      margin: EdgeInsets.only(top: 5),
      padding: EdgeInsets.symmetric(horizontal: 20),
      decoration: BoxDecoration(color: Colors.white),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Padding(padding: EdgeInsets.symmetric(vertical: 5)),
          Row(
            children: [
              Icon(
                Icons.sentiment_very_satisfied,
                size: 16,
                color: Colors.grey,
              ),
              Text(
                '  一天清晨',
                style: TextStyle(fontSize: 16),
              ),
            ],
          ),
          Padding(padding: EdgeInsets.symmetric(vertical: 5)),
          Text(
            '有新工具了,趕緊來看看來看看有新工具了,趕緊來看看來看看有新工具了,趕緊來看看來看看有新工具了,趕緊來看看來看看',
            style: TextStyle(fontSize: 18),
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
          Padding(padding: EdgeInsets.symmetric(vertical: 5)),
          Text(
            '有新工具了,趕緊來看看來看看有新工具了',
            style: TextStyle(fontSize: 14, color: Colors.grey),
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
          Padding(padding: EdgeInsets.symmetric(vertical: 5)),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return CommonListWiget(
      networkApi: (currentPage) async {
        Future.delayed(Duration(seconds: 2)).then((value) => {
              dataList.addAll(['1', '2'])
            });
        return dataList;
      },
      itemBuilder: (BuildContext context, int position) {
        if (position == 0) {
          return hotWidget();
        }
        return listItemWidget();
      },
    );
  }
}

複製程式碼

同樣在頁面引用一下這個檔案

@override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Color.fromRGBO(235, 242, 244, 1),
        body: Column(
          children: [
            HomeTopPage(
              onSearchPress: () {},
              onTagPress: () {},
            ),
            Expanded(child: HomeList()),
          ],
        ));
  }
複製程式碼

加入動畫

首先改造一下home_list.dart檔案,檢測一下滾動的高度

AnimationController _controller;
  ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
    _scrollController
      ..addListener(() {
        setState(() {
          if (_scrollController.offset > 88) {
          // 把值傳出去
            widget.onScroll(255);
          } else if (_scrollController.offset <= 0) {
            widget.onScroll(0);
          } else {
            widget.onScroll(_scrollController.offset * 255 ~/ 88);
          }
        });
      });
  }
複製程式碼

接著改造一下home_top.dart檔案,讓頭部隨著滾動的狀態變化, 定義一個alpha屬性接受滑動的傳值。


@override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(top: Get.context.mediaQueryPadding.top),
      decoration: BoxDecoration(color: Colors.white),
      child: Stack(
        children: [
          renderSearch(),
          Container(
            height: 40,
            width: Get.width,
            margin: EdgeInsets.only(top: 55 + (0 - 40 * widget.alpha / 255)),
            child: SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Row(
                children: [
                  '關注',
                  '推薦',
                  '熱榜',
                  '後端',
                  '前端',
                  'Android',
                  'iOS',
                  '人工智慧',
                  '開發工具',
                  '程式碼人生',
                  '閱讀'
                ].map<Widget>((e) => tagButton(title: e)).toList(),
              ),
            ),
          )
        ],
      ),
    );
    
    renderSearch() {
    return Positioned(
        top: 0 - 40 * widget.alpha / 255,
        child: Container(
          width: Get.width,
          height: 40,
          child: Row(
            children: [
              Expanded(
                  child: InkWell(
                onTap: () {
                  widget.onSearchPress();
                },
                child: Container(
                  margin: EdgeInsets.only(left: 10),
                  height: 36,
                  decoration: BoxDecoration(
                    color: Color.fromRGBO(235, 242, 244, 1)
                        .withAlpha(255 - widget.alpha),
                    borderRadius: BorderRadius.all(Radius.circular(5)),
                  ),
                  child: Row(
                    children: [
                      Padding(padding: EdgeInsets.symmetric(horizontal: 5)),
                      Icon(
                        Icons.search,
                        size: 18,
                        color: Colors.grey.withAlpha(255 - widget.alpha),
                      ),
                      Padding(padding: EdgeInsets.symmetric(horizontal: 5)),
                      Text(
                        '搜尋掘金',
                        style: TextStyle(
                            fontSize: 18,
                            color: Colors.grey.withAlpha(255 - widget.alpha)),
                      )
                    ],
                  ),
                ),
              )),
              FlatButton(
                  minWidth: 80,
                  onPressed: () {
                    widget.onTagPress();
                  },
                  child: Row(
                    children: [
                      Icon(
                        Icons.settings,
                        size: 18,
                        color: Colors.grey.withAlpha(255 - widget.alpha),
                      ),
                      Text('標籤',
                          style: TextStyle(
                              fontSize: 18,
                              color: Colors.grey.withAlpha(255 - widget.alpha)))
                    ],
                  ))
            ],
          ),
        ));
  }
複製程式碼

主頁面呼叫

@override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Color.fromRGBO(235, 242, 244, 1),
        body: Column(
          children: [
            HomeTopPage(
              onSearchPress: () {},
              onTagPress: () {},
              alpha: _alpha,
            ),
            Expanded(child: HomeList(onScroll: (value) {
              setState(() {
                _alpha = value;
              });
            })),
          ],
        ));
  }
複製程式碼

就ok了, 歡迎同學們來指導討論。懂得分享才能共同進步。

相關文章