Reputation: 3592
I am trying to build a form in Flutter, please take a look in the screen image I've made so far:
I have few questions regarding this page:
How can I align the DropDown
buttons (Project and Task) so they will be stretched with the same width and that their icons will be at the same position? Also, How can I decorate them with some kind of border?
The input fields Widgets, like Start/Finish and Date, when you click the icon, it will open a Time or Date Picker. I would like that also when user taps on the text input field, it will open the picker, so the user wont be able to edit the content manually.
When I tap the Duration and Note TextFields
, the soft keyboard go over these fields. How can I make the view scrolled up when the keyboard opens and go back when the keyboard is closed?
Here is the code for the whole page:
import 'package:flutter/material.dart';
import 'package:tikal_time_tracker/ui/new_record_title.dart';
import '../data/models.dart';
import 'dart:async';
class NewRecordPage extends StatefulWidget {
List<Project> projects;
NewRecordPage({this.projects});
@override
State<StatefulWidget> createState() {
return new NewRecordPageState();
}
}
class NewRecordPageState extends State<NewRecordPage> {
Project _selectedProject;
TimeOfDay _startTime;
TimeOfDay _finishTime;
DateTime _date;
TextEditingController startTimeController;
TextEditingController finishTimeController;
TextEditingController dateInputController;
bool isButtonEnabled = false;
List<Project> _projects = new List<Project>();
JobTask _selectedTask;
List<JobTask> _tasks = new List<JobTask>();
@override
void initState() {
super.initState();
_projects.addAll(widget.projects);
startTimeController = new TextEditingController(
text: "",
);
finishTimeController = new TextEditingController(text: "");
dateInputController = new TextEditingController(text: "");
}
void _onProjectSelected(Project value) {
setState(() {
_selectedProject = value;
_tasks.clear();
_tasks.addAll(value.tasks);
_selectedTask = null;
});
}
void _onTaskSelected(JobTask value) {
setState(() {
_selectedTask = value;
});
}
void _onPickedStartTime(TimeOfDay startTime) {
setState(() {
_startTime = startTime;
});
}
void _onPickedFinishTime(TimeOfDay finishTime) {
setState(() {
_startTime = finishTime;
});
}
void _setButtonState() {
setState(() {
if (_date != null && _startTime != null) {
print("setButtonState: Button enabled");
isButtonEnabled = true;
} else {
print("setButtonState: Button diabled");
isButtonEnabled = false;
}
});
}
@override
Widget build(BuildContext context) {
final projectsDropDown = Container(
padding: EdgeInsets.symmetric(horizontal: 25.0),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
DropdownButton(
hint: new Text(
"Select a Project",
style: TextStyle(fontSize: 30.0),
),
value: _selectedProject,
isDense: false,
iconSize: 50.0,
items: _projects.map((Project value) {
return new DropdownMenuItem<Project>(
value: value,
child: new Text(
value.name,
style: TextStyle(fontSize: 25.0),
),
);
}).toList(),
onChanged: (Project value) {
_onProjectSelected(value);
})
]));
final tasksDropDown = Container(
padding: EdgeInsets.symmetric(horizontal: 25.0),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Expanded(
child: new DropdownButton(
iconSize: 50.0,
hint: Text(
"Select a Task",
style: TextStyle(fontSize: 30.0),
),
value: _selectedTask,
items: _tasks.map((JobTask value) {
return new DropdownMenuItem<JobTask>(
value: value,
child: new Text(
value
.toString()
.substring(value.toString().indexOf('.') + 1),
style: TextStyle(fontSize: 25.0),
),
);
}).toList(),
onChanged: (JobTask value) {
_onTaskSelected(value);
})),
],
),
);
final srartTimePicker = Container(
padding: EdgeInsets.only(left: 32.0, right: 32.0, top: 8.0),
child: new Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
print("onTap start");
_showStartTimeDialog();
},
child: Icon(Icons.access_time),
),
),
Container(
child: new Flexible(
child: new TextField(
decoration: InputDecoration(hintText: "Start",
contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(20.0))),
maxLines: 1,
controller: startTimeController)),
),
],
),
);
final finishTimePicker = Container(
padding: EdgeInsets.only(left: 32.0, right: 32.0, top: 8.0),
child: new Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
print("onTap finish");
_showFinishTimeDialog();
},
child: Icon(Icons.access_time),
),
),
Container(
child: new Flexible(
child: new TextField(
decoration: InputDecoration(hintText: "Finish",
contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(20.0))),
maxLines: 1,
controller: finishTimeController)),
),
],
),
);
final durationInput = Container(
padding: EdgeInsets.only(left: 32.0, right: 32.0, top: 8.0),
child: new Row(
children: <Widget>[
Container(
child: new Flexible(
child: new TextField(
decoration: InputDecoration(hintText: "Duration",
contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(20.0))),
maxLines: 1)),
),
],
),
);
final dateInput = Container(
padding: EdgeInsets.only(left: 32.0, right: 32.0, top: 8.0),
child: new Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
print("onTap dateInput");
_showDatePicker();
},
child: Icon(Icons.date_range),
),
),
Container(
child: new Flexible(
child: new TextField(
decoration: InputDecoration(hintText: "Date",
contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(20.0))),
maxLines: 1,
controller: dateInputController)),
),
],
),
);
return Scaffold(
appBar: new AppBar(
title: new Text("Edit New Record"),
),
body: Center(
child: ListView(
shrinkWrap: true,
children: <Widget>[
new NewRecordTitle(),
projectsDropDown,
tasksDropDown,
dateInput,
srartTimePicker,
finishTimePicker,
durationInput,
Container(
padding: EdgeInsets.only(left: 32.0, right: 32.0, top: 8.0),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
TextField(maxLines: 3,
decoration: InputDecoration(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(20.0)),
contentPadding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
hintText: "Note:"))
],
)),
Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
child: Material(
borderRadius: BorderRadius.circular(10.0),
shadowColor: isButtonEnabled
? Colors.orangeAccent.shade100
: Colors.grey.shade100,
elevation: 2.0,
child: MaterialButton(
minWidth: 200.0,
height: 42.0,
onPressed: () {
if (isButtonEnabled) {
print("Button Clicked");
}
},
color: isButtonEnabled ? Colors.orangeAccent : Colors.grey,
child: Text("Save", style: TextStyle(color: Colors.white)),
),
),
)
],
)
],
),
),
);
}
Future<Null> _showStartTimeDialog() async {
final TimeOfDay picked =
await showTimePicker(context: context, initialTime: TimeOfDay.now());
if (picked != null) {
setState(() {
_startTime = picked;
startTimeController =
new TextEditingController(text:
"${picked.hour}:${picked.minute}");
});
_setButtonState();
}
}
Future<Null> _showDatePicker() async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(DateTime.now()
.year - 1, 1),
lastDate: DateTime(DateTime
.now()
.year, 12));
if (picked != null) {
setState(() {
_date = picked;
dateInputController =
new TextEditingController(text:
"${picked.month}:${picked.year}");
});
}
}
Future<Null> _showFinishTimeDialog() async {
final TimeOfDay picked =
await showTimePicker(context: context, initialTime: TimeOfDay.now());
if (picked != null) {
setState(() {
_finishTime = picked;
finishTimeController =
new TextEditingController(text:
"${picked.hour}:${picked.minute}");
calculateDuration(
date: DateTime.now(),
startTime: _startTime,
finishTime: _finishTime);
});
}
}
}
TimeOfDay calculateDuration(
{DateTime date, TimeOfDay startTime, TimeOfDay finishTime}) {
DateTime s = new DateTime(date.year, date.month, date.day, startTime.hour, startTime.minute);
DateTime f = new DateTime(date.year, date.month, date.day, finishTime.hour, finishTime.minute);
int milisecs = f.millisecond - s.millisecond;
print("$milisecs");
DateTime total = new DateTime.fromMillisecondsSinceEpoch(milisecs);
print("${total.hour}:${total.minute}");
}
Please see a screen shot with the keyboard that go over the widgets:
Thanks for any answers.
Upvotes: 2
Views: 3059
Reputation: 3018
1) Easy, Just set an equal width their texts :
DropdownButton(
hint: Container(
width: MediaQuery.of(context).size.width * 0.7, //You can set width here.
child: new Text(
"Select a Project",
style: TextStyle(fontSize: 30.0),
),
),
value: _selectedProject,
isDense: false,
iconSize: 50.0,
items: _projects.map((value) {
return new DropdownMenuItem(
value: value,
child: new Text(
value.name,
style: TextStyle(fontSize: 25.0),
),
);
}).toList(),
onChanged: (value) {
_onProjectSelected(value);
})
2) GestureDecorator should wrap the whole row, The problem is with the textField. TextFields has their own gestureDecorator. I think there is a way to put the outer GestureDecorator in higher priority. But this is not a proper way.
TextFields should be used only to edit a text. use text widgets instead. look at this example:
final srartTimePicker = Container(
padding: EdgeInsets.only(left: 32.0, right: 32.0, top: 8.0),
child: GestureDetector( //Thid widgets wraps every other widget
onTap: () {
print("onTap start");
_showStartTimeDialog();
},
child: new Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.access_time),
),
Container(
child: new Flexible(
fit: FlexFit.tight,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).disabledColor,
width: 2.0,
style: BorderStyle.solid,
),
borderRadius: BorderRadius.circular(20.0)),
child: Padding(
padding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
child: new Text(
_startTime != null
? "${_startTime.hour}:${_startTime
.minute}"
: "Start",
style: TextStyle(),
),
),
),
),
),
],
),
));
3) I didn't face this issue running your code. Whenever the soft keyboard pops up, the content should scroll up to focus on current textField. update the question and put a screenshot for this one if it's possible.
For keyboard over screen problem look at these articles: Issue and the Solution
Upvotes: 2