DataRow is used in DataTable to create a row in the table in Flutter. DataRow has onSelectChaned
event to do something when the row is selected but doesn’t have onDoubleTap
event.
What should we do if we want double tap or double click event? Do we need to install a package? No, we don’t.
We can detect double tap/click without any other package.
You can check the complete code in my GitHub repo.
Define the row data class
Firstly, let’s create a class that will be used in the DataTable. The data in this class will be shown on the row.
class DoubleTapTableRowData {
final String filepath;
final String dataType;
final String remark;
DoubleTapTableRowData({
required this.filepath,
required this.dataType,
required this.remark,
});
@override
bool operator ==(Object other) {
return other is DoubleTapTableRowData && other.hashCode == hashCode;
}
@override
int get hashCode => Object.hash(filepath, dataType, remark);
}
We need to detect double tap/click when the action is done on the same row. The two methods are overridden here to check whether the item is the same or not. It’s ok to use another way to check it if you find it.
Check the following post if you want to know more about the two methods to compare two objects.
Define DoubleTapChecker to detect double tap/click on a row
Let’s fulfill the DataTable.
import 'package:flutter/material.dart';
class RowDoubleTap extends StatefulWidget {
@override
State<StatefulWidget> createState() => _RowDoubleTap();
}
class _RowDoubleTap extends State<RowDoubleTap> {
List<DoubleTapTableRowData> data = [];
final doubleTapChecker = _DoubleTapChecker<DoubleTapTableRowData>();
String doubleTapText = "double tap result here";
void _initializeData() {
data = [
DoubleTapTableRowData(
filepath: "/home/user/abc.txt",
dataType: "text",
remark: "abc text",
),
DoubleTapTableRowData(
filepath: "/home/user/sound.wav",
dataType: "wav",
remark: "special sound",
),
DoubleTapTableRowData(
filepath: "/home/user/secret",
dataType: "text",
remark: "secret into",
),
];
}
@override
void initState() {
_initializeData();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Row Double Tap"),
),
body: Center(
child: Column(
children: [
_doubleTapRow(),
Text(doubleTapText),
],
),
),
);
}
Widget _doubleTapRow() {
return DataTable(
showCheckboxColumn: false,
columns: [
DataColumn(label: Text('Filepath')),
DataColumn(label: Text('Data type')),
DataColumn(label: Text('Remark')),
],
rows: data
.map(
(e) => DataRow(
onSelectChanged: ((selected) {
setState(() {
if (doubleTapChecker.isDoubleTap(e)) {
doubleTapText = "Double tapped ${e.filepath}";
return;
}
doubleTapText = "Single tap ${e.filepath}";
});
}),
cells: [
DataCell(Text(e.filepath)),
DataCell(Text(e.dataType)),
DataCell(
Text(e.remark),
onTap: () => setState(
() => doubleTapText = "onTap event for ${e.filepath}"),
),
],
),
)
.toList(),
);
}
}
The view image is the following.
To apply the same definition to all rows, I used .map()
method. onSelectChanged
is the main place for this post. As you can see, double tap/click detection is done in the method.
rows: data
.map(
(e) => DataRow(
onSelectChanged: ((selected) {
setState(() {
if (doubleTapChecker.isDoubleTap(e)) {
doubleTapText = "Double tapped ${e.filepath}";
return;
}
doubleTapText = "Single tap ${e.filepath}";
});
}),
cells: [
DataCell(Text(e.filepath)),
DataCell(Text(e.dataType)),
DataCell(
Text(e.remark),
onTap: () => setState(
() => doubleTapText = "onTap event for ${e.filepath}"),
),
],
),
)
.toList(),
Then, let’s check the class.
class _DoubleTapChecker<T> {
T? _lastSelectedItem;
DateTime _lastTimestamp = DateTime.now();
bool isDoubleTap(T item) {
if (_lastSelectedItem == null || _lastSelectedItem != item) {
_lastSelectedItem = item;
_lastTimestamp = DateTime.now();
return false;
}
final currentTimestamp = DateTime.now();
final duration = currentTimestamp.difference(_lastTimestamp).inMilliseconds;
_lastTimestamp = DateTime.now();
print("last: $_lastTimestamp, current: $currentTimestamp, duration: $duration");
return duration < 400;
}
}
I defined this class in the same file but it’s better to define it in another file to make it reusable. It uses generics. It shouldn’t be a problem if you use it for a different data type.
The following is the initialization part to set item to _lastSelectedItem
. It must be set for the first time and if the item is different from the previous one. Note that it might not work if hashCode
and ==
operator are not implemented correctly in the class.
bool isDoubleTap(T item) {
if (_lastSelectedItem == null || _lastSelectedItem != item) {
_lastSelectedItem = item;
_lastTimestamp = DateTime.now();
return false;
}
If the same row is tapped/clicked twice, we need to check the duration between the two actions. It can be calculated by difference
method. The action is detected as double tap/click if the duration is smaller than the specified time.
It’s up to you to decide how long you want the duration to be but I think 400 is good enough.
final currentTimestamp = DateTime.now();
final duration = currentTimestamp.difference(_lastTimestamp).inMilliseconds;
_lastTimestamp = DateTime.now();
print("last: $_lastTimestamp, current: $currentTimestamp, duration: $duration");
return duration < 400;
Sample Video
Note that double click doesn’t work if onTap
event is defined on the cell where you click. onTap
is defined for secret info
cell. You can see the behavior around 6 seconds in the video.
Comments