ทบทวน Layout Widget ตอนที่ 2 Multi child Layout

บทความใหม่ ไม่กี่เดือนก่อน โดย Ninenik Narkdee
multi-child layout

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ multi-child layout

ดูแล้ว 329 ครั้ง


เนื้อหาต่อเนื่องจากตอนที่แล้ว ที่เราได้พูดถึงเกี่ยวกับ
การใช้งาน Sliver Widget ไปแล้ว เนื้อหานี้เราจะมาดูใน
ส่วนที่สอง เกี่ยวกับการใช้งาน Multi-child Layout ซึ่งประกอบ
ไปด้วย widget ต่างๆ ต่อไปนี้
 
เนื้อหาตอนที่แล้ว  http://niik.in/1102
เนื้อหานี้ใช้โค้ดตัวอย่างเริ่มต้น จากบทความ ตามลิ้งค์นี้ http://niik.in/961
โดยใช้ โค้ดตัวอย่างจากส่วน เพิ่มเติมเนื้อหา ครั้งที่ 2 
 

หน้าที่ของ Widget และ  delegate ใน  Multi-child Layout Widget 

 

1. Column

    เป็น widget ที่จัดเรียง widget ลูกในแนวตั้ง (column) จากบนลงล่าง โดยสามารถควบคุม 
alignment และ spacing ระหว่าง widget ได้
    มักใช้ในกรณีที่ต้องการจัดเรียง widget หลาย ๆ ตัวตามแนวตั้ง
 
ตัวอย่าง
 
import 'package:flutter/material.dart';
  
class Home extends StatefulWidget {
    static const routeName = '/home';
 
    const Home({Key? key}) : super(key: key);
  
    @override
    State<StatefulWidget> createState() {
        return _HomeState();
    }
}
  
class _HomeState extends State<Home> {
  
    @override
    Widget build(BuildContext context) {
  
        return Scaffold(
            appBar: AppBar(
                title: Text('Home'),
                leading: IconButton(
                  icon: Icon(Icons.menu),
                  onPressed: () {
                    Scaffold.of(context).openDrawer();
                  },
                ),                
            ),
            body: GradientColumnExample(),
        );
    }
}

class GradientColumnExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly, // จัดวาง container ให้ห่างกันเท่ากัน
        children: [
          _buildGradientContainer(Colors.red, Colors.orange),
          _buildGradientContainer(Colors.blue, Colors.purple),
          _buildGradientContainer(Colors.green, Colors.yellow),
        ],
      ),
    );
  }

  Widget _buildGradientContainer(Color startColor, Color endColor) {
    return Container(
      width: 200, // กำหนดความกว้างของ container
      height: 100, // กำหนดความสูงของ container
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [startColor, endColor],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(12.0), // เพิ่มมุมมนให้ container
      ),
    );
  }
}
 
ผลลัพธ์
 

 

2. CustomMultiChildLayout

    เป็น widget ที่ช่วยในการกำหนดตำแหน่งและขนาดของ widget ลูกหลาย ๆ ตัวแบบกำหนดเอง
ตามที่ต้องการ
    เหมาะสำหรับการออกแบบ layout ที่ซับซ้อนซึ่งต้องการควบคุมตำแหน่งของ widget ลูกแต่ละตัว
ด้วยตนเอง
 
ตัวอย่าง
 
class CustomMultiChildLayoutExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomMultiChildLayout(
      delegate: MyLayoutDelegate(),
      children: [
        LayoutId(
          id: 'header',
          child: Container(
            color: Colors.blue,
            child: Text(
              'Header',
              style: TextStyle(color: Colors.white, fontSize: 20),
            ),
            padding: EdgeInsets.all(16),
          ),
        ),
        LayoutId(
          id: 'body',
          child: Container(
            color: Colors.green,
            child: Text(
              'Body',
              style: TextStyle(color: Colors.white, fontSize: 20),
            ),
            padding: EdgeInsets.all(16),
          ),
        ),
        LayoutId(
          id: 'footer',
          child: Container(
            color: Colors.red,
            child: Text(
              'Footer',
              style: TextStyle(color: Colors.white, fontSize: 20),
            ),
            padding: EdgeInsets.all(16),
          ),
        ),
      ],
    );
  }
}

class MyLayoutDelegate extends MultiChildLayoutDelegate {
  @override
  void performLayout(Size size) {
    // วางตำแหน่งและกำหนดขนาดของ header
    if (hasChild('header')) {
      final headerSize = layoutChild('header', BoxConstraints.loose(size));
      positionChild('header', Offset(0, 0));
    }

    // วางตำแหน่งและกำหนดขนาดของ body
    if (hasChild('body')) {
      final bodySize = layoutChild('body', BoxConstraints.loose(size));
      positionChild('body', Offset(0, 100));
    }

    // วางตำแหน่งและกำหนดขนาดของ footer
    if (hasChild('footer')) {
      final footerSize = layoutChild('footer', BoxConstraints.loose(size));
      positionChild('footer', Offset(0, size.height - footerSize.height));
    }
  }

  @override
  bool shouldRelayout(MyLayoutDelegate oldDelegate) => false;
}
 
ผลลัพธ์
 

 

3. CarouselView

    เป็น widget ที่ใช้ในการแสดงเนื้อหาหลาย ๆ ชิ้นในรูปแบบของ carousel โดยสามารถเลื่อนเพื่อดู
เนื้อหาอื่น ๆ ที่เรียงต่อกันในแนว horizontal หรือ vertical
    มีฟังก์ชันการใช้งานที่สะดวก เช่น การตั้งค่าการเลื่อนอัตโนมัติ (auto-scrolling), การตั้งค่าความเร็ว
ในการเลื่อน, การแสดง indicator สำหรับหน้าที่กำลังแสดงอยู่ เป็นต้น     เหมาะสำหรับใช้ในการแสดง
ภาพชุด, ข้อมูลโปรโมชั่น, หรือข้อมูลที่ต้องการนำเสนอเป็นชุด ๆ แบบ carousel
 
ตัวอย่าง
 
class CarouselExample extends StatefulWidget {
  const CarouselExample({super.key});

  @override
  State<CarouselExample> createState() => _CarouselExampleState();
}

class _CarouselExampleState extends State<CarouselExample> {
  final CarouselController controller = CarouselController(initialItem: 1);

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final double height = MediaQuery.sizeOf(context).height;

    return ListView(
      children: <Widget>[
        ConstrainedBox(
          constraints: BoxConstraints(maxHeight: height / 2),
          child: CarouselView.weighted(
            controller: controller,
            itemSnapping: true,
            flexWeights: const <int>[1, 7, 1],
            children: ImageInfo.values.map((ImageInfo image) {
              return HeroLayoutCard(imageInfo: image);
            }).toList(),
          ),
        ),
        const SizedBox(height: 20),
        const Padding(
          padding: EdgeInsetsDirectional.only(top: 8.0, start: 8.0),
          child: Text('Multi-browse layout'),
        ),
        ConstrainedBox(
          constraints: const BoxConstraints(maxHeight: 50),
          child: CarouselView.weighted(
            flexWeights: const <int>[1, 2, 3, 2, 1],
            consumeMaxWeight: false,
            children: List<Widget>.generate(20, (int index) {
              return ColoredBox(
                color: Colors.primaries[index % Colors.primaries.length]
                    .withOpacity(0.8),
                child: const SizedBox.expand(),
              );
            }),
          ),
        ),
        const SizedBox(height: 20),
        ConstrainedBox(
          constraints: const BoxConstraints(maxHeight: 200),
          child: CarouselView.weighted(
              flexWeights: const <int>[3, 3, 3, 2, 1],
              consumeMaxWeight: false,
              children: CardInfo.values.map((CardInfo info) {
                return ColoredBox(
                  color: info.backgroundColor,
                  child: Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Icon(info.icon, color: info.color, size: 32.0),
                        Text(info.label,
                            style: const TextStyle(fontWeight: FontWeight.bold),
                            overflow: TextOverflow.clip,
                            softWrap: false),
                      ],
                    ),
                  ),
                );
              }).toList()),
        ),
        const SizedBox(height: 20),
        const Padding(
          padding: EdgeInsetsDirectional.only(top: 8.0, start: 8.0),
          child: Text('Uncontained layout'),
        ),
        ConstrainedBox(
          constraints: const BoxConstraints(maxHeight: 200),
          child: CarouselView(
            itemExtent: 330,
            shrinkExtent: 200,
            children: List<Widget>.generate(20, (int index) {
              return UncontainedLayoutCard(index: index, label: 'Show $index');
            }),
          ),
        )
      ],
    );
  }
}

class HeroLayoutCard extends StatelessWidget {
  const HeroLayoutCard({
    super.key,
    required this.imageInfo,
  });

  final ImageInfo imageInfo;

  @override
  Widget build(BuildContext context) {
    final double width = MediaQuery.sizeOf(context).width;
    return Stack(
        alignment: AlignmentDirectional.bottomStart,
        children: <Widget>[
          ClipRect(
            child: OverflowBox(
              maxWidth: width * 7 / 8,
              minWidth: width * 7 / 8,
              child: Image(
                fit: BoxFit.cover,
                image: NetworkImage(
                    'https://flutter.github.io/assets-for-api-docs/assets/material/${imageInfo.url}'),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(18.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                Text(
                  imageInfo.title,
                  overflow: TextOverflow.clip,
                  softWrap: false,
                  style: Theme.of(context)
                      .textTheme
                      .headlineLarge
                      ?.copyWith(color: Colors.white),
                ),
                const SizedBox(height: 10),
                Text(
                  imageInfo.subtitle,
                  overflow: TextOverflow.clip,
                  softWrap: false,
                  style: Theme.of(context)
                      .textTheme
                      .bodyMedium
                      ?.copyWith(color: Colors.white),
                )
              ],
            ),
          ),
        ]);
  }
}

class UncontainedLayoutCard extends StatelessWidget {
  const UncontainedLayoutCard({
    super.key,
    required this.index,
    required this.label,
  });

  final int index;
  final String label;

  @override
  Widget build(BuildContext context) {
    return ColoredBox(
      color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.5),
      child: Center(
        child: Text(
          label,
          style: const TextStyle(color: Colors.white, fontSize: 20),
          overflow: TextOverflow.clip,
          softWrap: false,
        ),
      ),
    );
  }
}

enum CardInfo {
  camera('Cameras', Icons.video_call, Color(0xff2354C7), Color(0xffECEFFD)),
  lighting('Lighting', Icons.lightbulb, Color(0xff806C2A), Color(0xffFAEEDF)),
  climate('Climate', Icons.thermostat, Color(0xffA44D2A), Color(0xffFAEDE7)),
  wifi('Wifi', Icons.wifi, Color(0xff417345), Color(0xffE5F4E0)),
  media('Media', Icons.library_music, Color(0xff2556C8), Color(0xffECEFFD)),
  security(
      'Security', Icons.crisis_alert, Color(0xff794C01), Color(0xffFAEEDF)),
  safety(
      'Safety', Icons.medical_services, Color(0xff2251C5), Color(0xffECEFFD)),
  more('', Icons.add, Color(0xff201D1C), Color(0xffE3DFD8));

  const CardInfo(this.label, this.icon, this.color, this.backgroundColor);
  final String label;
  final IconData icon;
  final Color color;
  final Color backgroundColor;
}

enum ImageInfo {
  image0('The Flow', 'Sponsored | Season 1 Now Streaming',
      'content_based_color_scheme_1.png'),
  image1('Through the Pane', 'Sponsored | Season 1 Now Streaming',
      'content_based_color_scheme_2.png'),
  image2('Iridescence', 'Sponsored | Season 1 Now Streaming',
      'content_based_color_scheme_3.png'),
  image3('Sea Change', 'Sponsored | Season 1 Now Streaming',
      'content_based_color_scheme_4.png'),
  image4('Blue Symphony', 'Sponsored | Season 1 Now Streaming',
      'content_based_color_scheme_5.png'),
  image5('When It Rains', 'Sponsored | Season 1 Now Streaming',
      'content_based_color_scheme_6.png');

  const ImageInfo(this.title, this.subtitle, this.url);
  final String title;
  final String subtitle;
  final String url;
}
 
ผลลัพธ์
 

 

4. Flow

    เป็น widget ที่จัดเรียง widget ลูกในทิศทางที่กำหนด (เช่น ซ้ายไปขวา บนลงล่าง) โดยใช้การจัดการ
layout แบบกำหนดเอง
    เหมาะสำหรับการสร้าง layout ที่ซับซ้อนหรือการจัดเรียง widget แบบ dynamic ที่ไม่สามารถ
จัดการได้ด้วย Row, Column หรือ Wrap
 
ตัวอย่าง
 
class CustomFlowExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Flow(
        delegate: CustomFlowDelegate(),
        children: List.generate(10, (index) {
          return Container(
            width: 80,
            height: 80,
            color: Colors.primaries[index % Colors.primaries.length],
            child: Center(
              child: Text(
                'Item $index',
                style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
              ),
            ),
          );
        }),
      ),
    );
  }
}

class CustomFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    double x = 0.0;
    double y = 0.0;
    for (int i = 0; i < context.childCount; i++) {
      final childSize = context.getChildSize(i)!;
      if (x + childSize.width > context.size.width) {
        // เริ่มบรรทัดใหม่เมื่อความกว้างเกินขอบเขต
        x = 0;
        y += childSize.height;
      }
      context.paintChild(i, transform: Matrix4.translationValues(x, y, 0));
      x += childSize.width;
    }
  }

  @override
  bool shouldRepaint(covariant FlowDelegate oldDelegate) {
    return false;
  }
}
 
ผลลัพธ์
 

 

5. GridView

    เป็น widget ที่ใช้ในการแสดง widget ลูกในรูปแบบตาราง (grid) โดยมีการแบ่งแถวและคอลัมน์
อัตโนมัติตามขนาดของหน้าจอ
    มีให้เลือกใช้หลายรูปแบบ เช่น GridView.count, GridView.builder, GridView.extent
 
ตัวอย่าง
 
class GradientGridView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 4, // จำนวนคอลัมน์ใน GridView
        crossAxisSpacing: 4.0,
        mainAxisSpacing: 4.0,
        childAspectRatio: 1.0,
      ),
      itemCount: 100, // จำนวนรายการใน GridView
      itemBuilder: (context, index) {
        // คำนวณเฉดสีที่แตกต่างกันไปในแต่ละรายการ
        final startColor = Color.lerp(Colors.blue, Colors.red, index / 100)!;
        final endColor = Color.lerp(Colors.green, Colors.yellow, index / 100)!;

        return Container(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [startColor, endColor],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
          ),
          child: Center(
            child: Text(
              'Item ${index + 1}',
              style: TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        );
      },
    );
  }
}
 
ผลลัพธ์
 

 

6. IndexedStack

    เป็น widget ที่วาง widget ลูกหลาย ๆ ตัวซ้อนกันในตำแหน่งเดียวกัน โดยสามารถเลือกแสดงได้
เพียงตัวเดียวในแต่ละครั้ง
    มักใช้ในกรณีที่ต้องการแสดง widget ที่แตกต่างกันตามเงื่อนไข หรือในกรณีที่ต้องการสลับหน้าแสดงผล
 
ตัวอย่าง
 
class IndexedStackExample extends StatefulWidget {
  @override
  _IndexedStackExampleState createState() => _IndexedStackExampleState();
}

class _IndexedStackExampleState extends State<IndexedStackExample> {
  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // การแสดงผล widget ที่ใช้ IndexedStack
        Expanded(
          child: IndexedStack(
            index: _selectedIndex,
            children: <Widget>[
              _buildPage('Home Page', Colors.blue),
              _buildPage('Settings Page', Colors.green),
              _buildPage('Profile Page', Colors.orange),
            ],
          ),
        ),
        // ปุ่มสำหรับสลับหน้าจอ
        BottomNavigationBar(
          currentIndex: _selectedIndex,
          onTap: (index) {
            setState(() {
              _selectedIndex = index;
            });
          },
          items: [
            BottomNavigationBarItem(
              icon: Icon(Icons.home),
              label: 'Home',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.settings),
              label: 'Settings',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.person),
              label: 'Profile',
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildPage(String title, Color color) {
    return Container(
      color: color,
      child: Center(
        child: Text(
          title,
          style: TextStyle(fontSize: 24, color: Colors.white),
        ),
      ),
    );
  }
}
 
ผลลัพธ์
 
 

7. LayoutBuilder

    เป็น widget ที่ใช้ในการสร้าง layout แบบ dynamic โดยให้ผู้พัฒนากำหนด layout ตามขนาดของ
พื้นที่ที่มีอยู่
    มีประโยชน์เมื่อ layout ของ widget ต้องการปรับเปลี่ยนตามขนาดของหน้าจอหรือพื้นที่ที่มี
 
ตัวอย่าง
 
class DynamicLayoutExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // ขนาดของพื้นที่ที่มีอยู่
        double width = constraints.maxWidth;
        double height = constraints.maxHeight;

        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                width: width * 0.8, // กำหนดความกว้างเป็น 80% ของพื้นที่ที่มีอยู่
                height: height * 0.4, // กำหนดความสูงเป็น 40% ของพื้นที่ที่มีอยู่
                color: Colors.blue,
                child: Center(
                  child: Text(
                    '80% Widthn40% Height',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                    textAlign: TextAlign.center,
                  ),
                ),
              ),
              SizedBox(height: 20), // ระยะห่างระหว่าง Container
              Container(
                width: width * 0.5, // กำหนดความกว้างเป็น 50% ของพื้นที่ที่มีอยู่
                height: height * 0.2, // กำหนดความสูงเป็น 20% ของพื้นที่ที่มีอยู่
                color: Colors.red,
                child: Center(
                  child: Text(
                    '50% Widthn20% Height',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                    textAlign: TextAlign.center,
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}
 
ผลลัพธ์
 

 

8. ListBody

    เป็น widget ที่จัดเรียง widget ลูกในแนวตั้งหรือแนวนอนตามลำดับ โดยไม่มีการเลื่อน (scrolling)
    มักใช้ในกรณีที่ต้องการจัดกลุ่มของ widget หลายตัวในทิศทางเดียวกัน
    * ไม่นิยมใช้ ควรใช้เป็น ListView แทน ซึ่งสามารถทำได้หลายอย่างมากกว่า
 

9. ListView

    เป็น widget ที่ใช้ในการแสดงรายการของ widget หลาย ๆ ตัวในแนวตั้งหรือแนวนอน โดยสามารถ
เลื่อน (scroll) ได้
    มีหลายรูปแบบ เช่น ListView.builder สำหรับการสร้างรายการแบบ dynamic
 
ตัวอย่าง
 
class GradientListView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 100, // จำนวนรายการใน ListView
      itemBuilder: (context, index) {
        // คำนวณเฉดสีที่แตกต่างกันไปในแต่ละรายการ
        final startColor = Color.lerp(Colors.blue, Colors.red, index / 100)!;
        final endColor = Color.lerp(Colors.green, Colors.yellow, index / 100)!;

        return Container(
          height: 50, // กำหนดความสูงของแต่ละรายการ
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [startColor, endColor],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
          ),
          child: Center(
            child: Text(
              'Item ${index + 1}',
              style: TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        );
      },
    );
  }
}
 
ผลลัพธ์
 

 

10. Row

    เป็น widget ที่จัดเรียง widget ลูกในแนวนอน (row) จากซ้ายไปขวา โดยสามารถควบคุม alignment
และ spacing ระหว่าง widget ได้
    มักใช้ในกรณีที่ต้องการจัดเรียง widget หลาย ๆ ตัวตามแนวนอน
 
ตัวอย่าง
 
class GradientRowExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly, // จัดเรียง container ให้ห่างกันเท่ากัน
        children: [
          _buildGradientContainer(Colors.red, Colors.orange),
          _buildGradientContainer(Colors.blue, Colors.purple),
          _buildGradientContainer(Colors.green, Colors.yellow),
        ],
      ),
    );
  }

  Widget _buildGradientContainer(Color startColor, Color endColor) {
    return Container(
      width: 100,
      height: 100,
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [startColor, endColor],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(12.0), // เพิ่มมุมมน
      ),
    );
  }
}
 
ผลลัพธ์
 
 

11. Stack

    เป็น widget ที่วาง widget ลูกหลาย ๆ ตัวซ้อนกันในตำแหน่งเดียวกัน โดยสามารถกำหนดตำแหน่งของ
widget แต่ละตัวได้
    มักใช้ในกรณีที่ต้องการแสดง widget หลาย ๆ ตัวซ้อนทับกัน เช่น การวางรูปภาพกับข้อความบนรูปภาพ
 
ตัวอย่าง
 
class ExclusiveNews extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Stack(
        alignment: Alignment.center,
        children: [
          // รูปภาพด้านล่าง
          Image.network(
            'https://www.peanutsquare.com/wp-content/uploads/2023/06/Flutter-3.0-Whats-New-and-How-to-Get-Started-jpg.webp',
            width: 300,
            height: 200,
            fit: BoxFit.cover,
          ),
          // ข้อความด้านบน
          Container(
            width: 300,
            height: 200,
            alignment: Alignment.center,
            decoration: BoxDecoration(
              color: Colors.black.withOpacity(0.6), // พื้นหลังโปร่งใส
            ),
            child: Text(
              'Exclusive News: Flutter 3.0 Released!',
              style: TextStyle(
                color: Colors.white,
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
              textAlign: TextAlign.center,
            ),
          ),
        ],
      ),
    );
  }
}
 
ผลลัพธ์
 
 

12. Table

    เป็น widget ที่ใช้ในการสร้างตาราง โดยสามารถกำหนดจำนวนแถวและคอลัมน์ได้ตามต้องการ
    มักใช้ในกรณีที่ต้องการแสดงข้อมูลในรูปแบบของตารางที่มีโครงสร้างชัดเจน
 
ตัวอย่าง
 
class PremierLeagueTable extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Table(
        border: TableBorder.all(),
        columnWidths: {
          0: FixedColumnWidth(50.0),
          1: FlexColumnWidth(),
          2: FixedColumnWidth(50.0),
          3: FixedColumnWidth(50.0),
          4: FixedColumnWidth(50.0),
        },
        children: [
          TableRow(
            decoration: BoxDecoration(color: Colors.grey[300]),
            children: [
              _buildTableHeader('Pos'),
              _buildTableHeader('Team'),
              _buildTableHeader('P'),
              _buildTableHeader('W'),
              _buildTableHeader('Pts'),
            ],
          ),
          _buildTableRow(1, 'Manchester City', 38, 29, 90),
          _buildTableRow(2, 'Liverpool', 38, 28, 89),
          _buildTableRow(3, 'Chelsea', 38, 21, 74),
          _buildTableRow(4, 'Tottenham', 38, 22, 71),
          // เพิ่มทีมอื่น ๆ ได้ตามต้องการ
        ],
      ),
    );
  }

  TableRow _buildTableRow(int position, String team, int played, int wins, int points) {
    return TableRow(
      children: [
        _buildTableCell(position.toString()),
        _buildTableCell(team),
        _buildTableCell(played.toString()),
        _buildTableCell(wins.toString()),
        _buildTableCell(points.toString()),
      ],
    );
  }

  Widget _buildTableHeader(String text) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Text(
        text,
        style: TextStyle(fontWeight: FontWeight.bold),
        textAlign: TextAlign.center,
      ),
    );
  }

  Widget _buildTableCell(String text) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Text(
        text,
        textAlign: TextAlign.center,
      ),
    );
  }
}
 
ผลลัพธ์
 

 

13. Wrap

    เป็น widget ที่จัดเรียง widget ลูกในทิศทางที่กำหนด (เช่น ซ้ายไปขวา บนลงล่าง) และสามารถ 
wrap ไปยังแถวถัดไปได้หากพื้นที่ไม่พอ
    เหมาะสำหรับกรณีที่ต้องการแสดงรายการ widget ที่มีขนาดไม่คงที่ และไม่ต้องการให้ overflow
 
ตัวอย่าง
 
class WrapExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Padding(
        padding: EdgeInsets.all(16.0),
        child: Wrap(
          spacing: 8.0, // ระยะห่างระหว่าง widget ลูกในแนวนอน
          runSpacing: 8.0, // ระยะห่างระหว่างแถวในแนวตั้ง
          alignment: WrapAlignment.center, // การจัดแนว widget ลูกในแนวตั้ง
          runAlignment: WrapAlignment.center, // การจัดแนวแถวในแนวตั้ง
          children: List.generate(20, (index) {
            return Container(
              width: 100 + (index % 5) * 20, // ขนาดของ widget ลูกที่เปลี่ยนแปลง
              height: 100, // ความสูงของ widget ลูก
              color: Colors.primaries[index % Colors.primaries.length], // สีของ widget ลูก
              child: Center(
                child: Text(
                  'Item ${index + 1}',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            );
          }),
        ),
      ),
    );
  }
}
 
ผลลัพธ์
 

 
ตัวอย่างโค้ดข้างต้นเป็นแนวทางให้เราสามารถนำไปประยุกต์ใช้งานเพิ่มเติมต่อได้


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ



ทบทวนบทความที่แล้ว









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ