Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
438 views
in Technique[技术] by (71.8m points)

flutter - SingleChildScrollView obscured by custom keyboard

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

  1. I tried multiple things of this issue.
  2. I use a "custom keyboard"
  3. 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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...