Flip page effect using Flutter and flip_widget library - flutter

I am working on a flutter app, and what I am trying to implement is a flip effect using this plugin Flip Widget.
The plugin works fine for the right to left (next page) drag effect but I can't make the same effect work from left to right (previous page), I have tried different values for the tilt and percentage value (negative and positive) and nothing worked so far. Here is the code
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flip_widget/flip_widget.dart';
import 'dart:math' as math;
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
const double _MinNumber = 0.008;
double _clampMin(double v) {
if (v < _MinNumber && v > -_MinNumber) {
if (v >= 0) {
v = _MinNumber;
} else {
v = -_MinNumber;
}
}
return v;
}
class _MyAppState extends State<MyApp> {
GlobalKey<FlipWidgetState> _flipKey = GlobalKey();
Offset _oldPosition = Offset.zero;
bool _visible = true;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
Size size = Size(256, 256);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
Visibility(
child: Container(
width: size.width,
height: size.height,
child: GestureDetector(
child: FlipWidget(
key: _flipKey,
textureSize: size * 2,
child: Container(
color: Colors.blue,
child: Center(
child: Text("hello"),
),
),
),
onHorizontalDragStart: (details) {
_oldPosition = details.globalPosition;
_flipKey.currentState?.startFlip();
},
onHorizontalDragUpdate: (details) {
Offset off = details.globalPosition - _oldPosition;
double tilt = 1/_clampMin((-off.dy + 20) / 100);
double percent = math.max(0, -off.dx / size.width * 1.4);
percent = percent - percent / 2 * (1-1/tilt);
_flipKey.currentState?.flip(percent, tilt);
},
onHorizontalDragEnd: (details) {
_flipKey.currentState?.stopFlip();
},
onHorizontalDragCancel: () {
_flipKey.currentState?.stopFlip();
},
),
),
visible: _visible,
),
TextButton(
onPressed: () {
setState(() {
_visible = !_visible;
});
},
child: Text("Toggle")
)
],
),
),
);
}
}

The library developer responded to this Github issue and provided an excellent example code for this feature.
https://github.com/gsioteam/flip_widget/issues/2
Thanks for the great work #gsioteam

Related

AddpostFramecall back Function not Call in persistent bottom navigation bar

I want to animate my list back and forth with scroll animation this FoodFragment call at the persistent bottom navigation
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:ikwte/AppConstant/StaticData.dart';
import 'package:ikwte/AppConstant/ThemeColors.dart';
import 'package:ikwte/AppUtils/AppUtils.dart';
import 'package:ikwte/ProviderControllers/FoodController.dart';
import 'package:ikwte/Widgets/FragmentWidgets/FoodHelper.dart';
import 'package:ikwte/main.dart';
import 'package:provider/provider.dart';
class FoodFragment extends StatefulWidget {
const FoodFragment({Key? key}) : super(key: key);
#override
State<FoodFragment> createState() => _FoodFragmentState();
}
class _FoodFragmentState extends State<FoodFragment> {
ScrollController _scrollController = ScrollController();
var themeColor = ThemeColors();
var utils = AppUtils();
var static = Statics();
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
double minScrollExtent1 = _scrollController.position.minScrollExtent;
double maxScrollExtent1 = _scrollController.position.maxScrollExtent;
animateToMaxMin(maxScrollExtent1, minScrollExtent1, maxScrollExtent1, 1,
_scrollController);
print("Function Triggered");
});
print("InitState Triggered");
}
animateToMaxMin(double max, double min, double direction, int seconds,
ScrollController scrollController) {
scrollController
.animateTo(direction,
duration: Duration(seconds: seconds), curve: Curves.linear)
.then((value) {
direction = direction == max ? min : max;
animateToMaxMin(max, min, direction, seconds, scrollController);
});
}
#override
Widget build(BuildContext context) {
FoodHelper helper = FoodHelper(context, _scrollController);
return RefreshIndicator(
backgroundColor: themeColor.yellowColor,
color: themeColor.blueColor,
onRefresh: () async {
await apisCall();
},
child: Scaffold(
backgroundColor: themeColor.blueColor,
body: Column(
children: [
utils.statusBar(context, color: themeColor.blueColor),
Expanded(
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: themeColor.blueColor,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
helper.appBar(),
helper.searchFieldText(),
helper.topListHeading(),
helper.topList(),
helper.whatAreYouLookingForHeading(),
helper.whatAreYouLookingForList(),
helper.collaborationsHeading(),
helper.collaborationList(),
helper.newInTownHeading(),
helper.newInTownList(),
helper.allRestaurantsHeading(),
helper.allRestaurantsList()
],
),
)),
),
utils.bottomBar(context, color: themeColor.blueColor),
],
),
),
);
}
apisCall() {
navigatorkey.currentContext!
.read<FoodController>()
.getAllRestaurantsListApi();
navigatorkey.currentContext!.read<FoodController>().getCategoryListApi();
navigatorkey.currentContext!
.read<FoodController>()
.getTopRestaurantsListApi();
navigatorkey.currentContext!
.read<FoodController>()
.getNewInTownRestaurantApi();
navigatorkey.currentContext!.read<FoodController>().getSecretMenuApi();
}
}
But the issue i am facing right now is AddpostFrameCallback Function is not calling on the persistent bottom navigation so my animation is not getting trigged i tried multiple Solutions but it didn't work for me .How can i reslove this issue

how to achieve a functionality like linear loading bar which will load up as user move between various screens

I am using android studio and flutter. I want to build the screen as shown below in the image:screen Image
let's say I have 4 screens. on the first screen, the bar will load up to 25%. the user will move to next screen by clicking on continue, the linearbar will load up to 50% and so on. the user will get back to previous screens by clicking on the back button in the appbar.
I tried stepper but it doesn't serve my purpose.
You can use the widget LinearProgressIndicator(value: 0.25,) for the first screen and with value: 0.5 for the second screen etc.
If you want to change the bar value within a screen, just use StatefullWidget's setState(), or any state management approaches will do.
import 'package:flutter/material.dart';
class ProgressPage extends StatefulWidget {
const ProgressPage({super.key});
#override
State<ProgressPage> createState() => _ProgressPageState();
}
class _ProgressPageState extends State<ProgressPage> {
final _pageController = PageController();
final _pageCount = 3;
int? _currentPage;
double? _screenWidth;
double? _unit;
double? _progress;
#override
void initState() {
super.initState();
_pageController.addListener(() {
_currentPage = _pageController.page?.round();
setState(() {
_progress = (_currentPage! + 1) * _unit!;
});
});
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_screenWidth = MediaQuery.of(context).size.width;
_unit = _screenWidth! / _pageCount;
_progress ??= _unit;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('HOZEROGOLD')),
body: Column(
children: [
Align(
alignment: Alignment.topLeft,
child: Container(
color: Colors.yellow,
height: 10,
width: _progress,
),
),
Expanded(
child: PageView(
controller: _pageController,
children: _createPage(),
),
),
],
),
);
}
List<Widget> _createPage() {
return List<Widget>.generate(
_pageCount,
(index) => Container(
color: Colors.white,
child: Center(
child: ElevatedButton(
onPressed: () => _moveNextPage(),
child: Text('NEXT $index'),
),
),
),
);
}
void _moveNextPage() {
if (_pageController.page!.round() == _pageCount-1) {
_pageController.jumpToPage(0);
} else {
_pageController.nextPage(
curve: Curves.bounceIn,
duration: const Duration(milliseconds: 100));
}
}
}
HAPPY CODING! I hope it will be of help.

Flutter ListView scroll to index insufficient scrolling

i have a expandable listview and i have a scroll problem. It is not a problem if it is selected individually or by sliding slowly, but when you scroll and select fast it will be insufficient slide problem.
I added a 'dartpad' so that the code can be tested. What can be the problem?
Code: https://dartpad.dev/c4015095fc23456619eb20a5edcb0e8b
Video: https://vimeo.com/532603204
This is an interesting problem, I had some time to deal with this, and it appears the scroll controller is updated well as I see in the log. But, for some reason, it didn't work, so I put scroll view around the list and gave the ListView.builder no scroll. Only this to take account for is the last parts of the list, because you don't want to scroll more down if you are in the last parts because you cant go down more.
This probably isn't the best solution but it is what I managed to put together, hope this helps.
import 'dart:developer';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Scroll Bug Demo',
home: MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
final _demoList = List.generate(100, (index) => 'List Item $index');
int _expandedIndex = -1;
final _scrollController = ScrollController();
bool _isExpanded = false;
bool _isInLastPart = false;
#override
Widget build(BuildContext context) {
_isInLastPart = (_expandedIndex > _demoList.length - 15);
final _size = MediaQuery.of(context).size;
return SafeArea(
child: Scaffold(
body: SingleChildScrollView(
controller: _scrollController,
child: SizedBox(
height: (48 * _demoList.length).toDouble() +
((_isExpanded && _isInLastPart) ? 80 : 0).toDouble(),
width: _size.width,
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => clickItem(index),
child: Container(
color: Colors.grey,
height: (_expandedIndex == index && _isExpanded) ? 120 : 40,
margin: EdgeInsets.only(bottom: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_demoList[index]),
if (_expandedIndex == index && _isExpanded)
Column(
children: <Widget>[
Text('text'),
Text('text'),
Text('text'),
Text('text'),
Text('text'),
],
),
],
),
),
);
},
itemCount: _demoList.length,
),
),
),
),
);
}
clickItem(int index) {
setState(() {
if (_expandedIndex == index)
_isExpanded = !_isExpanded;
else
_isExpanded = true;
_expandedIndex = index;
});
if (_expandedIndex < _demoList.length - 15) {
// log("expanded index_: $_expandedIndex, index: $index, jump to: ${index * 48.toDouble()}/${99 * 48.toDouble()}, scroll> ${_scrollController.position.pixels},isExpanded: $_isExpanded");
_scrollController.jumpTo(index * 48.toDouble());
} else {
if (_isExpanded) {
// log("expanded index_: $_expandedIndex, index: $index, jump to: ${83 * 48.toDouble() + 8}/${99 * 48.toDouble()}, scroll> ${_scrollController.offset},isExpanded: $_isExpanded");
_scrollController.jumpTo(83 * 48.toDouble() + 80 + 8);
} else {
// log("expanded index_: $_expandedIndex, index: $index, jump to: ${83 * 48.toDouble() + 80 + 8}/${99 * 48.toDouble()}, scroll> ${_scrollController.offset}, isExpanded: $_isExpanded");
_scrollController.jumpTo(83 * 48.toDouble() + 8);
}
}
}
}

How to zoom a GestureDetector with onPanUpdate call?

I'm working on a drawing app. I want to enable both drawing on the canvas and zooming the drawings. So far, I tried achieving it by wrapping GestureDetector in InteractiveViewer and using AbsorbPointer to turn the zoom mode on and off. See the minimum demo code below.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(
home: new IssueExamplePage(),
debugShowCheckedModeBanner: false,
));
class IssueExamplePage extends StatefulWidget {
#override
_IssueExamplePageState createState() => _IssueExamplePageState();
}
class _IssueExamplePageState extends State<IssueExamplePage> {
bool drawingBlocked = false;
List<Offset> points = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
color: Colors.grey,
padding: EdgeInsets.all(5),
child: Container(
color: Colors.white,
child: InteractiveViewer(
child: AbsorbPointer(
absorbing: drawingBlocked,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onPanUpdate: (details) {
RenderBox renderBox = context.findRenderObject();
Offset cursorLocation = renderBox.globalToLocal(details.localPosition);
setState(() {
points = List.of(points)..add(cursorLocation);
});
},
onPanEnd: (details) {
setState(() {
points = List.of(points)..add(null);
});
},
child: CustomPaint(
painter: MyPainter(points),
size: Size.infinite
),
),
),
),
),
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
child: Icon(drawingBlocked? CupertinoIcons.hand_raised_fill : CupertinoIcons.hand_raised),
onPressed: () {
setState(() {
drawingBlocked = !drawingBlocked;
});
}
),
SizedBox(
height: 10
),
FloatingActionButton(
child: Icon(Icons.clear),
onPressed: () {
setState(() {
points = [];
});
}
),
SizedBox(
height: 20
),
],
),
);
}
}
class MyPainter extends CustomPainter {
MyPainter(this.points);
List<Offset> points;
Paint paintBrush = Paint()
..color = Colors.blue
..strokeWidth = 5
..strokeJoin = StrokeJoin.round
..strokeCap = StrokeCap.round;
#override
void paint(Canvas canvas, Size size) {
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paintBrush);
}
}
}
#override
bool shouldRepaint(MyPainter oldDelegate) {
return points != oldDelegate.points;
}
}
However, with this realization OnPanUpdate is working with a delay (see Flutter onPanStart invokes late when the widget is wrapped inside InteractiveViewer). Is there any other way to achieve the expected result (both zooming and drawing) or is it possible to fix the OnPanUpdate delay?
UPDATE: I found some sort of solution. You can use Listener instead of GestureDetector (it has substitues for the most of the parameters: onPanStart -> onPointerDown, onPanUpdate -> onPointerMove, etc.). But it seems like there's no fix for GestureDetector.

Flutter Custom Painter drawing is laggy

I am trying to code a drawing app, in which users can choose different pen color and draw colorful drawings. I have created a class PointsGroup which stores list of offsets and associated color. In GestureDetector's onPanUpdate, the PointsGroup is appended to list of PointsGroup and passed to SignaturePainter.
But the drawing is bit laggy, it is not drawn as soon as pen moves.
You can see the video https://free.hubcap.video/v/LtOqoEj9H0dY9F9xC_jSst9HT3tSOJlTi
import 'package:flutter/material.dart';
List<Color> colorList = [
Colors.indigo,
Colors.blue,
Colors.green,
Colors.yellow,
Colors.orange,
Colors.red
];
void main() => runApp(MaterialApp(
home: HomePage(),
debugShowCheckedModeBanner: false,
));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Offset> _points = <Offset>[];
List<Offset> _setPoints = <Offset>[];
List<PointsGroup> _ptsGroupList = <PointsGroup>[];
int startIndex;
int endIndex;
#override
void initState() {
ColorChoser.penColor = Colors.black;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
GestureDetector(
onPanStart: (details) {
setState(() {
_points.clear();
startIndex = _ptsGroupList.length;
ColorChoser.showColorSelector = false;
});
},
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox object = context.findRenderObject();
Offset _localPosition =
object.globalToLocal(details.globalPosition);
_points = new List.from(_points)..add(_localPosition);
_setPoints = new List.from(_points);
_ptsGroupList.add(new PointsGroup(
setPoints: _setPoints, setColor: ColorChoser.penColor));
});
},
onPanEnd: (DragEndDetails details) {
setState(() {
_points.add(null);
ColorChoser.showColorSelector = true;
endIndex = _ptsGroupList.length;
if (startIndex < endIndex) {
_ptsGroupList.replaceRange(
startIndex, endIndex - 1, [_ptsGroupList.removeLast()]);
}
});
},
child: CustomPaint(
painter: SignaturePainter(grpPointsList: _ptsGroupList),
size: Size.infinite,
),
),
ColorChoser(),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.undo),
onPressed: () {
setState(() {
if (_ptsGroupList.length > 0) {
_ptsGroupList.removeLast();
}
});
}),
);
}
}
class ColorChoser extends StatefulWidget {
const ColorChoser({
Key key,
}) : super(key: key);
static Color backgroundColor = Colors.white;
static Color penColor = Colors.blue;
static bool showColorSelector = true;
#override
_ColorChoserState createState() => _ColorChoserState();
}
class _ColorChoserState extends State<ColorChoser> {
#override
Widget build(BuildContext context) {
return Visibility(
visible: ColorChoser.showColorSelector,
child: Positioned(
bottom: 0,
left: 0,
width: MediaQuery.of(context).size.width,
child: Container(
height: 60,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: colorList.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
setState(() {
ColorChoser.penColor = colorList[index];
});
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4.0, vertical: 5.0),
child: Container(
color: colorList[index],
// height: 30,
width: 45,
),
),
);
}),
),
),
);
}
}
class SignaturePainter extends CustomPainter {
List<Offset> points;
List<PointsGroup> grpPointsList = <PointsGroup>[];
var paintObj;
SignaturePainter({
this.grpPointsList = const [],
});
#override
void paint(Canvas canvas, Size size) {
for (PointsGroup pts in grpPointsList) {
points = pts.setPoints;
paintObj = Paint()
..color = pts.setColor
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paintObj);
}
}
}
}
#override
bool shouldRepaint(SignaturePainter oldDelegate) =>
oldDelegate.points != points;
}
class PointsGroup {
List<Offset> setPoints = <Offset>[];
Color setColor;
PointsGroup({this.setPoints, this.setColor});
}
Also the drawing is not shown for the very first draw. As soon as pen
is lifted it starts showing.
P.S. If there is any alternate way is to achieve the desired multi-colored drawing, it will be okay.
You are clearing all the points whenever onPanStart is triggered (when the user places their finger on the screen). If you remove _points.clear() from onPanStart: (details) {} you will retain all the points that the user draws.
The application begins to lag and framerate is impacted after many points are drawn. You'll notice this when the user has drawn a decent amount on the canvas. To prevent lag from occurring, one strategy is to reduce the number of points being drawn. You can halve the number of points and still give the user the autonomy to draw what they desire by doing this:
final int THRESHOLD = 2;
if (totalPoints % THRESHOLD == 0){
_points = new List.from(_points)..add(_localPosition);
}
totalPoints is a counter you increment by one in onPanUpdate: (details) {}
Another technique is to wrap the subclass widget that extends CustomPainter, in this case CustomPaint, with a RepaintBoundary widget https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html. This widget will ensure that only the regions of the canvas where painting occurs is redrawn when needed. By limiting refresh rendering to one widget, you will speed up the process and deliver better results.
RepaintBoundary(
child: CustomPaint(
isComplex: true,
willChange: false,
painter: Painter(
points: _points,
),
),
),