openplanning

Bài thực hành Flutter thiết kế giao diện trang (1)

  1. Phác thảo thiết kế
  2. ImageSection
  3. TitleSection
  4. ButtonSection
  5. DescriptionSection
  6. MainScreen
Bài học này chúng ta sẽ thiết kế một trang giống như hình minh hoạ dưới đây.
File ảnh được sử dụng trong bài học này:
Bài viết mà bạn đang đọc nằm trong khoá học Flutter miễn phí dưới đây của chúng tôi, nơi bạn có thể tìm thấy các bài thực hành khác để tự học Flutter.
  • Khoá học Flutter miễn phí cho người mới bắt đầu

1. Phác thảo thiết kế

Để thiết kế một trang bạn cần phải phân chia nó thành các phần (section) riêng biệt:
TitleSection:
ButtonSection:

2. ImageSection

ImageSection là nơi hiển thị một ảnh. Trong vùng này chúng ta sẽ hiển thị một ảnh với chiều cao cố định và chiều rộng lấp đầy màn hình.
image_section.dart
import 'package:flutter/material.dart';

class ImageSection extends StatelessWidget {
  final String imageUrl;

  const ImageSection({required this.imageUrl, super.key});

  @override
  Widget build(BuildContext context) {
    return Image.network(
      imageUrl,
      width: double.infinity,
      height: 240,
      fit: BoxFit.fitWidth,
    );
  }
}

3. TitleSection

TitleSection là một Row với 3 widget con. Trong đó Widget đầu tiên có chiều rộng lớn nhất có thể.
title_section.dart
import 'package:flutter/material.dart';

class TitleSection extends StatelessWidget {
  final String name;
  final String location;

  const TitleSection({
    super.key,
    required this.name,
    required this.location,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(32),
      child: Row(
        children: [
          Expanded(
            child: buildTitle(),
          ),
          Icon(
            Icons.star,
            color: Colors.red[500],
          ),
          const Text('41'),
        ],
      ),
    );
  }

  Widget buildTitle() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.only(bottom: 8),
          child: Text(
            name,
            style: const TextStyle(
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        Text(
          location,
          style: TextStyle(
            color: Colors.grey[500],
          ),
        ),
      ],
    );
  }
}

4. ButtonSection

ButtonSection là một Row chứa 3 button tuỳ biến (CustomVerticalButton).
Chúng ta tạo lớp CustomVerticalButton như là một button tuỳ biến và có thể tái sử dụng nó ở nhiều nơi.
custom_vertical_button.dart
import 'package:flutter/material.dart';

class CustomVerticalButton extends StatelessWidget {
  const CustomVerticalButton({
    super.key,
    required this.color,
    required this.iconData,
    required this.label,
  });

  final Color color;
  final IconData iconData;
  final String label;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(iconData, color: color),
        Padding(
          padding: const EdgeInsets.only(top: 8),
          child: Text(
            label,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}
Sử dụng CustomVerticalButton để tạo 3 button trong ButtonSection:
button_section.dart
import 'package:flutter/material.dart';
import 'custom_vertical_button.dart';

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

  @override
  Widget build(BuildContext context) {
    final Color color = Theme.of(context).primaryColor;
    return SizedBox(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          CustomVerticalButton(
            color: color,
            iconData: Icons.call,
            label: 'CALL',
          ),
          CustomVerticalButton(
            color: color,
            iconData: Icons.near_me,
            label: 'ROUTE',
          ),
          CustomVerticalButton(
            color: color,
            iconData: Icons.share,
            label: 'SHARE',
          ),
        ],
      ),
    );
  }
}

5. DescriptionSection

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

class DescriptionSection extends StatelessWidget {
  final String description;

  const DescriptionSection({
    super.key,
    required this.description,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(32),
      child: Text(
        description,
        softWrap: true,
      ),
    );
  }
}

6. MainScreen

Cuối cùng lắp ráp tất cả các phần (section) lại với nhau để tạo ra một trang.
main_screen.dart
import 'package:flutter/material.dart';
import 'button_section.dart';
import 'image_section.dart';
import 'description_section.dart';
import 'title_section.dart';

class MainScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Flutter Layout")),
      body: Center(
        child: _buildMainWidget(context),
      ),
    );
  }

  Widget _buildMainWidget(BuildContext context) {
    return const SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          ImageSection(
            imageUrl:
                'https://raw.githubusercontent.com/o7planning/rs/master/flutter/mountain-1.webp',
          ),
          SizedBox(height: 20),
          TitleSection(
            name: "Oeschinen Lake Campground",
            location: "Kandersteg, Switzerland",
          ),
          SizedBox(height: 20),
          ButtonSection(),
          SizedBox(height: 20),
          DescriptionSection(
            description:
                'Lake Oeschinen lies at the foot of the Blüemlisalp in the '
                'Bernese Alps. Situated 1,578 meters above sea level, it '
                'is one of the larger Alpine Lakes. A gondola ride from '
                'Kandersteg, followed by a half-hour walk through pastures '
                'and pine forest, leads you to the lake, which warms to 20 '
                'degrees Celsius in the summer. Activities enjoyed here '
                'include rowing, and riding the summer toboggan run.',
          ),
        ],
      ),
    );
  }
}
main.dart
import 'package:flutter/material.dart';

import 'main_screen.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: MainScreen(),
    );
  }
}

Các hướng dẫn lập trình Flutter

Show More