Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

會煮咖啡的貓發表於2020-06-24

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

B站視訊

www.bilibili.com/video/BV1Pp…

1 本節目標

  • 程式碼規範
  • 業務程式碼組織
  • 首頁程式碼編寫

2 程式碼規範

2.1 官方程式碼規範

dart.dev/guides/lang…

2.3 chrome 外掛 <彩雲小譯 - 網頁翻譯外掛>

chrome.google.com/webstore/de…

2.4 阿里專案規範

github.com/alibaba/flu…

3 業務介面程式碼組織

3.1 redux、fish-redux

  • redux 架構

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

  • fish-redux 架構

進一步的細分,進行規範

github.com/alibaba/fis… medium.com/@dave790602…

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

  • fish-redux 程式碼

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

3.2 bloc

  • 架構

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

bloclibrary.dev/#/

  • 程式碼組織

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

3.3 簡單就是美

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

3.4 如何平衡

  • 是否團隊開發
  • 是否簡單業務(20 頁面)

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

  • 是否重互動(視訊社交、聊天 A)

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

4 新聞首頁實現

4.1 介面組成分析

  • 分類導航、推薦新聞、頻道導航

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

  • 新聞列表、廣告 ad、郵件訂閱

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

4.2 程式碼框架

...
class _MainPageState extends State<MainPage> {

  @override
  void initState() {
    super.initState();
    _loadAllData();
  }

  // 讀取所有資料
  _loadAllData() async {
  }

  // 分類選單
  Widget _buildCategories() {
    return Container();
  }

  // 推薦閱讀
  Widget _buildRecommend() {
    return Container();
  }

  // 頻道
  Widget _buildChannels() {
    return Container();
  }

  // 新聞列表
  Widget _buildNewsList() {
    return Container();
  }

  // ad 廣告條
  // 郵件訂閱
  Widget _buildEmailSubscribe() {
    return newsletterWidget();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
        child: Column(
          children: <Widget>[
            _buildCategories(),
            Divider(height: 1),
            _buildRecommend(),
            Divider(height: 1),
            _buildChannels(),
            Divider(height: 1),
            _buildNewsList(),
            Divider(height: 1),
            _buildEmailSubscribe(),
          ],
        ),
      );
  }
}

複製程式碼

4.3 實現業務

  • 建立 widget 單獨檔案

Flutter 新聞客戶端 - 06 程式碼規範、業務程式碼組織、新聞首頁實現

  • 分類導航

lib/pages/main/categories_widget.dart

Widget newsCategoriesWidget({
  List<CategoryResponseEntity> categories,
  String selCategoryCode,
  Function(CategoryResponseEntity) onTap,
}) {
  return SingleChildScrollView(
    scrollDirection: Axis.horizontal,
    child: Row(
      children: categories.map<Widget>((item) {
        return Container(
          alignment: Alignment.center,
          height: duSetHeight(52),
          padding: EdgeInsets.symmetric(horizontal: 8),
          child: GestureDetector(
            child: Text(
              item.title,
              style: TextStyle(
                color: selCategoryCode == item.code
                    ? AppColors.secondaryElementText
                    : AppColors.primaryText,
                fontSize: duSetFontSize(18),
                fontFamily: 'Montserrat',
                fontWeight: FontWeight.w600,
              ),
            ),
            onTap: () => onTap(item),
          ),
        );
      }).toList(),
    ),
  );
}
複製程式碼
  • 頻道導航

lib/pages/main/channels_widget.dart

Widget newsChannelsWidget({
  List<ChannelResponseEntity> channels,
  Function(ChannelResponseEntity) onTap,
}) {
  return Container(
    height: duSetHeight(137),
    child: SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: Row(
        children: channels.map<Widget>((item) {
          return Container(
            width: duSetWidth(70),
            height: duSetHeight(97),
            margin: EdgeInsets.symmetric(horizontal: duSetWidth(10)),
            child: InkWell(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  // 圖示
                  Container(
                    height: duSetWidth(64),
                    margin: EdgeInsets.symmetric(horizontal: duSetWidth(3)),
                    child: Stack(
                      alignment: Alignment.center,
                      children: [
                        Positioned(
                          left: 0,
                          top: 0,
                          right: 0,
                          child: Container(
                            height: duSetWidth(64),
                            decoration: BoxDecoration(
                              color: AppColors.primaryBackground,
                              boxShadow: [
                                Shadows.primaryShadow,
                              ],
                              borderRadius:
                                  BorderRadius.all(Radius.circular(32)),
                            ),
                            child: Container(),
                          ),
                        ),
                        Positioned(
                          left: duSetWidth(10),
                          top: duSetWidth(10),
                          right: duSetWidth(10),
                          child: Image.asset(
                            "assets/images/channel-${item.code}.png",
                            fit: BoxFit.none,
                          ),
                        ),
                      ],
                    ),
                  ),
                  // 標題
                  Text(
                    item.title,
                    textAlign: TextAlign.center,
                    overflow: TextOverflow.clip,
                    maxLines: 1,
                    style: TextStyle(
                      color: AppColors.thirdElementText,
                      fontFamily: "Avenir",
                      fontWeight: FontWeight.w400,
                      fontSize: duSetFontSize(14),
                      height: 1,
                    ),
                  ),
                ],
              ),
              onTap: () => onTap(item),
            ),
          );
        }).toList(),
      ),
    ),
  );
}
複製程式碼
  • 新聞行 Item

lib/pages/main/news_item_widget.dart

Widget newsItem(NewsItem item) {
  return Container(
    height: duSetHeight(161),
    padding: EdgeInsets.all(duSetWidth(20)),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        // 圖
        imageCached(
          item.thumbnail,
          width: duSetWidth(121),
          height: duSetWidth(121),
        ),
        // 右側
        SizedBox(
          width: duSetWidth(194),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              // 作者
              Container(
                margin: EdgeInsets.all(0),
                child: Text(
                  item.author,
                  style: TextStyle(
                    fontFamily: 'Avenir',
                    fontWeight: FontWeight.normal,
                    color: AppColors.thirdElementText,
                    fontSize: duSetFontSize(14),
                    height: 1,
                  ),
                ),
              ),
              // 標題
              Container(
                margin: EdgeInsets.only(top: duSetHeight(10)),
                child: Text(
                  item.title,
                  style: TextStyle(
                    fontFamily: 'Montserrat',
                    fontWeight: FontWeight.w500,
                    color: AppColors.primaryText,
                    fontSize: duSetFontSize(16),
                    height: 1,
                  ),
                  overflow: TextOverflow.clip,
                  maxLines: 3,
                ),
              ),
              // Spacer
              Spacer(),
              // 一行 3 列
              Container(
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    // 分類
                    ConstrainedBox(
                      constraints: BoxConstraints(
                        maxWidth: duSetWidth(60),
                      ),
                      child: Text(
                        item.category,
                        style: TextStyle(
                          fontFamily: 'Avenir',
                          fontWeight: FontWeight.normal,
                          color: AppColors.secondaryElementText,
                          fontSize: duSetFontSize(14),
                          height: 1,
                        ),
                        overflow: TextOverflow.clip,
                        maxLines: 1,
                      ),
                    ),
                    // 新增時間
                    Container(
                      width: duSetWidth(15),
                    ),
                    ConstrainedBox(
                      constraints: BoxConstraints(
                        maxWidth: duSetWidth(100),
                      ),
                      child: Text(
                        '• ${duTimeLineFormat(item.addtime)}',
                        style: TextStyle(
                          fontFamily: 'Avenir',
                          fontWeight: FontWeight.normal,
                          color: AppColors.thirdElementText,
                          fontSize: duSetFontSize(14),
                          height: 1,
                        ),
                        overflow: TextOverflow.clip,
                        maxLines: 1,
                      ),
                    ),
                    // 更多
                    Spacer(),
                    InkWell(
                      child: Icon(
                        Icons.more_horiz,
                        color: AppColors.primaryText,
                        size: 24,
                      ),
                      onTap: () {},
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}
複製程式碼
  • 郵件訂閱

lib/pages/main/newsletter_widget.dart

Widget newsletterWidget() {
  return Container(
    margin: EdgeInsets.all(duSetWidth(20)),
    child: Column(
      children: <Widget>[
        // newsletter
        Row(
          children: <Widget>[
            Text(
              'Newsletter',
              style: TextStyle(
                fontFamily: 'Montserrat',
                fontSize: duSetFontSize(18),
                fontWeight: FontWeight.w600,
                color: AppColors.thirdElement,
              ),
            ),
            Spacer(),
            IconButton(
              icon: Icon(
                Icons.close,
                color: AppColors.thirdElementText,
                size: duSetFontSize(17),
              ),
              onPressed: () {},
            ),
          ],
        ),

        // email
        inputEmailEdit(
          marginTop: 19,
          keyboardType: TextInputType.emailAddress,
          hintText: "Email",
          isPassword: false,
          controller: null,
        ),

        // btn subcrible
        Padding(
          padding: EdgeInsets.only(top: 15),
          child: btnFlatButtonWidget(
            onPressed: () {},
            width: duSetWidth(335),
            height: duSetHeight(44),
            fontWeight: FontWeight.w600,
            title: "Subscribe",
          ),
        ),

        // disc
        Container(
          margin: EdgeInsets.only(top: duSetHeight(29)),
          width: duSetWidth(261),
          child: Text.rich(TextSpan(children: <TextSpan>[
            TextSpan(
              text: 'By clicking on Subscribe button you agree to accept',
              style: new TextStyle(
                color: AppColors.thirdElementText,
                fontFamily: "Avenir",
                fontWeight: FontWeight.w400,
                fontSize: duSetFontSize(14),
              ),
            ),
            TextSpan(
              text: ' Privacy Policy',
              style: new TextStyle(
                color: AppColors.secondaryElementText,
                fontFamily: "Avenir",
                fontWeight: FontWeight.w400,
                fontSize: duSetFontSize(14),
              ),
              recognizer: TapGestureRecognizer()
                ..onTap = () {
                  toastInfo(msg: 'Privacy Policy');
                },
            ),
          ])),
        ),
      ],
    ),
  );
}
複製程式碼
  • 推薦閱讀

lib/pages/main/recommend_widget.dart

Widget recommendWidget(NewsRecommendResponseEntity newsRecommend) {
  return Container(
    margin: EdgeInsets.all(duSetWidth(20)),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        // 圖
        imageCached(
          newsRecommend.thumbnail,
          width: duSetWidth(335),
          height: duSetHeight(290),
        ),
        // 作者
        Container(
          margin: EdgeInsets.only(top: duSetHeight(14)),
          child: Text(
            newsRecommend.author,
            style: TextStyle(
              fontFamily: 'Avenir',
              fontWeight: FontWeight.normal,
              color: AppColors.thirdElementText,
              fontSize: duSetFontSize(14),
            ),
          ),
        ),
        // 標題
        Container(
          margin: EdgeInsets.only(top: duSetHeight(10)),
          child: Text(
            newsRecommend.title,
            style: TextStyle(
              fontFamily: 'Montserrat',
              fontWeight: FontWeight.w600,
              color: AppColors.primaryText,
              fontSize: duSetFontSize(24),
              height: 1,
            ),
          ),
        ),
        // 一行 3 列
        Container(
          margin: EdgeInsets.only(top: duSetHeight(10)),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              // 分類
              ConstrainedBox(
                constraints: const BoxConstraints(
                  maxWidth: 120,
                ),
                child: Text(
                  newsRecommend.category,
                  style: TextStyle(
                    fontFamily: 'Avenir',
                    fontWeight: FontWeight.normal,
                    color: AppColors.secondaryElementText,
                    fontSize: duSetFontSize(14),
                    height: 1,
                  ),
                  overflow: TextOverflow.clip,
                  maxLines: 1,
                ),
              ),
              // 新增時間
              Container(
                width: duSetWidth(15),
              ),
              ConstrainedBox(
                constraints: const BoxConstraints(
                  maxWidth: 120,
                ),
                child: Text(
                  '• ${duTimeLineFormat(newsRecommend.addtime)}',
                  style: TextStyle(
                    fontFamily: 'Avenir',
                    fontWeight: FontWeight.normal,
                    color: AppColors.thirdElementText,
                    fontSize: duSetFontSize(14),
                    height: 1,
                  ),
                  overflow: TextOverflow.clip,
                  maxLines: 1,
                ),
              ),
              // 更多
              Spacer(),
              InkWell(
                child: Icon(
                  Icons.more_horiz,
                  color: AppColors.primaryText,
                  size: 24,
                ),
                onTap: () {},
              ),
            ],
          ),
        ),
      ],
    ),
  );
}
複製程式碼

藍湖設計稿

lanhuapp.com/url/lYuz1 密碼: gSKl

藍湖現在收費了,所以檢視標記還請自己上傳 xd 設計稿 商業設計稿檔案不好直接分享, 可以加微信聯絡 ducafecat

YAPI 介面管理

yapi.demo.qunar.com/

程式碼

github.com/ducafecat/f…

參考

VSCode 外掛

視訊

相關文章