Flutter 仿写新闻客户端

·  阅读 9053

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情

新建项目,加入图片字体,编写欢迎界面

新建项目

flutter create jimmy_flutter_demo

加入图片字体

在根目录上新建一个 assets 文件夹

assets
  fonts // 存放字体
  images // 存放图片
复制代码

pubspec.yaml 文件设定 images 的路径内容:

assets:
  - assets/images/
复制代码

pubspec.yaml 文件设定 fonts 的路径内容:

fonts:
  - family: Avenir
    fonts:
      - asset: assets/fonts/Avenir-Book.ttf
        weight: 400
  - family: Montserrat
    fonts:
      - asset: assets/fonts/Montserrat-SemiBold.ttf
        weight: 600
复制代码

编写欢迎页面

添加屏幕适配的包。

  # 屏幕适配
  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

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());

// 查看 https://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 做全局的引入。

然后对欢迎页面进行添加,内容如下:

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: <Widget>[
            _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
// 登陆页面初始化
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: <Widget>[
            Text('Sign In'),
            Text('Hello'),
            Text('World'),
          ],
        ),
      ),
    );
  }
}

复制代码
// 注册页面初始化
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: <Widget>[
            Text('Sign Up'),
            Text('Hello'),
            Text('World'),
          ],
        ),
      ),
    );
  }
}
复制代码
// 路由信息
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 报错的解决:

[Parse Issue (Xcode): Module 'fluttertoast' not found
复制代码

解决方案:

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?'

解决方案:

因为我们定义了 appBar 组件是 `Widget`,我们应该定义其为 `PreferredSizeWidget`。

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

// 改为

PreferredSizeWidget transparentAppBar({
  required BuildContext context,
  required List<Widget> actions,
}) {}
复制代码

组件抽取

比如: toast

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

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

// 透明背景的 AppBar
PreferredSizeWidget transparentAppBar({
  // 使用 PreferredSizeWidget 定义,而不是 Widget
  required BuildContext context,
  required List<Widget> 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 的时候出现。

解决方案,可以尝试方法如下:

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}');
        }
    ));
  }
复制代码

后续更新...

往期精彩推荐

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

分类:
iOS
收藏成功!
已添加到「」, 点击更改