Skip to content

Instantly share code, notes, and snippets.

@nicoaudy
Created March 7, 2023 07:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicoaudy/864c9dedd966eb7351f4308ff6e45e17 to your computer and use it in GitHub Desktop.
Save nicoaudy/864c9dedd966eb7351f4308ff6e45e17 to your computer and use it in GitHub Desktop.
TouchableOpacity react native look a like on flutter
// ignore_for_file: unnecessary_null_comparison
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
class TouchableOpacity extends StatefulWidget {
/// Creates a widget that uses Gesture Detector internally and works in the same way
/// but fades the child when the user touches it.
///
/// By default the opacity of the child is animated to 0.2 when the user touches it,
/// this can be changed by providing a value for [activeOpacity]
///
/// Pan and scale callbacks cannot be used simultaneously because scale is a
/// superset of pan. Simply use the scale callbacks instead.
///
/// Horizontal and vertical drag callbacks cannot be used simultaneously
/// because a combination of a horizontal and vertical drag is a pan. Simply
/// use the pan callbacks instead.
///
/// By default, gesture detectors contribute semantic information to the tree
/// that is used by assistive technology.
TouchableOpacity({
Key? key,
required this.child,
this.activeOpacity = 0.2,
this.onTapDown,
this.onTapUp,
this.onTap,
this.onTapCancel,
this.onDoubleTap,
this.onLongPress,
this.onLongPressStart,
this.onLongPressMoveUpdate,
this.onLongPressUp,
this.onLongPressEnd,
this.onVerticalDragDown,
this.onVerticalDragStart,
this.onVerticalDragUpdate,
this.onVerticalDragEnd,
this.onVerticalDragCancel,
this.onHorizontalDragDown,
this.onHorizontalDragStart,
this.onHorizontalDragUpdate,
this.onHorizontalDragEnd,
this.onHorizontalDragCancel,
this.onForcePressStart,
this.onForcePressPeak,
this.onForcePressUpdate,
this.onForcePressEnd,
this.onPanDown,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onPanCancel,
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
this.behavior,
this.excludeFromSemantics = false,
this.dragStartBehavior = DragStartBehavior.start,
}) : assert(excludeFromSemantics != null),
assert(dragStartBehavior != null),
assert(() {
final bool haveVerticalDrag = onVerticalDragStart != null ||
onVerticalDragUpdate != null ||
onVerticalDragEnd != null;
final bool haveHorizontalDrag = onHorizontalDragStart != null ||
onHorizontalDragUpdate != null ||
onHorizontalDragEnd != null;
final bool havePan =
onPanStart != null || onPanUpdate != null || onPanEnd != null;
final bool haveScale = onScaleStart != null ||
onScaleUpdate != null ||
onScaleEnd != null;
if (havePan || haveScale) {
if (havePan && haveScale) {
throw FlutterError('Incorrect TouchableOpacity arguments.\n'
'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer.');
}
final String recognizer = havePan ? 'pan' : 'scale';
if (haveVerticalDrag && haveHorizontalDrag) {
throw FlutterError('Incorrect TouchableOpacity arguments.\n'
'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.');
}
}
return true;
}()),
super(key: key);
@override
State<StatefulWidget> createState() {
return _TouchableOpacityState();
}
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.child}
final Widget child;
/// Double value that will be used as the opacity of the child when a pointer
/// down event is registered. Defaults to 0.2, value must be between 1.0 and
/// 0.0 .
final double activeOpacity;
/// A pointer that might cause a tap has contacted the screen at a particular
/// location.
///
/// This is called after a short timeout, even if the winning gesture has not
/// yet been selected. If the tap gesture wins, [onTapUp] will be called,
/// otherwise [onTapCancel] will be called.
final GestureTapDownCallback? onTapDown;
/// A pointer that will trigger a tap has stopped contacting the screen at a
/// particular location.
///
/// This triggers immediately before [onTap] in the case of the tap gesture
/// winning. If the tap gesture did not win, [onTapCancel] is called instead.
final GestureTapUpCallback? onTapUp;
/// A tap has occurred.
///
/// This triggers when the tap gesture wins. If the tap gesture did not win,
/// [onTapCancel] is called instead.
///
/// See also:
///
/// * [onTapUp], which is called at the same time but includes details
/// regarding the pointer position.
final GestureTapCallback? onTap;
/// The pointer that previously triggered [onTapDown] will not end up causing
/// a tap.
///
/// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
/// the tap gesture did not win.
final GestureTapCancelCallback? onTapCancel;
/// The user has tapped the screen at the same location twice in quick
/// succession.
final GestureTapCallback? onDoubleTap;
/// Called when a long press gesture has been recognized.
///
/// Triggered when a pointer has remained in contact with the screen at the
/// same location for a long period of time.
///
/// See also:
///
/// * [onLongPressStart], which has the same timing but has data for the
/// press location.
final GestureLongPressCallback? onLongPress;
/// Callback for long press start with gesture location.
///
/// Triggered when a pointer has remained in contact with the screen at the
/// same location for a long period of time.
///
/// See also:
///
/// * [onLongPress], which has the same timing but without the location data.
final GestureLongPressStartCallback? onLongPressStart;
/// A pointer has been drag-moved after a long press.
final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
/// A pointer that has triggered a long-press has stopped contacting the screen.
///
/// See also:
///
/// * [onLongPressEnd], which has the same timing but has data for the up
/// gesture location.
final GestureLongPressUpCallback? onLongPressUp;
/// A pointer that has triggered a long-press has stopped contacting the screen.
///
/// See also:
///
/// * [onLongPressUp], which has the same timing but without the location data.
final GestureLongPressEndCallback? onLongPressEnd;
/// A pointer has contacted the screen and might begin to move vertically.
final GestureDragDownCallback? onVerticalDragDown;
/// A pointer has contacted the screen and has begun to move vertically.
final GestureDragStartCallback? onVerticalDragStart;
/// A pointer that is in contact with the screen and moving vertically has
/// moved in the vertical direction.
final GestureDragUpdateCallback? onVerticalDragUpdate;
/// A pointer that was previously in contact with the screen and moving
/// vertically is no longer in contact with the screen and was moving at a
/// specific velocity when it stopped contacting the screen.
final GestureDragEndCallback? onVerticalDragEnd;
/// The pointer that previously triggered [onVerticalDragDown] did not
/// complete.
final GestureDragCancelCallback? onVerticalDragCancel;
/// A pointer has contacted the screen and might begin to move horizontally.
final GestureDragDownCallback? onHorizontalDragDown;
/// A pointer has contacted the screen and has begun to move horizontally.
final GestureDragStartCallback? onHorizontalDragStart;
/// A pointer that is in contact with the screen and moving horizontally has
/// moved in the horizontal direction.
final GestureDragUpdateCallback? onHorizontalDragUpdate;
/// A pointer that was previously in contact with the screen and moving
/// horizontally is no longer in contact with the screen and was moving at a
/// specific velocity when it stopped contacting the screen.
final GestureDragEndCallback? onHorizontalDragEnd;
/// The pointer that previously triggered [onHorizontalDragDown] did not
/// complete.
final GestureDragCancelCallback? onHorizontalDragCancel;
/// A pointer has contacted the screen and might begin to move.
final GestureDragDownCallback? onPanDown;
/// A pointer has contacted the screen and has begun to move.
final GestureDragStartCallback? onPanStart;
/// A pointer that is in contact with the screen and moving has moved again.
final GestureDragUpdateCallback? onPanUpdate;
/// A pointer that was previously in contact with the screen and moving
/// is no longer in contact with the screen and was moving at a specific
/// velocity when it stopped contacting the screen.
final GestureDragEndCallback? onPanEnd;
/// The pointer that previously triggered [onPanDown] did not complete.
final GestureDragCancelCallback? onPanCancel;
/// The pointers in contact with the screen have established a focal point and
/// initial scale of 1.0.
final GestureScaleStartCallback? onScaleStart;
/// The pointers in contact with the screen have indicated a new focal point
/// and/or scale.
final GestureScaleUpdateCallback? onScaleUpdate;
/// The pointers are no longer in contact with the screen.
final GestureScaleEndCallback? onScaleEnd;
/// The pointer is in contact with the screen and has pressed with sufficient
/// force to initiate a force press. The amount of force is at least
/// [ForcePressGestureRecognizer.startPressure].
///
/// Note that this callback will only be fired on devices with pressure
/// detecting screens.
final GestureForcePressStartCallback? onForcePressStart;
/// The pointer is in contact with the screen and has pressed with the maximum
/// force. The amount of force is at least
/// [ForcePressGestureRecognizer.peakPressure].
///
/// Note that this callback will only be fired on devices with pressure
/// detecting screens.
final GestureForcePressPeakCallback? onForcePressPeak;
/// A pointer is in contact with the screen, has previously passed the
/// [ForcePressGestureRecognizer.startPressure] and is either moving on the
/// plane of the screen, pressing the screen with varying forces or both
/// simultaneously.
///
/// Note that this callback will only be fired on devices with pressure
/// detecting screens.
final GestureForcePressUpdateCallback? onForcePressUpdate;
/// The pointer is no longer in contact with the screen.
///
/// Note that this callback will only be fired on devices with pressure
/// detecting screens.
final GestureForcePressEndCallback? onForcePressEnd;
/// How this gesture detector should behave during hit testing.
///
/// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
/// [HitTestBehavior.translucent] if child is null.
final HitTestBehavior? behavior;
/// Whether to exclude these gestures from the semantics tree. For
/// example, the long-press gesture for showing a tooltip is
/// excluded because the tooltip itself is included in the semantics
/// tree directly and so having a gesture to show it would result in
/// duplication of information.
final bool excludeFromSemantics;
/// Determines the way that drag start behavior is handled.
///
/// If set to [DragStartBehavior.start], gesture drag behavior will
/// begin upon the detection of a drag gesture. If set to
/// [DragStartBehavior.down] it will begin when a down event is first detected.
///
/// In general, setting this to [DragStartBehavior.start] will make drag
/// animation smoother and setting it to [DragStartBehavior.down] will make
/// drag behavior feel slightly more reactive.
///
/// By default, the drag start behavior is [DragStartBehavior.start].
///
/// Only the [onStart] callbacks for the [VerticalDragGestureRecognizer],
/// [HorizontalDragGestureRecognizer] and [PanGestureRecognizer] are affected
/// by this setting.
///
/// See also:
///
/// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
final DragStartBehavior dragStartBehavior;
}
class _TouchableOpacityState extends State<TouchableOpacity>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 100),
lowerBound: widget.activeOpacity,
upperBound: 1.0,
value: 1.0);
_controller.addListener(() {
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTap: widget.onTap,
onTapCancel: _onTapCancel,
onDoubleTap: widget.onDoubleTap,
onLongPress: widget.onLongPress,
onLongPressStart: widget.onLongPressStart,
onLongPressMoveUpdate: widget.onLongPressMoveUpdate,
onLongPressUp: widget.onLongPressUp,
onLongPressEnd: widget.onLongPressEnd,
onVerticalDragDown: widget.onVerticalDragDown,
onVerticalDragStart: widget.onVerticalDragStart,
onVerticalDragUpdate: widget.onVerticalDragUpdate,
onVerticalDragEnd: widget.onVerticalDragEnd,
onVerticalDragCancel: widget.onVerticalDragCancel,
onHorizontalDragDown: widget.onHorizontalDragDown,
onHorizontalDragStart: widget.onHorizontalDragStart,
onHorizontalDragUpdate: widget.onHorizontalDragUpdate,
onHorizontalDragEnd: widget.onHorizontalDragEnd,
onHorizontalDragCancel: widget.onHorizontalDragCancel,
onForcePressStart: widget.onForcePressStart,
onForcePressPeak: widget.onForcePressPeak,
onForcePressUpdate: widget.onForcePressUpdate,
onForcePressEnd: widget.onForcePressEnd,
onPanDown: widget.onPanDown,
onPanStart: widget.onPanStart,
onPanUpdate: widget.onPanUpdate,
onPanEnd: widget.onPanEnd,
onPanCancel: widget.onPanCancel,
onScaleStart: widget.onScaleStart,
onScaleUpdate: widget.onScaleUpdate,
onScaleEnd: widget.onScaleEnd,
behavior: widget.behavior,
excludeFromSemantics: widget.excludeFromSemantics,
dragStartBehavior: widget.dragStartBehavior,
child: Opacity(
opacity: _controller.value,
child: widget.child,
),
);
}
void _onTapDown(TapDownDetails details) {
if (widget.activeOpacity != 1.0) {
_controller.reverse();
}
if (widget.onTapDown != null) {
widget.onTapDown!(details);
}
}
void _onTapUp(TapUpDetails details) {
if (widget.activeOpacity != 1.0) {
_controller.forward();
}
if (widget.onTapUp != null) {
widget.onTapUp!(details);
}
}
void _onTapCancel() {
if (widget.activeOpacity != 1.0) {
_controller.forward();
}
if (widget.onTapCancel != null) {
widget.onTapCancel!();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment