Last active
February 26, 2021 16:25
-
-
Save wilsonowilson/a25d8d30886c4bcf12d7c1ac9e497ce8 to your computer and use it in GitHub Desktop.
RenderObject Swipe Card
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:math'; | |
import 'package:flutter/gestures.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | |
class FlipCardPlayground extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
GestureDetector( | |
); | |
return FlipCard( | |
numbers: numbers, | |
); | |
} | |
} | |
class FlipCard extends LeafRenderObjectWidget { | |
FlipCard({ | |
required this.numbers, | |
this.height = 300, | |
this.width = 200, | |
}); | |
final double height; | |
final double width; | |
final List<int> numbers; | |
@override | |
RenderObject createRenderObject(BuildContext context) { | |
return RenderFlipCard( | |
height: height, | |
width: width, | |
numbers: numbers, | |
); | |
} | |
@override | |
void updateRenderObject(BuildContext context, RenderFlipCard renderObject) { | |
renderObject | |
..height = height | |
..width = width | |
.._numbers = numbers; | |
} | |
} | |
class RenderFlipCard extends RenderBox { | |
RenderFlipCard({ | |
required double height, | |
required double width, | |
required List<int> numbers, | |
}) : _height = height, | |
_width = width, | |
_numbers = numbers { | |
currentNumber = numbers[0]; | |
_dragRecognizer = VerticalDragGestureRecognizer() | |
..onUpdate = _onDrag | |
..onEnd = _onEnd; | |
} | |
VerticalDragGestureRecognizer? _dragRecognizer; | |
double _dragProgress = 0; | |
List<int> _numbers; | |
int? currentNumber; | |
double _height; | |
set height(double height) { | |
_height = height; | |
markNeedsLayout(); | |
} | |
double _width; | |
set width(double width) { | |
_width = width; | |
markNeedsLayout(); | |
} | |
@override | |
void performLayout() { | |
size = Size(_width, _height); | |
} | |
@override | |
void paint(PaintingContext context, Offset offset) { | |
final canvas = context.canvas; | |
canvas.save(); | |
canvas.translate(offset.dx, offset.dy); | |
final bgPaint2 = Paint()..color = Colors.grey.shade900; | |
canvas.drawRRect( | |
RRect.fromLTRBR(0, 0, _width, _height, Radius.circular(10)), | |
bgPaint2, | |
); | |
_paintText(context, offset); | |
canvas.restore(); | |
} | |
void _drawTopLayer(PaintingContext context, Offset offset) { | |
final bgPaint = Paint()..color = Colors.grey.shade900; | |
final canvas = context.canvas; | |
_useTransform(context, offset, () { | |
canvas.drawRRect( | |
RRect.fromLTRBAndCorners(0, 0, _width, _height / 2, | |
topLeft: Radius.circular(10), topRight: Radius.circular(10)), | |
bgPaint, | |
); | |
}); | |
} | |
void _useTransform(PaintingContext context, Offset offset, Function painter) { | |
final canvas = context.canvas; | |
final transform = Matrix4.identity(); | |
transform.setEntry(3, 2, 0.001); | |
final originalMatrix = transform; | |
final centerOriginTranslation = Alignment.center.alongSize(size); | |
transform.translate( | |
-centerOriginTranslation.dx * 0, centerOriginTranslation.dy * 0.5); | |
canvas.save(); | |
transform.multiply(originalMatrix); | |
transform.translate( | |
centerOriginTranslation.dx, -centerOriginTranslation.dy); | |
final translation = Alignment.topCenter.alongSize(size); | |
transform.translate(translation.dx, translation.dy); | |
canvas.translate(-translation.dx * 2, 0); | |
transform.rotateX(pi * _dragProgress); | |
context.pushTransform(needsCompositing, offset, transform, | |
(context, offset) { | |
painter.call(); | |
}); | |
canvas.restore(); | |
} | |
void _useVerticalTransform( | |
PaintingContext context, Offset offset, Function painter) { | |
final canvas = context.canvas; | |
final transform = Matrix4.identity(); | |
final originalMatrix = transform; | |
final centerOriginTranslation = Alignment.center.alongSize(size); | |
transform.translate( | |
-centerOriginTranslation.dx * 0, centerOriginTranslation.dy * 0.5); | |
canvas.save(); | |
transform.multiply(originalMatrix); | |
transform.translate( | |
centerOriginTranslation.dx, -centerOriginTranslation.dy); | |
final translation = Alignment.topCenter.alongSize(size); | |
transform.translate(translation.dx, translation.dy); | |
canvas.translate(-translation.dx * 2, 0); | |
transform.rotateX(pi); | |
context.pushTransform(needsCompositing, offset, transform, | |
(context, offset) { | |
painter.call(); | |
}); | |
canvas.translate(0, size.height / 2); | |
canvas.restore(); | |
} | |
void _paintText(PaintingContext context, Offset offset) { | |
final canvas = context.canvas; | |
final textStyle = const TextStyle( | |
color: Colors.white, | |
fontSize: 100, | |
fontWeight: FontWeight.bold, | |
); | |
final textSpan = TextSpan( | |
text: '$currentNumber', | |
style: textStyle, | |
); | |
final textPainter = TextPainter( | |
text: textSpan, | |
textDirection: TextDirection.ltr, | |
); | |
final numberAhead = currentNumber! + 1; | |
final textSpan2 = TextSpan( | |
text: '$numberAhead', | |
style: textStyle, | |
); | |
final textPainter2 = TextPainter( | |
text: textSpan2, | |
textDirection: TextDirection.ltr, | |
); | |
textPainter.layout( | |
minWidth: 0, | |
maxWidth: size.width, | |
); | |
textPainter2.layout(); | |
final textCenter = Offset( | |
size.width / 2 - textPainter.width / 2, | |
size.height / 2 - textPainter.height / 2, | |
); | |
final textCenter2 = Offset( | |
size.width / 2 - textPainter2.width / 2, | |
size.height / 2 - textPainter2.height / 2, | |
); | |
canvas.save(); | |
canvas.clipRect( | |
Rect.fromLTRB( | |
0, | |
size.height / 2, | |
size.width, | |
size.height, | |
), | |
); | |
textPainter.paint(canvas, textCenter); | |
canvas.restore(); | |
canvas.save(); | |
canvas.clipRect( | |
Rect.fromLTRB( | |
0, | |
0, | |
size.width, | |
size.height / 2, | |
), | |
); | |
textPainter2.paint(canvas, textCenter2); | |
canvas.restore(); | |
const borderHeight = 2; | |
final borderPaint = Paint()..color = Colors.white; | |
canvas.drawRRect( | |
RRect.fromLTRBR( | |
0, | |
size.height / 2 - borderHeight / 2, | |
size.width, | |
size.height / 2 + borderHeight / 2, | |
Radius.circular(5), | |
), | |
borderPaint, | |
); | |
_drawTopLayer(context, offset); | |
canvas.save(); | |
final topRect = Rect.fromLTRB(0, 0, size.width, size.height / 2); | |
_useTransform(context, offset, () { | |
canvas.save(); | |
canvas.clipRect(topRect); | |
if (_dragProgress <= 0.5) textPainter.paint(canvas, textCenter); | |
canvas.restore(); | |
}); | |
// canvas.save(); | |
_useTransform(context, offset, () { | |
canvas.save(); | |
canvas.clipRect(topRect); | |
// canvas.rotate(pi * 0.1); | |
_useVerticalTransform(context, offset, () { | |
if (_dragProgress > 0.5) textPainter2.paint(canvas, textCenter2); | |
}); | |
canvas.restore(); | |
}); | |
canvas.restore(); | |
// canvas.restore(); | |
} | |
@override | |
void handleEvent(PointerEvent event, HitTestEntry entry) { | |
assert(debugHandleEvent(event, entry)); | |
if (event is PointerDownEvent) { | |
_dragRecognizer!.addPointer(event); | |
} | |
} | |
@override | |
bool hitTestSelf(Offset position) => true; | |
void _onDrag(DragUpdateDetails details) { | |
final position = details.localPosition; | |
final dy = position.dy.clamp(0, size.height); | |
_dragProgress = dy / size.height; | |
markNeedsPaint(); | |
} | |
void _onEnd(DragEndDetails details) { | |
if (_dragProgress > 0.5) updateCount(); | |
_dragProgress = 0; | |
} | |
void updateCount() async { | |
final currentNumberIndex = _numbers.indexOf(currentNumber!); | |
if (currentNumberIndex == _numbers.length - 1) { | |
currentNumber = numbers.first; | |
} else { | |
currentNumber = _numbers[currentNumberIndex + 1]; | |
} | |
markNeedsPaint(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment