仿写新闻客户端

语言: 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}'); } )); }

后续更新...

往期精彩推荐

如果读者觉得文章还可以,不防一键三连:关注➕点赞➕收藏