各タブの状態が保存されるフラッターアプリケーションでBottomNavigationBarを使用しています。また、テキスト入力のある画面を除いて、BottomNavigationBarが表示されるようにしました。私はそれを達成しましたが、テキスト入力を追加したいときに大きな問題があります。キーボードに集中できません。1秒もかからずにオンになり、元に戻ります。一部の人々の解決策を見てきましたが、私にはうまくいきませんでした。キーボードがオフになるのを修正するために、ここで助けていただければ幸いです。ありがとう
入力テキストフィールドをクリックすると、端末に表示されます
W/IInputConnectionWrapper(29345): getSelectedText on inactive InputConnection
W/IInputConnectionWrapper(29345): requestCursorAnchorInfo on inactive InputConnection
3
W/IInputConnectionWrapper(29345): getTextBeforeCursor on inactive InputConnection
W/IInputConnectionWrapper(29345): getSelectedText on inactive InputConnection
D/InputConnectionAdaptor(29345): The input method toggled cursor monitoring on
I/SurfaceView(29345): updateWindow -- setFrame, this = io.flutter.embedding.android.FlutterSurfaceView{f394ae6 V.E...... ......I. 0,0-720,1204}
これがフラッタードクターです。さらにファイルが必要な場合はお知らせください。ありがとう。
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.0.6, on Microsoft Windows [Version 10.0.19043.985], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[X] Chrome - develop for the web (Cannot find Chrome executable at .\Google\Chrome\Application\chrome.exe)
! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.
[√] Android Studio (version 4.1.0)
[√] VS Code (version 1.56.2)
[√] Connected device (2 available)
>
これは私のホームページクラスファイルです
import 'package:flutter/material.dart';
import 'package:my_time_tracker/app/home/account/account_page.dart';
import 'package:my_time_tracker/app/home/entries/entries_page.dart';
import 'package:my_time_tracker/app/home/home_scaffold.dart';
import 'package:my_time_tracker/app/home/jobs/edit_job_page.dart';
import 'package:my_time_tracker/app/home/tab_item.dart';
import 'jobs/jobs_page.dart';
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
TabItem _currentTab = TabItem.jobs;
int _selectedIndex = 0;
List<GlobalKey<NavigatorState>> navigatorKeys = [
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
GlobalKey<NavigatorState>(),
];
Map<int, Widget> get widgets {
return {
0: JobsPage(),
1: EntriesPage.create(context),
2: AccountPage(),
};
}
void _onItemTapped(int index) {
if (_selectedIndex == index) {
navigatorKeys[index].currentState.popUntil((route) => route.isFirst);
} else {
setState(() {
_selectedIndex = index;
});
}
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab =
!await navigatorKeys[_selectedIndex].currentState.maybePop();
print(
'isFirstrouteInCurrentTab:' + isFirstRouteInCurrentTab.toString());
return isFirstRouteInCurrentTab;
},
child: HomeScaffold(
navigatorKeys: navigatorKeys,
currentTabItem: _currentTab,
onSelectTab: _onItemTapped,
currentIndex: _selectedIndex,
widget: widgets,
),
);
}
}
これは私の HomeScaffold クラスです
import 'package:flutter/material.dart';
import 'package:my_time_tracker/app/home/account/account_page.dart';
import 'package:my_time_tracker/app/home/entries/entries_page.dart';
import 'package:my_time_tracker/app/home/jobs/jobs_page.dart';
import 'package:my_time_tracker/app/home/tab_item.dart';
import 'package:my_time_tracker/common_widgets/custom_text_style.dart';
class HomeScaffold extends StatefulWidget {
const HomeScaffold({
Key key,
@required this.currentTabItem,
@required this.onSelectTab,
@required this.navigatorKeys,
@required this.currentIndex,
@required this.widget,
}) : super(key: key);
final TabItem currentTabItem;
final int currentIndex;
final ValueChanged<int> onSelectTab;
final Map<int, Widget> widget;
final List<GlobalKey<NavigatorState>> navigatorKeys;
@override
_HomeScaffoldState createState() => _HomeScaffoldState();
}
class _HomeScaffoldState extends State<HomeScaffold> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
bottomNavigationBar: BottomNavigationBar(
items: [
_buildItem(TabItem.jobs),
_buildItem(TabItem.entries),
_buildItem(TabItem.account),
],
type: BottomNavigationBarType.shifting,
onTap: (index) => widget.onSelectTab(index),
selectedItemColor: Colors.teal,
unselectedItemColor: Colors.grey[700].withOpacity(.60),
backgroundColor: Colors.white,
elevation: 5.0,
currentIndex: widget.currentIndex,
showUnselectedLabels: true,
selectedLabelStyle: CustomTextStyles.textStyleBold(),
),
body: Stack(
children: [
_buildOffStageNavigator(0),
_buildOffStageNavigator(1),
_buildOffStageNavigator(2),
],
),
);
}
BottomNavigationBarItem _buildItem(TabItem tabItem) {
final itemData = TabItemData.allTabs[tabItem];
return BottomNavigationBarItem(
icon: Icon(itemData.icon),
label: itemData.label,
backgroundColor: itemData.backgroundColor,
);
}
Map<String, WidgetBuilder> _routeBuilders(BuildContext context, int index) {
return {
'/': (context) {
return [
JobsPage(),
EntriesPage.create(context),
AccountPage(),
].elementAt(index);
}
};
}
Widget _buildOffStageNavigator(int index) {
var routeBuilders = _routeBuilders(context, index);
return Offstage(
offstage: widget.currentIndex != index,
child: Navigator(
key: widget.navigatorKeys[index],
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (context) => routeBuilders[routeSettings.name](context),
);
},
),
);
}
}
これは私の TabItem クラスです
import 'package:flutter/material.dart';
enum TabItem { jobs, entries, account }
class TabItemData {
const TabItemData({
@required this.label,
@required this.icon,
this.backgroundColor,
});
final String label;
final IconData icon;
final Color backgroundColor;
static const Map<TabItem, TabItemData> allTabs = {
TabItem.jobs: TabItemData(
label: 'Jobs',
icon: Icons.work,
//backgroundColor: Colors.white,
),
TabItem.entries: TabItemData(
label: 'Entries',
icon: Icons.view_headline,
//backgroundColor: Colors.tealAccent,
),
TabItem.account: TabItemData(
label: 'Account',
icon: Icons.account_circle,
//backgroundColor: Colors.lightBlueAccent,
),
};
}
これは、テキスト入力を含む私の EditJob ページです
import 'package:flutter/material.dart';
import 'package:my_time_tracker/common_widgets/custom_text_style.dart';
//import 'package:my_time_tracker/common_widgets/firebase_exception_alert_dialog.dart';
import 'package:my_time_tracker/common_widgets/form_submit_button.dart';
import 'package:my_time_tracker/common_widgets/platform_alert_dialog.dart';
import 'package:my_time_tracker/common_widgets/show_snack_bar.dart';
import 'package:my_time_tracker/services/database.dart';
import 'package:provider/provider.dart';
import '../models/job.dart';
class EditJobPage extends StatefulWidget {
final Database database;
final Job job;
final ValueChanged<int> onPush;
const EditJobPage({Key key, @required this.database, this.job, this.onPush})
: super(key: key);
static Future<void> show(BuildContext context, {Job job}) async {
final database = Provider.of<Database>(context, listen: false);
await Navigator.of(context, rootNavigator: true).push(MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => EditJobPage(
database: database,
job: job,
),
));
}
@override
_EditJobPageState createState() => _EditJobPageState();
}
class _EditJobPageState extends State<EditJobPage> {
final _formKey = GlobalKey<FormState>();
String _name;
int _ratePerHour;
bool isLoading = false;
@override
void initState() {
super.initState();
if (widget.job != null) {
_name = widget.job.name;
_ratePerHour = widget.job.ratePerHour;
}
}
bool _validateAndSaveForm() {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
return true;
}
return false;
}
String get scaffoldContent {
if (widget.job != null) {
return '${widget.job.name} updated successfully.';
} else {
return '$_name added successfully.';
}
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.teal),
backgroundColor: Colors.white,
title: Text(
widget.job == null ? 'New Job' : 'Edit Job',
style: CustomTextStyles.textStyleTitle(
fontSize: size.height * 0.035,
color: Colors.teal,
),
),
centerTitle: true,
elevation: 0.0,
),
body: _buildContent(),
backgroundColor: Colors.white,
);
}
InputDecoration _buildInputDecoration(
String labelText,
IconData icon,
bool value,
) {
Size size = MediaQuery.of(context).size;
return InputDecoration(
labelText: labelText,
labelStyle: CustomTextStyles.textStyleBold(fontSize: size.height * 0.025),
icon: Icon(
icon,
color: Colors.teal[700],
size: size.height * 0.05,
),
enabled: value,
);
}
Widget _buildContent() {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
elevation: 5.0,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: _buildForm(),
),
SizedBox(
height: 5.0,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.62,
child: FormSubmitButton(
onPressed: isLoading ? null : _submit,
text: 'Save',
),
),
SizedBox(
height: 15.0,
),
],
),
),
),
);
}
Widget _buildForm() {
return Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildFormChildren(),
),
);
}
List<Widget> _buildFormChildren() {
return [
_buildJobNameField(),
_buildJobRateField(),
SizedBox(
height: 15.0,
)
];
}
Widget _buildJobNameField() {
return TextFormField(
decoration: _buildInputDecoration(
'Job name',
Icons.work,
isLoading == false,
),
initialValue: _name,
validator: (value) =>
value == null || value.isEmpty ? 'Name can\'t be empty' : null,
textInputAction: TextInputAction.next,
textCapitalization: TextCapitalization.words,
onSaved: (value) => _name = value,
);
}
Widget _buildJobRateField() {
return TextFormField(
decoration: _buildInputDecoration(
'Rate Per Hour',
Icons.attach_money,
isLoading == false,
),
initialValue: _ratePerHour != null ? '$_ratePerHour' : '',
keyboardType:
TextInputType.numberWithOptions(decimal: false, signed: false),
onSaved: (value) => _ratePerHour = int.tryParse(value) ?? 0,
onEditingComplete: _submit,
);
}
Future<void> _submit() async {
//Future.delayed(Duration(seconds: 5));
if (_validateAndSaveForm()) {
print('Error doesnt occur here0');
setState(() {
isLoading = true;
});
print('Error doesnt occur here1');
try {
final jobs = await widget.database
.jobsStream()
.first
.onError((error, stackTrace) {
return Future.error(error);
});
print('Error doesnt occur here2');
final allNames = jobs.map((job) => job.name).toList();
if (widget.job != null) {
allNames.remove(widget.job.name);
}
if (allNames.contains(_name)) {
PlatformAlertDialog(
title: 'Job already exist',
content: 'Please use a different job name.',
defaultActionText: 'Ok',
).show(context);
} else {
final id = widget.job?.id ?? documentIdFromCurrentDate();
final job = Job(
id: id,
name: _name,
ratePerHour: _ratePerHour,
);
await widget.database.setJob(job);
print('Error doesnt occur here5');
Navigator.of(context).pop();
MyCustomSnackBar(
enabled: widget.job == null ? true : false,
text: scaffoldContent,
onPressed: () => widget.database.deleteJob(job),
).show(context);
}
} catch (e) {
// FirebaseExceptionAlertDialog(title: 'Operation Failed', exception: e)
// .show(context);
print(e);
}
setState(() {
isLoading = false;
});
}
}
}