Using flutter 1.20.4
I'm trying to implement a custom digit keyboard (a simple container at the bottom), which should appear into view when tapping on one of the rows in the List.
Here a small snippet of how I do it:
Widget build(BuildContext context) {
BudgetPageState budgetPageState = Provider.of<BudgetPageState>(context, listen: true);
ButtonDial buttonDial =
budgetPageState.showButtonDial ? ButtonDial() : null;
return Scaffold(
appBar: AppBar(
title: Text("Budget Page"),
),
body: Column(children: <Widget>[
Expanded(child: CustomList()),
if (buttonDial != null) buttonDial
]));
}
}
However, when the keyboard appears, the bottom rows get obscured by the container.
I tried using Scrollable.ensureVisible
, that works for the middle rows, but the last ones are still obscured. It seems like the ScrollView still has it's old size (full height) when Scrollable.ensureVisible()
kicks in (I notice this by looking at the ScrollBar).
Code snippet:
Scrollable.ensureVisible(context, duration: Duration(milliseconds: 200), alignment: 0.5);
See video below.
Keyboard obscures last rows when tapped (here clicking on row 14)
However, once the keyboard is up, the SingleChildScrollView has shrunk to the new size and the Scrollable now works.
When keyboard is up, Scrollable.ensureVisible() does its job(here clicking on row 6 and 12)
I know this is similar to this question, but
- I tried multiple things of this issue.
- I use a "custom keyboard"
- The flutter github issue here below fixed this (I think)
Read through this popular Flutter Github issue, this made me use SingleChildScrollView
instead of ListView
.
Tried this, this fixes the keyboard obscuring the bottom Rows by shifting them up, however now when clicking on the first Rows, they get moved out of view.
Tried KeyboardAvoider, but as this is not an onscreen Keyboard, I doesn't work.
You'll find a full minimal reproducible example here below.
main.dart
(Main + ChangeNotifierProvider for the state)
import 'package:flutter/material.dart';
import 'package:scrollTest/budgetPage.dart';
import 'package:scrollTest/budgetPageState.dart';
import 'package:provider/provider.dart';
void main() {
runApp(HomeScreen());
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ChangeNotifierProvider(
create: (_) => BudgetPageState(), child: BudgetPage()),
),
);
}
}
budgetPage.dart
(Main Page with the CustomList()
and the buttonDial
(custom keyboard, here just a simple container)
import 'package:flutter/material.dart';
import 'package:scrollTest/budgetPageState.dart';
import 'package:scrollTest/customList.dart';
import 'package:provider/provider.dart';
class BudgetPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
BudgetPageState budgetPageState = Provider.of<BudgetPageState>(context, listen: true);
ButtonDial buttonDial =
budgetPageState.showButtonDial ? ButtonDial() : null;
return Scaffold(
appBar: AppBar(
title: Text("Budget Page"),
),
body: Column(children: <Widget>[
Expanded(child: CustomList()),
if (buttonDial != null) buttonDial
]));
}
}
class ButtonDial extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height * 0.3,
child: Container(
color: Colors.blue,
),
);
}
}
customList.dart
(Simple List view SingleChildScrollView
and a ScrollController
)
import 'package:flutter/material.dart';
import 'package:scrollTest/CustomRow.dart';
class CustomList extends StatefulWidget {
@override
_CustomListState createState() => _CustomListState();
}
class _CustomListState extends State<CustomList> {
ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scrollbar(
isAlwaysShown: true,
controller: _scrollController,
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: _buildList(),
),
),
);
}
}
List<Widget> _buildList() {
List<Widget> widgetList = [];
for (int i = 0; i < 15; i++) {
widgetList.add(CustomRow(rowID: i));
}
return widgetList;
}
customRow.dart
(This is where I scroll to the selected row in handleOnTap
)
import 'package:flutter/material.dart';
import 'package:scrollTest/budgetPageState.dart';
import 'package:provider/provider.dart';
class CustomRow extends StatefulWidget {
final int rowID;
CustomRow({Key key, @required this.rowID}) : super(key: key);
@override
_CustomRowState createState() => _CustomRowState();
}
class _CustomRowState extends State<CustomRow> {
BudgetPageState budgetPageState;
void handleOnTap(BuildContext context) {
if (!budgetPageState.isSelected(widget.rowID)) {
Scrollable.ensureVisible(context, duration: Duration(milliseconds: 200), alignment: 0.5);
}
budgetPageState.toggleButtonDial(widget.rowID);
budgetPageState.updateIsSelected(widget.rowID);
}
@override
void initState() {
super.initState();
budgetPageState = Provider.of<BudgetPageState>(context, listen: false);
budgetPageState.insertRowInHashMap(widget.rowID);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap:() => handleOnTap(context),
child: Container(
height: 60,
width: double.infinity,
color: budgetPageState.isSelected(widget.rowID)
? Colors.grey[200]
: Colors.white,
child: Center(
child: Text(
"Test ${widget.rowID}",
),
),
),
);
}
}
budgetPageState.dart
(The state managed using ChangeNotifier
. Mainly contains logic for selecting/deselecting a row as well as logic for when to show the keyboard (using bool showButtonDial
and notifyListeners()
)
import 'dart:collection';
import 'package:flutter/material.dart';
class BudgetPageState extends ChangeNotifier {
bool showButtonDial = false;
Map<int, bool> _isSelectedMap = HashMap();
int selectedId = -1;
bool isSelected(int rowId) {
return this._isSelectedMap[rowId];
}
Map<int, bool> get isSelectedMap => _isSelectedMap;
void updateIsSelected(int rowId) async {
///Select the row [rowId] if we tapped on a different one than the one
///that is currently highlighted ([selectedId])
///The same row was tapped, we remove the highlight i.e. we don't
///put it back to [true]
//Unselect all
_isSelectedMap.forEach((k, v) => _isSelectedMap[k] = false);
if (selectedId != rowId) {
this._isSelectedMap[rowId] = true;
selectedId = rowId;
} else {
selectedId = -1;
}
notifyListeners();
}
void toggleButtonDial(int rowId) {
if (!showButtonDial) {
showButtonDial = true;
} else if (rowId == selectedId) {
showButtonDial = false;
}
}
void insertRowInHashMap(int subcatId) {
this._isSelectedMap[subcatId] = false;
}
}
question from:
https://stackoverflow.com/questions/65645052/singlechildscrollview-obscured-by-custom-keyboard