การใช้งาน BottomNavigationBar ใน Flutter เบื้องต้น

เขียนเมื่อ 4 ปีก่อน โดย Ninenik Narkdee
flutter navigationbar bottomnavigationbar

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

ดูแล้ว 20,525 ครั้ง




ในตอนที่แล้ว เราได้รู้จัก การใช้งาน Drawer หรือที่เรียกว่า
SideMenu เบื้องต้นมาแล้ว ซึ่งเป็นรูปแบบการ Design หนึ่ง
ที่นิยมใช้งาน และเหมาะกับ App ที่อาจจะมีจำนวนหน้าจัดการต่างๆ
จำนวนมาก ทบทวนเนื้อหาตอนที่แล้วได้ที่
    การใช้งาน Drawer กำหนด SideMenu ใน Flutter เบื้องต้น http://niik.in/960
 
    ในตอนต่อไปนี้ เราจะมาดู รูปแบบการใช้งานเมนูอีกแบบ ที่น่าจะพบเห็นบ่อย นั่น
ก็คือการกำหนดแถบเมนูที่ส่วนด้านล่างของ App ซึ่งในที่นี้จะใช้งาน BottomNavigationBar 
Widget สำหรับกำหนดเมนู เพื่อเชื่อมโยงไปยังหน้า ต่างๆ ใน App
 

 

รู้จัก Widget เพิ่มเติม

    การใช้งาน BottomNavigationBar Widget

    เป็น Material widget ที่อยู่ส่วนด้านล่างของ App สำหรับใช้แสดงข้อมูลประมาณ 3 - 5 หน้า  
ประกอบด้วย Icon หรือ Text หรือทั้งสองอย่าง ทำหน้าที่เชื่อมโยงไปยังหน้าต่างๆ ภายใน App
คล้ายกับรูปแบบการทำงานของ Drawer หรือ SideMenu จากบทความที่ผ่านมา แต่ไม่เหมือนกัน
เสียทีเดียว เพราะมีการจัดการกันคนละรูปแบบ โดยเฉพาะในเรื่องของ Navigation Stack
    อย่างไรก็ตาม  ในบาง App ที่ อาจจะไม่มีหน้าจัดการมากนัก ก็มักใช้รูปแบบการกำหนดเมนู
ในลักษณะนี้แทน SideMenu  แต่ถ้า เป็นหน้าจอขนาดใหญ่ๆ การใช้ SideMenu จะเป็นวิธีที่เหมาะสมกว่า 
เพราะถ้าเราใช้งาน NavigationBar แถบเมนูแต่ละเมนูอาจจะอยู่ห่างกัน เกิดช่องว่างขึ้นได้
    BottomNavigationBar จะใช้งานร่วมกับ Scaffold และกำหนดเป็นค่า argument ให้กับ 
bottomNavigationBar property ดังนี้
 
Scaffold(
  bottomNavigationBar: // เพิ่ม BottomNavigationBar 
  // เข้าไปใน bottomNavigationBar  property ของ scaffold
);
    จะขอเรียกสั่นๆ ว่า "NavigationBar" 
 
    การกำหนดจำนวนของเมนู มีผลต่อรูปแบบการแสดงของสีพื้นหลัง สีเมนูปัจจุบัน สีเมนูที่ไม่ได้เลือก ถ้าไม่กำหนดไว้ก่อน
โดยถ้ามีเมนู ไม่เกิน 3 เมนู ตัว NavigationBar จะมี type เป็นแบบ "fixed" แต่ถ้ามีมากกว่า 3 เมนูขึ้นไป จะมี type เป็น "shifting"
 
    Type แบบ fixed เมนูจะแสดงแบบไม่มีการเคลื่อนไหว ทั้ง Icon และ Text ของเมนูแต่ละ item จะเรียงอยู่ในแนวปกติ
สีพื้นหลังของ NavigationBar จะเป็นสีขาว อิงจากสี ThemeData.canvasColor สีของ item เมนูปัจจุบันจะเป็นสีตาม 
ThemeData.primaryColor หรือเป็นสีตาม selectedItemColor ถ้ากำหนด
 
    Type แบบ shifting เมนูจะแสดงเฉพาะ Icon เป็นค่าเริ่มต้น เฉพาะเมนูปัจจุบัน ที่จะแสดง Text ด้วย โดยเมนูที่เป็นปัจจุบัน จะมี
ลักษณะดูเหมือนถูกยกให้สูงขึ้นกว่าเมนูอื่นๆ และมี animation เมื่อเปลี่ยนเมนู  ถ้าไม่ได้กำหนดสีของ selectedItemColor สีของทั้งหมด
จะเป็นสีขาว สีพืนหลังของ NavigationBar จะเป็นสีเดียวกับสีพื้นหลังของ item แต่ละเมนู นั่นคือแทบจะไม่เห็นเมนู 
ดังนั้นในกรณีที่มากกว่า 3 เมนู เราควรจัดรูปแบบการแสดงโดยกำหนด property ต่างๆ ให้เหมาะสม เช่น ควรกำหนดอย่างน้อย คือสีของ
selectedItemColor และสีของ unselectedItemColor  หรือกำหนดโดยใช้สีจาก Theme โดยกำหนดให้กับ unselectedIconTheme
และ selectedIconTheme ก็ได้เหมือนกัน
 
    รูปแบบสี ขนาดข้อความ สีพื้นหลัง เหล่านี้ เราสามารถกำหนดเพิ่มเติมได้ ตาม property ที่มีมาให้ อย่างไรก็ตาม ในกรณีที่เป็น type 
แบบ shifting หรือมีมากกว่า 3 เมนูค่าเริ่มต้นสีพื้นหลังของ NavigationBar จะบังคับเน้นไปที่โทนสีขาว จะเปลี่ยนสี backgroundColor
โดยตรงไม่ได้ แต่ก็มีวิธีเปลี่ยนสีโดยใช้ widget อื่นมาครอบและจัดรูปแบบอีกที
 
    ตัวอย่างการกำหนด ส่วนของ NavigationBar แบบ fixed อย่างง่าย และผลลัพธ์ที่ได้
 
Scaffold(
  bottomNavigationBar: BottomNavigationBar(
                items:  const <BottomNavigationBarItem>[
                    BottomNavigationBarItem(
                        icon: Icon(FontAwesomeIcons.home),
                        label: 'Home',
                    ),
                    BottomNavigationBarItem(
                        icon: Icon(FontAwesomeIcons.infoCircle),
                        label: 'About',
                    ),
                    BottomNavigationBarItem(
                        icon: Icon(FontAwesomeIcons.userAlt),
                        label: 'Profile',
                    ),
                ],
                currentIndex: _selectedIndex,
                onTap: _onItemTapped,
            ),
);
    ผลลัพธ์ที่ได้
 
 

 
 
    ตัวอย่างการกำหนด ส่วนของ NavigationBar แบบ shifting อย่างง่าย และผลลัพธ์ที่ได้
 
Scaffold(
	bottomNavigationBar: BottomNavigationBar(
		items:  const <BottomNavigationBarItem>[
			BottomNavigationBarItem(
				icon: Icon(FontAwesomeIcons.home),
				label: 'Home',
			),
			BottomNavigationBarItem(
				icon: Icon(FontAwesomeIcons.infoCircle),
				label: 'About',
			),
			BottomNavigationBarItem(
				icon: Icon(FontAwesomeIcons.userAlt),
				label: 'Profile',
			),
			BottomNavigationBarItem(
				icon: Icon(FontAwesomeIcons.addressCard),
				label: 'Contact',
			),
			BottomNavigationBarItem(
				icon: Icon(FontAwesomeIcons.cog),
				label: 'Settings',
			),
		],
		currentIndex: _selectedIndex,
		selectedItemColor: Theme.of(context).primaryColor,
		unselectedItemColor: Colors.grey,
		onTap: _onItemTapped,
	),
);
    ผลลัพธ์ที่ได้
 
 
 
 
    NavigationBar จะใช้ index ของ Items เมนูที่เลือก เป็นตัวกำหนด และอ้างอิงส่วนของเนื้อหาของเมนูนั้นๆ โดย index จะเริ่มต้นที่ 0
อย่างในโค้ดตัวอย่างข้างต้น เรากำหนด _selectedIndex ให้เป็นค่าของ currentIndex property ของ NavigationBar เพื่อบอกว่า ขณะนี้
อยู่ที่เมนูไหน หรือ index ไหน  โดย _selectedIndex จะรับค่า index เมนูที่เลือก จากการงานใน onTap event หรือเมื่อแตะที่เมนูใดๆ
ก็จะเรียกใช้งานฟังก์ชั่น เช่นข้างต้น เรียกใช้ฟังก์ชั่น _onItemTapped ที่มีใช้งาน typedef ฟังก์ชั่น ที่ส่งค่า index ที่เปลี่ยนแปลงเข้าไป
ในฟังก์ชั่น  โดยทั้งฟังก์ชั่น _onItemTapped และ _selectedIndex property เป็นส่วนที่เราจะต้องกำหนดเพิ่มเข้าไป จะได้เห็นในโค้ดเต็ม
 
    ทำความเข้าใจเกี่ยวกับ การอ้างอิงอาเรย์ข้อมูลที่แสดง กับอาเรย์เมนู โดยใช้ index  ดังนี้
 
    static const List<Widget> _pageWidget = <Widget>[
        Text(
            'Index 0: Home',
        ),
        Text(
            'Index 1: Business',
        ),
        Text(
            'Index 2: School',
        ),
    ];
    ส่วนแรกคือ อาเรย์ของข้อมูล โดยให้เข้าใจดังนี้ List<Widget> คือกำหนด อาเรย์ของข้อมูลประเภท widget ในตัวแปรชื่อ _pageWidget
เป็นแบบ static และเป็นค่า const (constant) หากใครไม่เข้าใจส่วนนี้แนะนำเนื้อหาเกี่ยวกับ บทความภาษา Dart เพิ่มเติม
    
    ก็จะได้เป็น _pageWidget[0] คือข้อความคำว่า Home ..... _pageWidget[2] คือ School เป็นต้น
 
    ต่อไปส่วนของ อาเรย์ของ เมนู
 
    static const List<BottomNavigationBarItem> _menuBar
        = <BottomNavigationBarItem>[
            BottomNavigationBarItem(
                icon: Icon(FontAwesomeIcons.home),
                label: 'Home',
            ),
            BottomNavigationBarItem(
                icon: Icon(FontAwesomeIcons.infoCircle),
                label: 'Business',
            ),
            BottomNavigationBarItem(
                icon: Icon(FontAwesomeIcons.userAlt),
                label: 'School',
            ),
    ];
    เข้าใจในรูปแบบเดียวกัน List<BottomNavigationBarItem> คือกำหนด อาเรย์ของข้อมูลประเภท BottomNavigationBarItem 
ในตัวแปรชื่อ _menuBar เป็นแบบ static และเป็นค่า const (constant) 
    ก็จะได้เป็น _menuBar[0] คือเมนูแรก Home ..... _menuBar[2] คือ เมนูที่ 3 คำว่า School เป็นต้น
 
    จะเห็นว่าทั้งตัวแปรที่เป็นเมนู และตัวแปรที่เป็น ข้อมูล จะมีค่า index ที่เชื่อมโยงกัน ดังนั้นเมื่อเราแตะที่เมนูใดๆ ค่า index ก็จะเปลี่ยน
หากเราแสดงเนื้อหาตามค่า index ข้อมูลของทั้งสองส่วนก็จะสัมพันธ์กัน 
 
    ดูคำสั่งการใช้งาน และ property เพิ่มเติมได้ที่ BottomNavigationBar Widget API  
 
 
 
 

ประยุกต์การใช้งาน BottomNavigationBar

    ในตอนที่แล้วเราออกแบบ App ในรูปแบบ มี Drawer หรือ SideMenu เนื้อหานี้ เราจะออกแบบหน้า App อีกรูปแบบ สำหรับ App ที่อาจจะ
มีเมนูไม่มากนัก โดยจะใช้งาน BottomNavigationBar แสดงข้อมูลหน้าต่างๆ เราจะสร้างหน้าใหม่เพิ่มอีกหน้าชื่อว่า launcher.dart เป็นหน้า
ที่มีการใช้งาน scaffold และกำหนด bottomNavigationBar กับ body แต่ไม่ได้กำหนด AppBar 
    โดยเมื่อเปิด App มาจะมาที่หน้า Launcher ก่อน โดย หน้า Launcher จะเป็นหน้าที่แสดง NavigationBar และแสดงหน้า Home เป็นหน้า
แรกหรือเป็นเมนูแรกที่ Active และเมื่อเราเปลี่ยนเมนู ก็จะไปดึงหน้าต่างๆ ตามที่กำหนดใน อาเรย์ข้อมูล ที่สัมพันธ์กับเมนูมาแสดง เราจะได้
โครงสร้างไฟล์ทั้งหมดดังนี้ 
 
 

 
 
    จะมีไฟล์ที่เกี่ยวข้องอยู่ 3 ไฟล์ คือ ไฟล์ main.dart ที่ปรับการเรียก route ไปเฉพาะหน้าแรก ที่เป็น Launcher แทน Home
ไฟล์ launcher.dart เป็นหน้าหลัก มีการใช้งาน และกำหนด NavigationBar และสุดท้ายไฟล์ home.dart เนื่องจากเรากำหนด
routeName ให้ Launcher เป็น "/" แล้ว ดังนั้นหน้าจะแค่เปลี่ยนเป็น "/home" แทน ส่วนไฟล์อื่นๆ เหมือนเดิมจากตอนที่แล้ว
 
    ไฟล์ launcher.dart
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'home.dart';
import 'contact.dart';
import 'profile.dart';
import 'about.dart';
import 'settings.dart';
 
class Launcher extends StatefulWidget {
    static const routeName = '/';

    const Launcher({Key? key}) : super(key: key);
 
    @override
    State<StatefulWidget> createState() {
        return _LauncherState();
    }
}
 
class _LauncherState extends State<Launcher> {
    int _selectedIndex = 0;
    final List<Widget> _pageWidget = <Widget>[
        const Home(),
        const About(),
        const Profile(),
        const Contact(),
        const Settings(),
    ];
    final List<BottomNavigationBarItem> _menuBar
    =  <BottomNavigationBarItem>[
        BottomNavigationBarItem(
            icon: Icon(FontAwesomeIcons.home),
            label: 'Home',
        ),
        BottomNavigationBarItem(
            icon: Icon(FontAwesomeIcons.infoCircle),
            label: 'About',
        ),
        BottomNavigationBarItem(
            icon: Icon(FontAwesomeIcons.userAlt),
            label: 'Profile',
        ),
        BottomNavigationBarItem(
            icon: Icon(FontAwesomeIcons.addressCard),
            label: 'Contact',
        ),
        BottomNavigationBarItem(
            icon: Icon(FontAwesomeIcons.cog),
            label: 'Settings',
        ),
    ];
 
    void _onItemTapped(int index) {
        setState(() {
            _selectedIndex = index;
        });
    }
 
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            body: _pageWidget.elementAt(_selectedIndex),
            bottomNavigationBar: BottomNavigationBar(
                items: _menuBar,
                currentIndex: _selectedIndex,
                selectedItemColor: Theme
                    .of(context)
                    .primaryColor,
                unselectedItemColor: Colors.grey,
                onTap: _onItemTapped,
            ),
        );
    }
}
    ไฟล์ main.dart
import 'package:flutter/material.dart';
import './screens/launcher.dart';
 
void main() {  runApp(const MyApp());}
 
// ส่วนของ Stateless widget
class MyApp extends StatelessWidget{
   const MyApp({Key? key}) : super(key: key);
   
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
                theme: ThemeData(
                    colorScheme: ColorScheme.fromSwatch(
                          primarySwatch: Colors.blue,
                        ).copyWith(
                          secondary: Colors.purple,
                    ),
                ),
                title: 'First Flutter App',
                initialRoute: '/', // สามารถใช้ home แทนได้
                routes: {
                    Launcher.routeName: (context) => Launcher(),
                },
        );
    }
}
    ไฟล์ home.dart
import 'package:flutter/material.dart';
 
class Home extends StatefulWidget {
    static const routeName = '/';

    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'),
            ),
            body: Center(
                child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                        Text('Home Screen'),
                    ],
                )
            ),
        );
    }
}
    ผลลัพธฺ์ที่ได้
 
 

 
 
 
    ในรูปแบบการใช้งานนี้ เราไม่ได้ใช้งาน Drawer หรือ SideMenu ร่วมด้วย แต่หากจะประยุกต์ใช้งานด้วยกันก็ได้  ซึ่งอาจจะต้องทำความ
เข้าใจเพิ่มเติมก่อนประยุกต์ใช้งานร่วมกัน  เพราะการใช้งาน SideMenu จะมองที่การวางซ้อนทับกันของหน้าข้อมูล แต่ในส่วนของ การใช้งาน
NavigationBar จะเหมือนกับเน้นไปที่ Navigation Stack แรก แล้วข้อมูในหน้าอื่นๆ ก็เหมือนมาแทรกอยู่ใน Widget ของ Stack แรก อย่าง
ในตัวอย่างของเรา เราเปิด App ขึ้นมาโดยใช้ Launcher เป็น หน้าหลัก หรือก็คือ Stack แรก หรือชั้นแรก และเมื่อเลือกที่เมนู ก็เป็นแค่
การเปลี่ยนในส่วนของ body ที่ไปดึงข้อมูลจากหน้าอื่นๆ มาใช้งาน หรือมาแทรกเข้ามา ไม่ได้เป็นการเปิดหน้าใหม่ซ้อนทับเข้ามา เหมือนการ
ใช้งาน SideMenu ดังนั้นจึงให้เข้าใจความแตกต่างส่วนนี้ เป็นแนวทาง หากต้องการประยุกต์เพิ่มเติม
 
    ในตอนต่อๆ ไป เราอาจจะเลือกรูปแบบจากทั้งสองการใช้งาน มาเป็นตัวอย่าง ทั้งแบบ SideMenu โดยใช้ Drawer และแบบที่มีการใช้งาน
NavigationBar ประกอบการอธิบายส่วนอืนๆ ต่อ ไป และเนื้อหาตอนหน้าจะเป็นอะไร รอติดตาม


   เพิ่มเติมเนื้อหา ครั้งที่ 1 วันที่ 25-07-2024


ดาวน์โหลดโค้ดตัวอย่าง สามารถนำไปประยุกต์ หรือ run ทดสอบได้

โค้ดตัวอย่างจากบทความด้านบน มีเฉพาะเมนูด้านล่าง
 
http://niik.in/download/flutter/demo_003_25072024_source.rar


   เพิ่มเติมเนื้อหา ครั้งที่ 2 วันที่ 25-07-2024


ดาวน์โหลดโค้ดตัวอย่าง สามารถนำไปประยุกต์ หรือ run ทดสอบได้

โค้ดตัวอย่างจากบทความด้านบน เพิ่มเมนูด้านข้างจากบทความก่อนหน้าและเมนูด้าานล่าง
 
http://niik.in/download/flutter/demo_004_25072024_source.rar


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



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



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









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






เนื้อหาพิเศษ เฉพาะสำหรับสมาชิก

กรุณาล็อกอิน เพื่ออ่านเนื้อหาบทความ

ยังไม่เป็นสมาชิก

สมาชิกล็อกอิน



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




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





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

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


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


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







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