Complete GestureDetector Mastery
In this tutorial, weโll completely master GestureDetector, Flitterโs core interactive widget. Letโs learn step by step how to handle all user gestures from clicking to dragging.
๐ฏ Learning Objectives
After completing this tutorial, youโll be able to:
- Handle basic click events and update the screen
- Implement advanced interactions with double-click and mouse hover events
- Create dynamic interfaces using drag gestures
- Build complex interaction systems by combining multiple gestures
- Enhance user experience with cursor styles and visual feedback
๐ Prerequisites
- Understanding of StatefulWidget and setState() usage
- Knowledge of Container and basic styling
- Familiarity with class-based state management patterns
๐ช What is GestureDetector?
GestureDetector is Flitterโs core widget that enables you to detect and respond to all user gestures (touch, click, drag, hover, etc.).
๐ Important Concept
In Flitter, you cannot add event handlers directly to Container or Text widgets. You must wrap them with GestureDetector to enable interaction.
// โ Wrong way - won't work!
Container({
onClick: () => {}, // Error!
child: Text("Click")
})
// โ
Correct way
GestureDetector({
onClick: () => {},
child: Container({
child: Text("Click")
})
})
1. Mastering Basic Click Events
1.1 onClick Event Basics
Letโs start with the most basic click event:
class ClickCounter extends StatefulWidget {
createState() {
return new ClickCounterState();
}
}
class ClickCounterState extends State {
count = 0;
build(context) {
return GestureDetector({
onClick: () => {
this.setState(() => {
this.count++;
});
},
child: Container({
width: 150,
height: 80,
decoration: new BoxDecoration({
color: '#3B82F6',
borderRadius: 8
}),
child: Text(`Click count: ${this.count}`, {
style: { color: '#FFFFFF', fontSize: 16 }
})
})
});
}
}
1.2 Using Mouse Event Information
The onClick event provides a MouseEvent object:
GestureDetector({
onClick: (event) => {
console.log(`Click position: (${event.clientX}, ${event.clientY})`);
console.log(`Button used: ${event.button}`); // 0: left, 1: middle, 2: right
console.log(`Ctrl key pressed: ${event.ctrlKey}`);
},
child: yourWidget
})
1.3 Practice: Enhanced Click Counter
Implement the following in the code area above:
- Add
clickCount
state variable - Implement GestureDetector that increments count on each click
- Create UI that shows click count in real-time
2. Double-Click and Advanced Click Events
2.1 Double-Click Events
Double-clicks are handled separately from onClick:
GestureDetector({
onClick: () => {
console.log("Single click!");
},
onDoubleClick: () => {
console.log("Double click!");
},
child: yourWidget
})
2.2 Handling Different Mouse Buttons
GestureDetector({
onMouseDown: (event) => {
switch(event.button) {
case 0: console.log("Left button pressed"); break;
case 1: console.log("Middle button pressed"); break;
case 2: console.log("Right button pressed"); break;
}
},
onMouseUp: (event) => {
console.log("Mouse button released");
},
child: yourWidget
})
2.3 Practice: Various Click Handlers
- Add
doubleClickCount
state variable - Implement double-click event handler
- Count clicks and double-clicks separately
3. Mouse Hover Events and Visual Feedback
3.1 Hover Event Basics
Letโs implement important hover effects for the web:
class HoverButton extends StatefulWidget {
createState() {
return new HoverButtonState();
}
}
class HoverButtonState extends State {
isHovered = false;
build(context) {
return GestureDetector({
onMouseEnter: () => {
this.setState(() => {
this.isHovered = true;
});
},
onMouseLeave: () => {
this.setState(() => {
this.isHovered = false;
});
},
child: Container({
decoration: new BoxDecoration({
color: this.isHovered ? '#4F46E5' : '#3B82F6',
borderRadius: 8
}),
child: Text("Hover me!", {
style: { color: '#FFFFFF' }
})
})
});
}
}
3.2 Changing Cursor Styles
GestureDetector supports various cursor styles:
GestureDetector({
cursor: 'pointer', // Basic pointer
// Or other styles:
// 'grab', 'grabbing', 'move', 'not-allowed', 'help', etc.
child: yourWidget
})
3.3 Available Cursor Styles
// Basic cursors
'default' | 'pointer' | 'move' | 'text' | 'wait' | 'help'
// Resize cursors
'e-resize' | 'ne-resize' | 'nw-resize' | 'n-resize'
'se-resize' | 'sw-resize' | 's-resize' | 'w-resize'
// Special cursors
'grab' | 'grabbing' | 'crosshair' | 'not-allowed'
3.4 Practice: Hover-Reactive Button
- Add
isHovered
andhoverCount
state variables - Create button that changes color and cursor on hover
- Count and display hover occurrences
4. Complete Drag Gesture Mastery
4.1 Basic Drag Events
Drag is the most complex but powerful interaction:
class DraggableBox extends StatefulWidget {
createState() {
return new DraggableBoxState();
}
}
class DraggableBoxState extends State {
isDragging = false;
dragStartPosition = { x: 0, y: 0 };
currentPosition = { x: 0, y: 0 };
build(context) {
return GestureDetector({
onDragStart: (event) => {
this.setState(() => {
this.isDragging = true;
this.dragStartPosition = {
x: event.clientX,
y: event.clientY
};
});
},
onDragMove: (event) => {
this.setState(() => {
this.currentPosition = {
x: event.clientX - this.dragStartPosition.x,
y: event.clientY - this.dragStartPosition.y
};
});
},
onDragEnd: () => {
this.setState(() => {
this.isDragging = false;
});
},
child: Container({
decoration: new BoxDecoration({
color: this.isDragging ? '#EF4444' : '#3B82F6'
}),
child: Text("Try dragging!")
})
});
}
}
4.2 Adding Drag Constraints
You can limit drag areas or restrict movement to specific directions:
// Drag only horizontally
onDragMove: (event) => {
this.setState(() => {
this.position = {
x: Math.max(0, Math.min(maxWidth, event.clientX - this.startX)),
y: this.position.y // Y coordinate fixed
};
});
}
// Drag only within specific area
onDragMove: (event) => {
const newX = event.clientX - this.startX;
const newY = event.clientY - this.startY;
this.setState(() => {
this.position = {
x: Math.max(0, Math.min(maxX, newX)),
y: Math.max(0, Math.min(maxY, newY))
};
});
}
4.3 Practice: Drag Tracker
- Add
isDragging
,dragCount
,currentPosition
state variables - Create widget that changes color while dragging
- Display drag position and count in real-time
5. Comprehensive Practice: Complete Interactive Widget
Now letโs combine all gestures to create a complete interactive widget!
5.1 Features to Complete
State Variables:
class InteractiveDemoState extends State {
// Counters for each gesture
clickCount = 0;
doubleClickCount = 0;
hoverCount = 0;
dragCount = 0;
// Current states
isHovered = false;
isDragging = false;
// Additional info
lastEventType = "None";
lastEventTime = null;
}
Event Handlers:
handleClick() {
this.setState(() => {
this.clickCount++;
this.lastEventType = "Click";
this.lastEventTime = new Date().toLocaleTimeString();
});
}
handleDoubleClick() {
this.setState(() => {
this.doubleClickCount++;
this.lastEventType = "Double Click";
this.lastEventTime = new Date().toLocaleTimeString();
});
}
// Implement other handlers similarly
5.2 Complete GestureDetector Setup
GestureDetector({
onClick: () => this.handleClick(),
onDoubleClick: () => this.handleDoubleClick(),
onMouseEnter: () => this.handleMouseEnter(),
onMouseLeave: () => this.handleMouseLeave(),
onDragStart: () => this.handleDragStart(),
onDragMove: (event) => this.handleDragMove(event),
onDragEnd: () => this.handleDragEnd(),
cursor: 'pointer',
child: Container({
width: 250,
height: 120,
decoration: new BoxDecoration({
color: this.getBoxColor(), // Color based on state
borderRadius: 12,
border: this.isHovered ? Border.all({ color: '#FFFFFF', width: 2 }) : null
}),
child: this.buildBoxContent()
})
})
5.3 State-Based Color Logic
getBoxColor() {
if (this.isDragging) return '#DC2626'; // Red
if (this.isHovered) return '#7C3AED'; // Purple
return '#3B82F6'; // Default blue
}
5.4 TODO: Practice Assignment
Complete the following in the code area above:
- Define state variables: All gesture-specific counters and state variables
- Implement event handlers: Complete processing logic for each gesture
- Visual feedback: Color changes and border effects based on state
- Information display: Real-time statistics and current state display
6. Advanced Gesture Patterns and Tips
6.1 Understanding Gesture Priority
Priority when multiple gestures are set simultaneously:
GestureDetector({
onClick: () => {
// Executes on short touch/click and immediate release
},
onDragStart: () => {
// Executes when moving after touch/click (onClick is cancelled)
},
onDoubleClick: () => {
// Executes on rapid consecutive clicks (first onClick is delayed)
}
})
6.2 Controlling Event Propagation
onClick: (event) => {
event.preventDefault(); // Prevent default browser behavior
event.stopPropagation(); // Stop event bubbling
// Execute custom logic
this.handleCustomClick();
}
6.3 Performance Optimization Tips
Handling frequent events:
// Use throttling for frequent events like onMouseMove
let lastUpdate = 0;
onMouseMove: (event) => {
const now = Date.now();
if (now - lastUpdate < 16) return; // 60fps limit
lastUpdate = now;
this.updateMousePosition(event.clientX, event.clientY);
}
Optimizing heavy calculations:
onClick: () => {
// Separate heavy calculations from state updates
const result = this.expensiveCalculation();
this.setState(() => {
this.result = result;
});
}
๐ฏ Expected Results
The completed interactive widget should behave as follows:
- Blue box: Default waiting state
- Purple box: On mouse hover + white border
- Red box: While dragging
- Real-time statistics: Display occurrence count for each gesture type
- State display: Current interaction state (waiting/hover/drag)
- Last event: Most recently occurred gesture type
๐ Additional Challenge Tasks
If youโve completed the basic practice, try these challenges:
Level 1: Basic Extensions
- Right-click menu implementation (check button === 2 in onMouseDown)
- Keyboard combinations detection (Ctrl+click, Shift+drag, etc.)
- Measure and display drag distance
Level 2: Intermediate Features
- Drag momentum implementation (continued movement after drag ends)
- Snap functionality addition (align to grid)
- Multi-touch simulation (simultaneous interaction in multiple areas)
Level 3: Advanced Applications
- Gesture history recording and playback
- Custom gesture recognizer creation (circle drawing, swipe patterns, etc.)
- Performance monitoring addition (measure event processing time)
๐ Completion Checklist
Check if youโve achieved all learning objectives:
- โ Basic click events perfectly handled
- โ Double-click and hover advanced interactions implemented
- โ Drag gestures completely mastered
- โ Multiple gesture combinations mastered
- โ Visual feedback user experience enhanced
- โ Performance and accessibility considerations implemented
๐ Next Steps
If youโve completely mastered GestureDetector, proceed to the next tutorial:
- Complete Draggable Widget Mastery - More advanced drag and drop functionality
- Advanced StatefulWidget Patterns - Complex state management and lifecycle
- Form Input Processing - User input and validation