Moti Bartov
Moti Bartov

Reputation: 3592

Flutter Widgets arrangement and events

I am trying to build a form in Flutter, please take a look in the screen image I've made so far:

enter image description here

I have few questions regarding this page:

  1. 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?

  2. 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.

  3. 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:

enter image description here

Thanks for any answers.

Upvotes: 2

Views: 3059

Answers (1)

Yamin
Yamin

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

Related Questions