メインコンテンツへ移動

เนื้อหาภาษาไทย / タイ語のコンテンツ

システムガイド 内容確認済み

การสร้าง Custom Render Objects ใน Flutter

บทความนี้กล่าวถึงการสร้าง Custom Render Objects ใน Flutter เพื่อควบคุมการแสดงผล UI ได้อย่างละเอียด โดยเน้นที่การทำความเข้าใจ `RenderObject`, การสร้าง `CustomRen...

การสร้าง Custom Render Objects ใน Flutter

ใน Flutter, Widget เป็นเพียง "พิมพ์เขียว" ของสิ่งที่ต้องการแสดงผลบนหน้าจอ การแสดงผลจริงถูกจัดการโดย Render Objects ซึ่งเป็นส่วนสำคัญของ Render Tree ที่ Flutter ใช้เพื่อวาด UI Render Objects มีหน้าที่คำนวณขนาดและตำแหน่งของตัวเอง รวมถึงวาดตัวเองบนหน้าจอ บทความนี้จะพาคุณไปสำรวจการสร้าง Custom Render Objects เพื่อควบคุมการแสดงผล UI ได้อย่างละเอียด

บทความก่อนหน้า เราได้เรียนรู้การใช้งาน TensorFlow Lite (TFLite) ใน Flutter เพื่อเพิ่มความสามารถด้าน Machine Learning ให้กับแอปของเรา ในบทความนี้ เราจะเปลี่ยนมุมมองมาเน้นที่การปรับแต่งการแสดงผล UI โดยการสร้าง Custom Render Objects ซึ่งเป็นอีกหนึ่งวิธีที่ช่วยให้แอป Flutter ของคุณมีความยืดหยุ่นและตอบโจทย์ความต้องการเฉพาะเจาะจงได้มากขึ้น

ทำความเข้าใจ RenderObject

RenderObject คือคลาสพื้นฐานสำหรับทุกสิ่งที่ถูกวาดบนหน้าจอใน Flutter มันมีหน้าที่หลัก 3 อย่าง:

  • PerformLayout: คำนวณขนาดและตำแหน่งของ Render Object และลูกๆ ของมัน
  • Paint: วาด Render Object บน Canvas
  • HitTest: ตรวจสอบว่าตำแหน่งที่กำหนดอยู่ภายในขอบเขตของ Render Object หรือไม่

เมื่อคุณสร้าง Custom Render Object คุณจะต้องสืบทอดจากคลาส RenderObject หรือคลาสย่อยของมัน (เช่น RenderBox) และ override method เหล่านี้เพื่อให้ได้พฤติกรรมที่คุณต้องการ

เหตุผลเชิงออกแบบ: ทำไมต้องมี RenderObject แยกจาก Widget? การแยกส่วนนี้ช่วยให้ Flutter ทำงานได้อย่างมีประสิทธิภาพ Widget เป็น immutable (เปลี่ยนแปลงไม่ได้) และถูกสร้างใหม่เมื่อมีการเปลี่ยนแปลงข้อมูล RenderObject เป็น mutable (เปลี่ยนแปลงได้) และถูกนำกลับมาใช้ใหม่เมื่อเป็นไปได้ การแยกส่วนนี้ช่วยลดภาระในการสร้างและทำลาย object จำนวนมาก ทำให้ UI ของ Flutter ทำงานได้อย่างราบรื่น

การสร้าง Custom RenderBox

RenderBox เป็นคลาสย่อยของ RenderObject ที่ใช้สำหรับ Render Objects ที่มีขอบเขตเป็นสี่เหลี่ยมผืนผ้า ซึ่งเป็นกรณีที่พบบ่อยที่สุดในการสร้าง Layout UI เราจะเริ่มต้นด้วยการสร้าง Custom RenderBox อย่างง่าย:

import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

class CustomBox extends RenderBox {
  Color color;

  CustomBox({required this.color});

  @override
  void performLayout() {
    size = constraints.biggest; // กำหนดขนาดเป็นขนาดที่ใหญ่ที่สุดที่ Constraints อนุญาต
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    final canvas = context.canvas;
    final rect = offset & size; // สร้าง Rect จาก Offset และ Size
    final paint = Paint()..color = color;
    canvas.drawRect(rect, paint);
  }
}

class CustomWidget extends SingleChildRenderObjectWidget {
  final Color color;

  const CustomWidget({Key? key, required this.color, Widget? child}) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return CustomBox(color: color);
  }

  @override
  void updateRenderObject(BuildContext context, CustomBox renderObject) {
    renderObject.color = color;
  }
}

คำอธิบาย Code:

  • CustomBox: คลาสที่สืบทอดจาก RenderBox
  • performLayout(): method นี้ถูกเรียกเมื่อ Render Object ต้องการคำนวณขนาดของตัวเอง เรากำหนดขนาดเป็นขนาดที่ใหญ่ที่สุดที่ Constraints อนุญาต (constraints.biggest)
  • paint(): method นี้ถูกเรียกเมื่อ Render Object ต้องการวาดตัวเองบน Canvas เราสร้าง Rect จาก Offset และ Size แล้ววาดสี่เหลี่ยมผืนผ้าด้วยสีที่กำหนด
  • CustomWidget: เป็น Widget ที่สร้างและอัปเดต CustomBox Render Object
  • createRenderObject(): สร้าง instance ของ CustomBox
  • updateRenderObject(): อัปเดต properties ของ CustomBox เมื่อ Widget ถูก rebuild

การใช้งาน:

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Custom Render Object')),
        body: Center(
          child: CustomWidget(
            color: Colors.blue,
            child: const Text('Hello, Custom Render Object!'),
          ),
        ),
      ),
    ),
  );
}

Code นี้จะแสดงสี่เหลี่ยมผืนผ้าสีฟ้าที่ครอบคลุมพื้นที่ทั้งหมดของ Center Widget และมีข้อความ "Hello, Custom Render Object!" อยู่ตรงกลาง

การจัดการ Constraints

Constraints เป็นข้อจำกัดที่กำหนดขนาดและตำแหน่งของ Render Object Constraints มีหลายประเภท เช่น:

  • BoxConstraints: ใช้สำหรับ RenderBox และกำหนดความกว้างและความสูงขั้นต่ำและสูงสุด
  • SizeConstraints: ใช้สำหรับ RenderObject ทั่วไปและกำหนดขนาดที่แน่นอน

เมื่อคุณ implement PerformLayout คุณต้องพิจารณา Constraints ที่ได้รับและคำนวณขนาดที่เหมาะสม RenderBox มี method helper หลายอย่างที่ช่วยในการจัดการ Constraints เช่น computeDryLayout ซึ่งช่วยให้คุณคำนวณขนาดโดยไม่ส่งผลกระทบต่อ Layout จริง

  @override
  Size computeDryLayout(BoxConstraints constraints) {
    // คำนวณขนาดโดยไม่ส่งผลกระทบต่อ Layout จริง
    return constraints.biggest;
  }

แนวปฏิบัติที่ดี: พยายามใช้ Constraints อย่างมีประสิทธิภาพ อย่าละเลย Constraints ที่ได้รับ เพราะอาจทำให้ Layout ผิดพลาดได้

การวาด (Painting) ขั้นสูง

Paint method ให้คุณเข้าถึง Canvas ซึ่งเป็นพื้นผิวที่คุณสามารถวาดรูปทรง, ข้อความ, และอื่นๆ ได้ คุณสามารถใช้ Paint class เพื่อกำหนดลักษณะการวาด เช่น สี, ความหนา, และรูปแบบ

  @override
  void paint(PaintingContext context, Offset offset) {
    final canvas = context.canvas;
    final rect = offset & size;
    final paint = Paint()
      ..color = color
      ..strokeWidth = 5.0
      ..style = PaintingStyle.stroke; // วาดเฉพาะเส้นขอบ

    canvas.drawRect(rect, paint);
  }

Code นี้จะวาดสี่เหลี่ยมผืนผ้าสีฟ้าที่มีเส้นขอบหนา 5 pixels

เหตุผลเชิงออกแบบ: การใช้ PaintingContext ช่วยให้ Flutter จัดการ layering และ compositing ได้อย่างมีประสิทธิภาพ คุณไม่จำเป็นต้องจัดการ Canvas โดยตรง แต่ใช้ PaintingContext เพื่อวาดลงใน layer ที่เหมาะสม

การสร้าง Layout ที่ซับซ้อน

Custom Render Objects สามารถใช้สร้าง Layout ที่ซับซ้อนได้ โดยการจัดการ Child Render Objects RenderObject มี method หลายอย่างที่ช่วยในการจัดการ Child Render Objects เช่น:

  • add(RenderObject child): เพิ่ม Child Render Object
  • remove(RenderObject child): ลบ Child Render Object
  • visitChildren(RenderObjectVisitor visitor): เยี่ยมชม Child Render Objects ทั้งหมด

คุณต้อง override PerformLayout เพื่อกำหนดขนาดและตำแหน่งของ Child Render Objects

ตัวอย่าง: การสร้าง Custom Render Object ที่จัดเรียง Child Render Objects ในแนวนอน:

class CustomHorizontalLayout extends RenderBox with ContainerRenderObjectMixin<RenderBox, ParentData> {
  @override
  void performLayout() {
    double x = 0.0;
    RenderBox? child = firstChild;

    while (child != null) {
      child.layout(constraints, parentUsesSize: true);
      final ParentData childParentData = child.parentData! as ParentData;
      childParentData.offset = Offset(x, 0.0);
      x += child.size.width;
      child = childParentData.nextSibling;
    }

    size = Size(x, constraints.maxHeight);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
  }

  @override
  bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
    return defaultHitTestChildren(result, position: position);
  }
}

class CustomHorizontalLayoutWidget extends MultiChildRenderObjectWidget {
  const CustomHorizontalLayoutWidget({Key? key, required List<Widget> children}) : super(key: key, children: children);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return CustomHorizontalLayout();
  }
}

class ParentData extends ContainerBoxParentData<RenderBox> {}

คำอธิบาย Code:

  • CustomHorizontalLayout: คลาสที่สืบทอดจาก RenderBox และใช้ ContainerRenderObjectMixin เพื่อจัดการ Child Render Objects
  • performLayout(): จัดเรียง Child Render Objects ในแนวนอน โดยกำหนดตำแหน่ง (offset) ให้กับ Child แต่ละตัว
  • CustomHorizontalLayoutWidget: Widget ที่สร้างและจัดการ CustomHorizontalLayout
  • ParentData: คลาสที่ใช้เก็บข้อมูลของ Child Render Objects

ข้อควรระวัง: การสร้าง Layout ที่ซับซ้อนด้วย Custom Render Objects อาจทำให้ code ซับซ้อนและยากต่อการ maintain พิจารณาใช้ Widget ที่มีอยู่แล้ว (เช่น Row, Column, Stack) ก่อนที่จะสร้าง Custom Render Object

สรุป

การสร้าง Custom Render Objects เป็นเทคนิคขั้นสูงที่ช่วยให้คุณควบคุมการแสดงผล UI ใน Flutter ได้อย่างละเอียด คุณสามารถใช้ Custom Render Objects เพื่อสร้าง Layout ที่ซับซ้อน, ปรับแต่งการวาด, และเพิ่มประสิทธิภาพการทำงานของแอป

ในบทความถัดไป เราจะสำรวจการใช้ Metal หรือ Vulkan API ใน Flutter เพื่อเพิ่มประสิทธิภาพการแสดงผล graphics ในระดับต่ำ

Office.in.th Custom Service

この構成をもとにシステムを開発・カスタマイズしたいですか?

Office.in.th は、エンタープライズ Web アプリケーション、API 連携、官民の業務管理システムの設計・開発を承り、長期的なサポートも提供します。

ソフトウェア開発サービスを見る