适用于 Flutter 应用程序的自适应材料组件

2022-09-16  本文已影响0人  蜗牛是不是牛

Flutter 对移动、桌面和 Web 的支持给我们的生活带来了新的挑战:支持不同的屏幕尺寸并调整我们的设计。

大多数时候,开发人员倾向于考虑他们拥有的设备并相应地构建他们的应用程序。但在所有目标设备上都有不同的屏幕尺寸、方向、像素密度。作为一名优秀的开发人员,我们应该相应地调整您的设计。

这个话题的重要性已经引起了很多人的关注,所以很多人开始创建关于它的内容和解决方案。引起我注意的解决方案之一是 Material Design 团队的自适应材料组件库

在这篇博文中,您将了解如何使用自适应材料组件库,以及它如何帮助您轻松实现自适应设计。

一点历史

如果您不想阅读一些谈论他的学习经历的家伙,您可以跳过并转到下一部分进行实施

Flutter 开发人员长期以来一直依赖LayoutBuilder、MediaQuery.of、FractionallySizedBox、AspectRatio、Column、Flexible和许多其他小部件来实现应用程序的实际自适应设计。但他们总是有类似的问题,例如“我从平板电脑到手机再回到电脑屏幕的断点到底是什么?”。他们寻找了几种不同的资源,但找不到明确的答案。一方面他们的设计师可能会给他们一些建议,但另一方面他们只会问自己“难道没有更好的方法来做到这一点吗?”

幸运的是,Flutter 的人们已经听到并感受到了 Flutter 开发人员试图实现这一目标的问题和绝望的尝试,他们想要提供帮助。他们开始研究一个名为flutter_adaptive_scaffold的库。这个库处理屏幕大小的变化、导航和在正确的时间显示应用程序的适当部分实际上是社区的游戏规则改变库。


当我在 Flutter Vikings 的演讲中玩弄这个库时,我意识到flutter_adaptive_scaffold库很好,但它太自以为是了。如果我只需要 sccren 更改侦听器,我就无法拥有它,因为我受制于他们实现 UI 的方式。我仍然保留它在我的演讲中向人们展示它是如何在 Flutter 应用程序上工作的,但我的一部分渴望更好的方法。

在 Flutter 期间,我主持了几次演讲,我有机会看到一些伟大人物的精彩演讲。其中之一来自Anthony Robledo关于google_fonts的文章,但我关注的是他提到的自适应材料库。所以我开始深入研究,现在你会从中看到我的发现。

英雄:adaptive_components

在我寻找一个易于使用的自适应设计库时,adaptive_components是处理不同屏幕尺寸的 UI 变化的最直接的方法。该库遵循不同屏幕尺寸的材料设计指南

截至 2022 年 9 月 15 日,adaptive_components库有两个实体组件。其中一个是AdaptiveContainer,另一个是AdaptiveColumn

AdaptiveContainer是一个专用容器,可让您创建具有自适应约束的容器。它在内部使用LayoutBuilder来检查容器是否处于所需的约束中。您可以将AdaptiveContainer与多个子小部件(例如 Column、Stack 等)一起使用。

AdaptiveColumn 是一个小部件,用于保留子小部件并将它们放置在Wrap小部件中,并使用LayoutBuilder检索的约束信息。AdaptiveColumnAdaptiveContainer用作子元素,因为AdaptiveContainer有一个columnSpan属性来帮助放置子元素。

如何使用自适应容器?

通过使用AdaptiveContainer,您将能够创建上面的应用程序。该应用程序会将 UI 拉伸到一个点,并在一个点之后显示详细视图,而无需我们定义任何“断点”。

要使用AdaptiveContainerAdaptiveColumn,我们需要将Adaptive_components库添加到我们的项目中。打开您的pubspec.yamldependencies文件并在标签下添加以下内容:


dependencies:
  flutter:
    sdk: flutter

  adaptive_components: ^0.0.7

添加此调用flutter pub get以将库下载到您的项目后。一旦你有了项目,现在是时候使用这个库了:

import 'package:adaptive_components/adaptive_components.dart';
import 'package:adaptive_libraries_showcase/game.dart';
import 'package:adaptive_libraries_showcase/game_detail.dart';
import 'package:adaptive_libraries_showcase/game_list.dart';
import 'package:adaptive_libraries_showcase/main_page_large.dart';
import 'package:flutter/material.dart';

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

class AdaptiveContainerExample extends StatefulWidget {
  const AdaptiveContainerExample({Key? key}) : super(key: key);

  @override
  State<AdaptiveContainerExample> createState() =>
      _AdaptiveContainerExampleState();
}

class _AdaptiveContainerExampleState extends State<AdaptiveContainerExample> {
  Game game = games.first;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: const Color(0xFF030403),
        // (1)
        body: Stack(
          children: [
            // (2)
            AdaptiveContainer(
              // (3)
              constraints: const AdaptiveConstraints(
                xsmall: false,
                small: false,
                medium: false,
                large: true,
                xlarge: true,
              ),
              child: Row(
                children: [
                  Expanded(
                    child: GameList(
                      games: games,
                      onGameSelected: (game) {
                        setState(() {
                          this.game = game;
                        });
                      },
                    ),
                  ),
                  Expanded(
                    child: GameDetail(game: game),
                  ),
                ],
              ),
            ),
            AdaptiveContainer(
              constraints: const AdaptiveConstraints(
                xsmall: true,
                small: true,
                medium: true,
                large: false,
                xlarge: false,
              ),
              child: GameList(
                games: games,
                onGameSelected: (game) {},
              ),
            ),
          ],
        ),
      ),
    );
  }
}

如何使用自适应列?

使用AdaptiveColumn不是火箭科学。每当您计划使用(像任何其他多子小部件一样)时,您都会将其添加到您的代码中并在其中添加一些子小部件。

您之前可能没有意识到这一点(因为您还没有看到源代码),但是在游戏详细信息页面中,我们实际上是使用AdaptiveColumn来布置子小部件。在上面的 UI 中,您可以看到一些小部件在同一“行”中,而一些小部件是一个接一个地绘制的。AdaptiveColumnAdaptiveContainer的组合实际上帮助我们实现了这一点。

import 'package:adaptive_components/adaptive_components.dart';
import 'package:adaptive_libraries_showcase/game.dart';
import 'package:adaptive_libraries_showcase/game_item_small.dart';
import 'package:flutter/material.dart';

class GameDetail extends StatelessWidget {
  const GameDetail({
    required this.game,
    Key? key,
  }) : super(key: key);

  final Game game;

  @override
  Widget build(BuildContext context) {
    final similarGames = games.where((element) =>
        element.category == game.category && element.name != game.name);
    return Padding(
      padding: const EdgeInsets.only(top: 8),
      // (1)
      child: Align(
        alignment: Alignment.topCenter,
        // (2)
        child: AdaptiveColumn(
          children: [
            // (3)
            AdaptiveContainer(
              // (4)
              columnSpan: 12,
              color: Colors.blue,
              child: Image.network(
                game.backdropImage,
                height: 300,
                fit: BoxFit.cover,
              ),
            ),
            AdaptiveContainer(
              columnSpan: 6,
              child: Text(
                game.name,
                style: Theme.of(context)
                    .textTheme
                    .headline2
                    ?.copyWith(color: Colors.white),
              ),
            ),
            AdaptiveContainer(
              columnSpan: 6,
              child: Text(
                game.description,
                style: Theme.of(context)
                    .textTheme
                    .headline6
                    ?.copyWith(color: Colors.white),
              ),
            ),
            AdaptiveContainer(
              columnSpan: 12,
              child: Text(
                'Other ${game.category.name} games',
                style: Theme.of(context)
                    .textTheme
                    .headline6
                    ?.copyWith(color: Colors.white),
              ),
            ),
            // (5)
            ...similarGames.map(
              (e) => AdaptiveContainer(
                columnSpan: 1,
                child: GameItemSmall(game: e),
              ),
            ),
            AdaptiveContainer(
              columnSpan: 12,
              child: Text(
                game.releaseDate,
                style: Theme.of(context)
                    .textTheme
                    .headline6
                    ?.copyWith(color: Colors.white),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

导航器:adaptive_navigation

Material Design 对导航有强烈的看法。他们使用此包根据屏幕大小使用 Drawer、NavigationRail 或 BottomNavigationBar。每个导航目的地都是预定义的。

为了使用AdaptiveNavigationScaffold,我们需要将Adaptive_navigation库添加到我们的项目中。打开您的pubspec.yamldependencies文件并在标签下添加以下内容:


dependencies:
  flutter:
    sdk: flutter

  adaptive_navigation: ^0.0.7

添加此调用flutter pub get以将库下载到您的项目后。一旦你有了项目,现在你可以在你的项目中使用这个库:

import 'package:adaptive_libraries_showcase/game.dart';
import 'package:adaptive_libraries_showcase/game_list.dart';
import 'package:adaptive_navigation/adaptive_navigation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: DefaultScaffoldDemo()));
}

class DefaultScaffoldDemo extends StatefulWidget {
  const DefaultScaffoldDemo({Key? key}) : super(key: key);

  @override
  State<DefaultScaffoldDemo> createState() => _DefaultScaffoldDemoState();
}

class _DefaultScaffoldDemoState extends State<DefaultScaffoldDemo> {
  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    // (1)
    return AdaptiveNavigationScaffold(
      selectedIndex: _selectedIndex,
      // (2)
      bottomNavigationOverflow: 10,
      // (3)
      onDestinationSelected: (index) {
        setState(() {
          _selectedIndex = index;
        });
      },
      destinations: _getDestinations(),
      body: GameList(
        games: games
            .where(
              (element) => element.category == Category.values[_selectedIndex],
        )
            .toList(growable: false),
        onGameSelected: (value) {},
      ),
    );
  }

  List<AdaptiveScaffoldDestination> _getDestinations() {
    int index = -1;
    return Category.values.map(
          (e) {
        index++;
        // (4)
        return AdaptiveScaffoldDestination(
          title: e.name,
          icon: icons[index],
        );
      },
    ).toList(growable: false);
  }
}

const icons = [
  Icons.accessibility_new,
  Icons.power_settings_new_outlined,
  Icons.open_in_new_off_rounded,
  Icons.backpack,
  Icons.cable_outlined,
  Icons.dark_mode_outlined,
  Icons.earbuds,
  Icons.face_outlined,
  Icons.games,
  Icons.hail
];

您可以充分利用它的用途。您可以为每个选定元素显示不同的 UI,或者像我一样,过滤掉与目标无关的任何数据。

如果您对用于选择导航类型的导航器的断点不满意,您可以覆盖该navigationTypeResolver属性,但 IMO,如果您想要一种自以为是的导航方式,那么坚持使用库是有意义的。

完整包:flutter_adaptive_scaffold

自 2022 年 9 月 15 日起,包adaptive_scaffold重命名为flutter_adaptive_scaffold,因为其他人已经获得了adaptive_scaffold这个名称。:)

但是这个AdaptiveScaffold是什么,我们为什么要使用它呢?AdaptiveScaffold对来自用户、设备和屏幕元素的输入做出反应,并根据 Material 3 指南渲染您的 Flutter 应用程序。它将导航和组件库与组件之间的精美动画相结合。

为了使用任一AdaptiveScaffold,我们需要将Adaptive_navigation库添加到我们的项目中。打开您的pubspec.yamldependencies文件并在标签下添加以下内容:


dependencies:
  flutter:
    sdk: flutter

  flutter_adaptive_scaffold: ^0.0.3

添加此调用flutter pub get以将库下载到您的项目后。是时候使用这个库了:

import 'package:adaptive_libraries_showcase/game.dart';
import 'package:adaptive_libraries_showcase/game_detail.dart';
import 'package:adaptive_libraries_showcase/game_list.dart';
import 'package:adaptive_libraries_showcase/main_navigation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: MyHomePage());
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;
  Game game = games.first;

  @override
  Widget build(BuildContext context) {
    // (1)
    return BottomNavigationBarTheme(
      data: const BottomNavigationBarThemeData(
        unselectedItemColor: Colors.black,
        selectedItemColor: Colors.black,
        backgroundColor: Colors.white,
      ),
      // (2)
      child: AdaptiveScaffold(
        selectedIndex: _selectedIndex,
        onSelectedIndexChange: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
        useDrawer: false,
        // (3)
        destinations: _getDestinations(),
        // (4)
        body: (_) => Row(
          children: [
            Expanded(
              child: GameList(
                games: games,
                onGameSelected: (game) {
                  setState(() {
                    this.game = game;
                  });
                },
              ),
            ),
            Expanded(
              child: GameDetail(
                game: game,
                hasDarkText: true,
              ),
            ),
          ],
        ),
        // (5)
        smallBody: (_) => GameList(
          games: games
              .where(
                (element) =>
                    element.category == Category.values[_selectedIndex],
              )
              .toList(growable: false),
          onGameSelected: (value) {},
        ),
        // (6)
        // secondaryBody: AdaptiveScaffold.emptyBuilder,
        // (7)
        // smallSecondaryBody: AdaptiveScaffold.emptyBuilder,
      ),
    );
  }

  // (8)
  List<NavigationDestination> _getDestinations() {
    int index = -1;
    return Category.values.map(
      (e) {
        index++;
        return NavigationDestination(
          label: e.name,
          icon: Icon(icons[index]),
        );
      },
    ).toList(growable: false);
  }
}

现在让我们检查上面的代码:

结论

这篇博文的目的是让您了解这个在幕后进行的惊人项目。他们有几个我在此过程中发现的问题,但随着更多的关注和使用,我相信它会成为一个好地方。

文章来源:https://salih.dev/adaptive-material-components-for-your-flutter-applications

上一篇 下一篇

猜你喜欢

热点阅读