วันก่อนเห็นพี่เอกโพสเกี่ยวกับวิธีแก้ปัญหา UI โดนบังเพราะ on-screen keyboard สำหรับคนทำแอพ Android แบบ Native (ใน post ใช้ Kotlin) ก็มานั่งคิดว่าเออ แล้ว Google Flutter มันเจอปัญหาแบบเดียวกันไหม
มานั่งดู เฮ้ย มันเจอกรณีแบบเดียวกันจริงๆ ด้วย
เลยนั่งหา solution เพื่อแก้ปัญหานี้ ซึ่งก็จะมีอธิบายกลไกคร่าวๆ และพลได้ทำเป็น flutter package ออกมาให้เอาไปใช้กันได้สบายๆ ครับ
สถานการณ์เป็นใจ
คือเรื่องของการที่ผู้ใช้กดเลือกช่องสำหรับกรอกข้อมูล ที่เราสร้างด้วย widget ชื่อ TextField หรือ TextFormField แล้วมีการ scroll พาตัว Widget ไปวางอยู่ด้านบนของคีย์บอร์ด (เรียกอีกชื่อว่า on-screen keyboard ก็ได้) เป็นเรื่องปกติใช่ไหม
ทว่ามันจะมีบางสถานการณ์ที่ เราไม่อยากให้ส่วนที่อยู่ด้านล่างของ TextField หรือ TextFormField มันถูกซ่อนไปไว้ด้านหลัง keyboard หน่ะสิ
เช่น อาจจะเป็นปุ่มลงทะเบียน, ปุ่ม sign in, sign up หรือ UI ส่วนอื่นๆ ที่ตอนผู้ใช้จะกรอกข้อมูล เราอยากให้มันยังอยู่บนหน้าจอ
ไม่อยากให้มันถูกซ่อนอยู่ด้านหลัง keyboard เงียบๆ
scrollPadding ไม่ดีหรอ?
จริงๆ แล้ว Flutter มีการเตรียมกลไก สำหรับการเว้นระยะห่างระหว่างขอบด้านล่างของ TextField และ TextFormField และตัว keyboard อยู่นะ มันมีชื่อว่า scrollPadding
ซึ่งทำให้เราสามารถกำหนดค่าด้วย EdgeInset แบบทั่วๆ ไปอยู่
TextField(
scrollPadding: EdgeInsets.all(10)
)
และมันก็ใช้ได้นะ
แต่ว่าการจะกะเกณฑ์ให้มีระยะห่างที่สวยงามเป๊ะๆ กับพื้นที่ของ UI ที่อยู่ถัดไปด้านล่างของส่วนกรอกข้อมูล มันอาจต้องใช้พลังเยอะซักหน่อย
พลเลยทำ Widget set ออกมาเป็น Never Behind Keyboard package ที่ทำให้เราเอาไปใช้ได้ง่ายๆ ครับ
ซึ่งกลไกภายใน Never Behind Keyboard package จะขอเขียนเล่าทางเทคนิคไว้อีกโพสหน้าแล้วกัน ด้านล่างเอาวิธีใช้ก่อน
แนะนำการใช้งาน Never Behind Keyboard
กลไกการทำงานของ Never Behind Keyboard จะประกอบไปด้วย Widget 3 ตัว นั่นคือ
- NeverBehindKeyboardArea
- NeverBehindFocusSource
- NeverBehindBottom
1. กำหนดพื้นที่การ scroll ด้วย NeverBehindKeyboardArea
ปกติถ้าเรามีการวางแบบฟอร์มการกรอกข้อมูล ที่ประกอบไปด้วย TextField หรือ TextFormField แล้ว ถ้ามีขนาดที่ยาวล้นหน้าจอ หรือมองแล้วว่าต้องถูกบังด้วย keyboard แน่ๆ ก็มักจะเอาใส่ไว้ใน ScrollView หรือ ListView widget
จุดนี้แหละที่เราต้องครอบ NeverBehindKeyboardArea ไว้นอกบริเวณที่เป็น ScrollView หรือ ListView widget ครับ
NeverBehindKeyboardArea(
scrollView: ListView(
children: [
],
),
),
จากนั้น ด้านในที่แหละที่เราสามารถวาง NeverBehindFocusSource กับ NeverBehindBottom ไว้ด้านในได้
2. กำหนดส่วนตรวจจับการกดเลือกกรอกข้อมูล ด้วย NeverBehindFocusSource
ทีนี้ไม่ว่าด้านในจะซับซ้อนแค่ไหน ตัวที่กระตุ้นกลไกของเราคือ TextField และ TextFormField เวลาถูกกดเลือก
ดังนั้นอยากให้ส่วนที่ทำให้เกิดกลไกการเปิด on-screen keyboard อยู่ภายใน NeverBehindFocusSource widget
และแน่นอนว่า NeverBehindFocusSource widget ต้องอยู่ภายใน NeverBehindKeyboardArea widget นะ
NeverBehindKeyboardArea(
scrollView: ListView(
children: [
// ไม่สำคัญว่าจะซับซ้อนแค่ไหน แค่ใส่ TextField หรือ TextFormField ไว้ด้านใน NeverBehindFocusSource ก็พอ
NeverBehindFocusSource(
child: Column(
children: [
TextField(
decoration: InputDecoration(
hintText: "หลักสูตรเรียนออนไลน์ Google Flutter สำหรับผู้เริ่มต้น www.nextflow.in.th",
),
),
TextFormField(),
],
),
),
],
),
),
เราสามารถใช้ NeverBehindFocusSource กับ TextField หรือ TextFormField ก็ได้ครับ และสามารถใส่ลงไป NeverBehindFocusSource ได้หลายตัวครับ
3. กำหนดขอบด้านล่างพื้นที่ ด้วย NeverBehindBottom
สุดท้ายแล้ว เราต้องกำหนดตำแหน่งอ้างอิงพื้นที่ระหว่าง NeverBehindFocusSource กับขอบด้านบนของ on-screen keyboard ด้วยการใช้ NeverBehindBottom widget
ซึ่งตำแหน่งที่มักวาง NeverBehindBottom widget ก็คือขอบด้านล่างสุดของพื้นที่ที่เราไม่อยากให้ถูกซ่อน หรือถูกบังไว้ด้านหลัง on-screen keyboard นั่นเอง
สำคัญตรงจุดที่มีการใช้ NeverBehindBottom widget คือต้องกำหนด key ด้วย GlobalKey() นะ ส่วนนี้สำคัญในการอ้างอิงตำแหน่งในการ scroll
และให้ชัวร์ว่า NeverBehindBottom widget อยู่ภายใน NeverBehindKeyboardArea widget นะครับ
NeverBehindKeyboardArea(
scrollView: ListView(
children: [
// ไม่สำคัญว่าจะซับซ้อนแค่ไหน แค่ใส่ TextField หรือ TextFormField ไว้ด้านใน NeverBehindFocusSource ก็พอ
NeverBehindFocusSource(
child: Column(
children: [
TextField(
decoration: InputDecoration(
hintText: "หลักสูตรเรียนออนไลน์ Google Flutter สำหรับผู้เริ่มต้น www.nextflow.in.th",
),
),
TextFormField(),
],
),
),
// กำหนดขอบด้านล่างสุดที่จะถูกวางไว้เหนือ on-screen keyboard และอย่าลืมสร้าง GlobalKey ให้มันด้วย
NeverBehindBottom(key: Globalkey())
],
),
),
เสร็จแล้วก็ลองใช้งานได้เลย หรือจะโหลดโปรเจคนี้มาลองรันก็ได้นะ หรือพร้อมแล้วก็ไปดูวิธีติดตั้งในของ package ใน pub.dev
ถ้าชอบก็เข้าไปกดให้คะแนนใน pub.dev และกดให้ดาวใน Github เป็นกำลังใจได้นะครับ
เริ่มต้นเรียนรู้สร้างแอพ iOS และ Android ด้วย Google Flutter ไปกับโค้ชพล
✅ เหมาะสำหรับผู้เริ่มต้น
✅ เขียนครั้งเดียว ใช้ได้ทั้งระบบ iOS และ Android ประหยัดเวลา
✅ เรียนได้ทุกเวลา ไม่มีวันหมดอายุ
✅ ได้รู้ตั้งแต่วิธีติดตั้งโปรแกรม จนเอาขึ้นApp Store และ Play Store
✅ ได้ทำแอพของจริง เริ่มต้นจาก 0 ทุกโปรเจค มีระบบถามตอบ กรณีติดปัญหา
แหล่งที่มาของข้อมูลที่ใช้ในการสร้าง
- แรงบันดาลใจที่ทำให้เกิด package นี้ขึ้นจากพี่เอก akexorcist
- Flutter ListView scroll to bottom on build – Stack Overflow
- dart – Widget of context in Flutter – Stack Overflow
- Is there any callback to tell me when “build” function is done in Flutter? – Stack Overflow
- Flutter/Dart Scrolling textfield above keyboard – Stack Overflow
- Flutter detect user scroll with ScrollController – Stack Overflow
- Flutter ListView scroll to bottom on build – Stack Overflow
- dart – ScrollController how can I detect Scroll start, stop and scrolling? – Stack Overflow
- flutter_keyboard_visibility | Flutter Package (pub.dev)
- flutter – How to position a Widget at the bottom of a SingleChildScrollView? – Stack Overflow
- flutter – Button overlaps on textfield when keyboard is open – Stack Overflow
- android – How to scroll to bottom of SingleChildScrollView when TextField gets focus? – Stack Overflow
- ensureVisible method – Scrollable class – widgets library – Dart API (flutter.dev)
- Flutter Web : Scroll to specific widget on click – Stack Overflow
- Flutter: How to check if an object is an instance of a class(A stateful or stateless widget) – Stack Overflow