Flutter笔记:发布一个模块 scale_design - (移动端)设计师尺寸适配工具

举报
jcLee95 发表于 2023/11/13 20:53:40 2023/11/13
【摘要】 scale_design 模块旨在解决移动端适配方面的问题。在Android中,与屏幕相关的两个重要概念是逻辑分辨率和单位。逻辑分辨率是您在应用程序中使用的虚拟分辨率,以确保应用在不同设备上具有一致的外观。为了实现这一目标,Android引入了两个主要单位:dp 和 sp。接下来,分别介绍。dp(密度独立像素)dp 是用于非文字元素的长度单位。它的关键特点是,它是一个与屏幕密度相关的单位。在屏
Flutter笔记
发布一个模块scale_design
设计师尺寸适配工具与常用组件库




这次做的 scale_design 模块旨在解决移动端适配方面的问题。需要指出的是,Flutter是一个跨平台的框架,它不仅仅把目光放在移动端,因此如果你考虑的是桌面端和 Web 开发,那么这个模块目前不适合你。

移动端上,由于屏幕小、屏幕尺寸固定的,他有一个特点是,整个页面的应用窗口即屏幕,一经初始化则大小为定值——不会像PC应用那样,



在Android中,与屏幕相关的两个重要概念是逻辑分辨率和单位。逻辑分辨率是您在应用程序中使用的虚拟分辨率,以确保应用在不同设备上具有一致的外观。为了实现这一目标,Android引入了两个主要单位:dp  sp。接下来,分别介绍。

在这里插入图片描述


dp 是用于非文字元素的长度单位。它的关键特点是,它是一个与屏幕密度相关的单位。在屏幕像素密度为160dpi的情况下,1dp等于1px。然而,在更高或更低的屏幕密度下,1dp的实际像素数会有所不同。这使得在不同设备上相同的dp大小的元素看起来差不多。因此,使用dp可以确保元素的大小在不同屏幕上具有一致性。


sp 是用于文字大小的单位。它类似于dp,但具有一个特殊的特性,即允许用户在设备上调整字体大小,而不会影响其他元素的大小。这对于用户可访问性和个性化设置非常重要。


屏幕密度通常以 dpi (每英寸点数)为单位表示。它描述了在每英寸长度内的像素数,即像素密度。这与dp和px之间的关系密切相关。以下是一些常见的屏幕密度版本以及它们的dp和px之间的转换关系:

屏幕密度版本 DPI dp与px的比例
ldpi(低屏幕密度) 120 1dp = 0.75px
mdpi(中等屏幕密度) 160 1dp = 1px
hdpi(高屏幕密度) 240 1dp = 1.5px
xhdpi(超高屏幕密度) 320 1dp = 2px
xxhdpi(超超高屏幕密度) 480 1dp = 3px
xxxhdpi(超超超高屏幕密度) 640 1dp = 4px

了解不同屏幕密度版本的转换关系可以帮助开发者在应用程序中创建适应不同设备的元素。这对于确保用户体验的一致性至关重要,无论用户使用的是低密度还是高密度屏幕。这也有助于开发者更好地理解Android系统中的长度单位和分辨率的概念,以便更好地开发和设计应用。


为了解决不同屏幕尺寸和密度带来的UI适配问题,需要一种适配方案。提出方案面临的背景是:

移动应用在不同设备上运行,这些设备具有各种不同的屏幕尺寸和像素密度。如果开发者不考虑这些因素,应用可能会在某些设备上看起来不协调或缺乏一致性。

解决这个问题的关键点是使用 逻辑像素单位(dp) 来测量和布局UI元素。

原因很简单:

  • 由于逻辑像素是与屏幕密度无关的单位,因此它们可以确保在不同设备上,相同的dp大小的元素看起来差不多。

为了实现这一思想,需要完成了步骤:

  1. 定义一个基准的设计尺寸,通常由设计师确定,作为UI元素的标准大小。

  2. 使用比例计算,将设计中的尺寸值除以基准设计尺寸,然后乘以当前设备的屏幕尺寸,以获得在当前设备上的适当尺寸。

  3. 提供辅助函数,如 scaleHeightscaleWidthscaleFont等函数,以简化比例计算的过程,使开发者能够轻松地创建响应式布局。

其中, scaleHeight 等函数包含在 scale_design 库中。(scaleHeight、scaleWidth和scaleFont 等函数是基于逻辑像素单位(dp)进行计算的)

这里思想的核心目标是实现UI元素的一致性,无论用户使用的是小屏幕还是大屏幕,低分辨率还是高分辨率的设备。它旨在解决跨设备UI适配的挑战,以提供更好的用户体验。



运行以下命令:

flutter pub add scale_design

这将向你的包的 pubspec.yaml 配置文件依赖字段中添加一行scale_design的记录,并运行一个隐式的flutter pub get,版本默认为当前最新版本。


要在项目中正确使用 scale_design,需要在项目启动之初获取屏幕的基本信息,即初始化。

可以在应用返回跟组件前调用静态初始化方法,比如最简单的情况:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 用首选的标准屏幕尺寸初始化Scale类
    // 这个标准屏幕尺寸一般由设计师(美工)给
    Scale().init(context, standardWidth, standardHeight);

    return MaterialApp(
      // ...
    );
  }
}

很多实际项目,你希望将项目的一些配置全部写在同一个配置文件中,在 mian.dart 写一个初始化函数进行各种初始化操作。就比如在配置文件中:

class LayoutConfigs {
  static double standardWidth = 812.0;
  static double standardHeight = 375.0;
}
import 'package:flutter/material.dart';
import 'package:scale_design/scale_design.dart';
import 'app/config.dart'; // 导入你的配置文件

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // 初始化方法
  Future<void> initialization(BuildContext context) async {
    // 初始化屏幕尺寸比例缩放
    Scale().init(
      context,
      LayoutConfigs.standardWidth,
      LayoutConfigs.standardHeight,
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: FutureBuilder(
        future: initialization(context),
        builder: (context, snapshot) {
          return const MyHomePage(
            title: 'Scale Design Demo',
          );
        },
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ...
      ),
    );
  }
}

字体尺寸适配的关键目标是在不同的设备和屏幕尺寸下提供
“一致的用户体验”,即:
确保文本内容既不会过大导致溢出,也不会太小导致难以阅读

我们这里的具体方案仍然是通过根据屏幕尺寸自动调整字体大小,可以有效应对多样化的移动设备,从小屏幕手机到大屏幕平板电脑。这种字体尺寸适配方案有助于提高用户体验,使应用程序更加灵活和适应不同的使用环境。

但是这个方案如何落实呢?

scale_design 模块的 scaleFont 方法是上述字体尺寸适配方案的具体实现之一。它是一个用于动态计算字体大小的工具,基于屏幕尺寸和设计标准的宽度之间的比例。这个方法的核心思想是将字体大小调整为适应不同屏幕宽度的需求,以保持文本内容的可读性。

具体而言,scaleFont 方法的应用如下:

  • 基准字体大小: 开发者首先定义了一个基准字体大小,通常是在设计阶段确定的。这个基准字体大小是文本在设计标准的屏幕上的理想大小。
  • 屏幕宽度比例: scaleFont 方法获取当前设备的屏幕宽度和设计标准的宽度之间的比例。这个比例反映了当前屏幕与设计标准的相对大小。
  • 动态调整: 使用上述比例,scaleFont 方法将基准字体大小动态调整为适合当前屏幕的字体大小。这确保了文本内容在不同屏幕尺寸下都能够以一致的比例呈现。

事实上,如果屏幕宽度比例为1,意味着当前屏幕的宽度与设计标准的宽度相等,无需进行字体大小的缩放。在这种情况下是以一个特例:

double scaleFont(double fontSize) {
  return fontSize;
}

scaleFont 方法返回的字体大小将与设计时的大小保持一致,不会进行任何缩放。
然而 scale_design 中考虑到对于一般的情况,是按照上面几个步骤的完整实现:

double scaleFont(double fontSize) {
  // 获取当前设备的屏幕宽度
  double screenWidth = Scale.screenWidth;

  // 获取设计标准的宽度
  double standardWidth = Scale.standardWidth;

  // 计算字体大小的比例
  double scale = screenWidth / standardWidth;

  // 根据比例调整字体大小
  return fontSize * scale;
}

即:

double scaleFont(double fontSize) {
  return Scale.screenWidth Scale.standardWidth/ ;
}

scaleWidth 和 scaleHeight

依据设计师的来稿,在项目中有需要用到一般元素的宽高的时候,使用scaleWidth、scaleHeight来实现宽高的dp表示,如:

import 'package:scale_design/scale_design.dart';

// ...
scaleWidth(320.0);
scaleHeight(60.0);

perWidth 和 perHeight

perWidth  perHeight 是两个函数,用于处理屏幕尺寸适配,具体功能如下:

  1. perWidth 函数:

    • perWidth 用于获取屏幕宽度的 1/n 部分,其中 n 是传入的参数。
    • 它接受一个 n 参数,表示要获取屏幕宽度的多少分之一,例如 n 为 2 时,表示获取屏幕宽度的一半。
    • 如果 n 大于 0,函数将返回屏幕宽度除以 n 的结果;否则,会抛出异常。
    • 这个函数通常用于根据屏幕宽度的比例来设置 UI 元素的宽度,以实现屏幕尺寸适配。
  2. perHeight 函数:

    • perHeight 用于获取屏幕高度的 1/n 部分,其中 n 是传入的参数。
    • 它接受一个 n 参数,表示要获取屏幕高度的多少分之一,例如 n 为 3 时,表示获取屏幕高度的三分之一。
    • 如果 n 大于 0,函数将返回屏幕高度除以 n 的结果;否则,会抛出异常。
    • 类似于 perWidthperHeight 主要用于根据屏幕高度的比例来设置 UI 元素的高度,以实现屏幕尺寸适配。

这两个函数对于创建响应式布局和动态适应不同屏幕尺寸的应用程序非常有用。开发者可以使用它们来设置 UI 元素的尺寸,以适应不同的设备屏幕,从而提供更好的用户体验。

import 'package:scale_design/scale_design.dart';

// ...
perWidth(2);   // 屏幕宽度的1/2
perHeight(2);   // 屏幕宽度的1/2

scaleFont

scaleFont 函数是用于根据屏幕大小进行字体大小适配的工具。例如:

Text(
  'Hello, World!',
  style: TextStyle(fontSize: scaleFont(18)),
);

不过,这可以使用使用对应于 Text 组件的 T 组件表示,那将更加简洁(T组件也需要导入该scale design库)。


包装的目的在于更加简单地使用一些常见地组件,比如 Flutter 中的 ElevatedButton ,在 Scale Design 库中的对应品是 ElevatedBtn,不再需要使用 scaleXXX 这些函数来指定宽高,传入的 double 数值已经在内部做了转换。并且默认情况下,不需要使用 Text来在按钮上实现文字,直接传入字符串默认就是文字,除非你硬性指定 child 属性,这时传入的text不再有效。

一个例子是,我们在项目中可以直接基于 ElevatedBtn 二次封装直接用于特定功能的组件:

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

class SubmitButton extends StatelessWidget {
  final String text;
  final Function()? onPressed;
  final double width;
  final double height;
  final double size;
  final Color color;

  const SubmitButton(
    this.text, {
    super.key,
    this.onPressed,
    this.width = 320.0,
    this.height = 49.0,
    this.size = 18.0,
    this.color = const Color.fromARGB(255, 255, 98, 7),
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedBtn(
      text,
      onPressed: onPressed,
      width: width,
      height: height,
      fontSize: size,
      backgroundColor: color,
    );
  }
}

class IconBtn extends StatelessWidget {
  final IconData icon;
  final Function()? onPressed;

  const IconBtn({super.key, required this.icon, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return IconButton(
      icon: Icon(icon),
      onPressed: onPressed,
      iconSize: scaleFont(24), // 调整图标大小
    );
  }
}

当然这个 SubmitButton 的 width、height 也是基于 dp 的。然后我们可以将这个按钮用于它适应的具体化场景,比如下面是我一个项目中登陆页面的表单提交:

SubmitButton("继续", onPressed: () async {
  if (_formKey.currentState!.validate()) {
    _formKey.currentState!.save();
    await authService.login().then((String? token) {
      if (token != '' && LoginViewController.to.remember) {
        LoginViewController.to.saveLoginInfo(
          authService.email.value,
          authService.password.value,
        );
        goToMallViewPage(index: 0, type: 'offAll');
      } else {
        LoginViewController.to.clearSavedPassword(
          authService.email.value,
        );
      }
    });
  }
}

在这里插入图片描述

类似的,scale_design 中还有 T 组件,是Text组件的替代,TSpan组件相当于对应的TextSpan组件,等等。不过也不全是这类对于原生的包装,也有一些组件是常见于项目的,但本身并不是基于 dp 封装的,需要使用 scaleXXX 这些函数传入对应的dp值。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。