仿寫新聞客戶端

語言: CN / TW / HK

攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第17天,點選檢視活動詳情

新建專案,加入圖片字型,編寫歡迎介面

新建專案

flutter create jimmy_flutter_demo

加入圖片字型

在根目錄上新建一個 assets 資料夾

bash assets fonts // 存放字型 images // 存放圖片

pubspec.yaml 檔案設定 images 的路徑內容:

bash assets: - assets/images/

pubspec.yaml 檔案設定 fonts 的路徑內容:

bash fonts: - family: Avenir fonts: - asset: assets/fonts/Avenir-Book.ttf weight: 400 - family: Montserrat fonts: - asset: assets/fonts/Montserrat-SemiBold.ttf weight: 600

編寫歡迎頁面

新增螢幕適配的包。

bash # 螢幕適配 flutter_screenutil: ^1.0.2

拉取新包:flutter pub get 獲取直接安裝 flutter pub add flutter_screenutil

設定螢幕見 lib/common/utils/screen.dart

設定這個 app 的一些色調,見 lib/common/values/colors.dart

新增歡迎頁面 lib/pages/welcome/welcomePage.dart

更改入口檔案 lib/main.dart

```bash import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:jimmy_flutter_demo/pages/welcome/welcomePage.dart';

void main() => runApp(MyApp());

// 檢視 http://github.com/OpenFlutter/flutter_screenutil/blob/master/README_CN.md

class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { //填入設計稿中裝置的螢幕尺寸,單位dp return ScreenUtilInit( designSize: const Size(360, 690), minTextAdapt: true, splitScreenMode: true, builder: (context, child) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'First Method', theme: ThemeData( primarySwatch: Colors.blue, textTheme: Typography.englishLike2018.apply(fontSizeFactor: 1.sp), ), home: child, ); }, child: const WelcomePage(), ); } } ```

這裡需要對 flutter_screenutil 做全域性的引入。

然後對歡迎頁面進行新增,內容如下:

```bash import 'package:flutter/material.dart'; import 'package:jimmy_flutter_demo/common/utils/utils.dart'; import 'package:jimmy_flutter_demo/common/values/values.dart';

class WelcomePage extends StatelessWidget { const WelcomePage({Key? key}) : super(key: key);

// 頁頭標題 Widget _buildPageHeaderTitle() { return Container( margin: EdgeInsets.only(top: duSetHeight(65)), child: Text( "Features", textAlign: TextAlign.center, style: TextStyle( color: AppColors.primaryText, fontFamily: "Montserrat", fontWeight: FontWeight.w600, fontSize: duSetFontSize(24), ), ), ); }

// 頁頭說明 Widget _buildPageHeaderDetail() { return Container( width: duSetWidth(242), height: duSetHeight(70), margin: EdgeInsets.only(top: duSetHeight(14)), child: Text( "The best of news channels all in one place. Trusted sources and personalized news for you.", textAlign: TextAlign.center, style: TextStyle( color: AppColors.primaryText, fontFamily: "Avenir", fontWeight: FontWeight.normal, fontSize: duSetFontSize(16), height: 1.3, ), ), ); }

// 特性說明 // 寬度 80 + 20 + 195 = 295 Widget _buildFeatureItem(String imageName, String intro, double marginTop) { return Container( width: duSetWidth(295), height: duSetHeight(80), margin: EdgeInsets.only(top: duSetHeight(marginTop)), child: Row( children: [ Container( width: duSetWidth(80), height: duSetHeight(80), child: Image.asset( "assets/images/$imageName.png", fit: BoxFit.none, ), ), const Spacer(), Container( width: duSetWidth(195), child: Text( intro, textAlign: TextAlign.center, style: TextStyle( color: AppColors.primaryText, fontFamily: "Avenge", fontWeight: FontWeight.normal, fontSize: duSetFontSize(16), ), ), ), ], ), ); }

// 開始按鈕 Widget _buildStartButton() { return Container( width: duSetWidth(295), height: duSetHeight(44), margin: EdgeInsets.only(bottom: duSetHeight(20)), child: TextButton( onPressed: () => {}, style: ButtonStyle( backgroundColor: MaterialStateProperty.all(AppColors.primaryElement), textStyle: MaterialStateProperty.all(const TextStyle( color: AppColors.primaryElementText, )), ), child: const Text("Get started"), ), ); }

@override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( children: [ _buildPageHeaderTitle(), _buildPageHeaderDetail(), _buildFeatureItem( "feature-1", "Compelling photography and typography provide a beautiful reading", 86, ), _buildFeatureItem( "feature-2", "Sector news never shares your personal data with advertisers or publishers", 40, ), _buildFeatureItem( "feature-3", "You can get Premium to unlock hundreds of publications", 40, ), const Spacer(), _buildStartButton() ], ), ), ); } } ```

上面的 TextButton 本來使用的是 FlatButton, 但是它已經被棄用了。

相關的效果圖:

第一課效果圖.png

靜態路由,元件抽取,登陸註冊頁面

為了實現靜態路由,我們來定義下登陸和註冊的頁面:

  • 登入頁 lib/pages/sign_in/sign_in.dart
  • 註冊頁 lib/pages/sign_up/sign_up.dart
  • 靜態路由 lib/routes.dart

```bash // 登陸頁面初始化 import 'package:flutter/material.dart';

class SignInPage extends StatelessWidget { SignInPage({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( children: [ Text('Sign In'), Text('Hello'), Text('World'), ], ), ), ); } }

```

```bash // 註冊頁面初始化 import 'package:flutter/material.dart';

class SignUpPage extends StatelessWidget { SignUpPage({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( children: [ Text('Sign Up'), Text('Hello'), Text('World'), ], ), ), ); } } ```

```bash // 路由資訊 import 'package:jimmy_flutter_demo/pages/sign_in/sign_in.dart'; import 'package:jimmy_flutter_demo/pages/sign_up/sign_up.dart';

// 靜態路由 var staticRoutes = { "/sign-in": (context) => SignInPage(), // 登入 "/sign-up": (context) => SignUpPage(), // 註冊 };

```

安裝使用 fluttertoast 報錯的解決:

bash [Parse Issue (Xcode): Module 'fluttertoast' not found

解決方案:

bash 1. 進入專案 ios 資料夾,刪除檔案  **"Podfile"**  和  **"Podfile. Lock"**   2. ios 目錄下,在終端執行 `flutter clean` 命令列 3. 回到專案根目錄,在終端執行 `flutter pub get` 4. ios 目錄下,在終端執行 `pod install`

元件 appBar 拆分過程的報錯:The argument type 'Widget' can't be assigned to the parameter type 'PreferredSizeWidget?'

解決方案:

``bash 因為我們定義了 appBar 元件是Widget,我們應該定義其為PreferredSizeWidget`。

Widget transparentAppBar({ required BuildContext context, required List actions, }) {}

// 改為

PreferredSizeWidget transparentAppBar({ required BuildContext context, required List actions, }) {} ```

元件抽取

比如: toast

```bash import 'dart:ui';

import 'package:flutter/material.dart'; import 'package:jimmy_flutter_demo/common/utils/utils.dart'; import 'package:fluttertoast/fluttertoast.dart';

Future toastInfo({ required String msg, Color backgroundColor = Colors.black, Color textColor = Colors.white, }) async { return await Fluttertoast.showToast( msg: msg, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.TOP, timeInSecForIosWeb: 1, backgroundColor: backgroundColor, textColor: textColor, fontSize: duSetFontSize(16), ); } ```

又比如:appBar

```bash import 'package:flutter/material.dart'; import 'package:jimmy_flutter_demo/common/values/values.dart';

// 透明背景的 AppBar PreferredSizeWidget transparentAppBar({ // 使用 PreferredSizeWidget 定義,而不是 Widget required BuildContext context, required List actions, }) { return AppBar( backgroundColor: Colors.transparent, elevation: 0, title: const Text(''), leading: IconButton( icon: const Icon( Icons.arrow_back, color: AppColors.primaryText, ), onPressed: () { Navigator.pop(context); }, ), actions: actions, ); }

```

Dio 的封裝使用

  1. 處理報錯:Non-nullable instance field '_storage' must be initialized.\ Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.

解決方案,在變數 _storage 新增 late 修飾符。

  1. 處理報錯 The argument type 'void Function(RequestOptions)' can't be assigned to the parameter type 'void Function(RequestOptions, RequestInterceptorHandler)?' 封裝 dio 的時候出現。

解決方案,可以嘗試方法如下:

bash initializeInterceptor(){ _dio.interceptors.add(InterceptorsWrapper( onError: (error, errorInterceptorHandler ){ print(error.message); }, onRequest: (request, requestInterceptorHandler){ print("${request.method} | ${request.path}"); }, onResponse: (response, responseInterceptorHandler) { print('${response.statusCode} ${response.statusCode} ${response.data}'); } )); }

後續更新...

往期精彩推薦

如果讀者覺得文章還可以,不防一鍵三連:關注➕點贊➕收藏