I'n new in flutter. I have a playground app and I'm trying to dismiss the item from the list. I found this example without dismiss functionality. It works fine by swiping in a specific direction, but what I need is dismissing it by taping on a number on the card and remove swiping functionality at all. Thanks in advance.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class ColorCards {
int id;
Color color;
ColorCards({this.id, this.color});
}
class _MyHomePageState extends State<MyHomePage> {
PageController controller;
int currentPage = 0;
List<ColorCards> cards = [
ColorCards(id: 1, color: Colors.green),
ColorCards(id: 2, color: Colors.yellow),
ColorCards(id: 3, color: Colors.green),
ColorCards(id: 4, color: Colors.yellow),
ColorCards(id: 5, color: Colors.green),
];
#override
initState() {
super.initState();
controller = PageController(
initialPage: currentPage,
keepPage: false,
viewportFraction: 0.85,
);
}
#override
dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
//Scaffold
return Scaffold(
appBar: AppBar(
title: Text('Playground App'),
backgroundColor: Colors.grey,
),
body: Center(
child: Container(
child: PageView.builder(
onPageChanged: (value) {
setState(() {
currentPage = value;
});
},
itemCount: cards.length,
controller: controller,
itemBuilder: (context, index) => builder(index)),
),
),
);
}
builder(int index) {
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
double value = 1.0;
if (controller.position.haveDimensions) {
value = controller.page - index;
value = (1 - (value.abs() * .1)).clamp(0.0, 1.0);
}
return Center(
child: Dismissible(
key: Key(cards[index].id.toString()),
direction: DismissDirection.up,
onDismissed: (direction) {
cards.removeAt(index);
setState(() {
controller.animateToPage(index,
duration: Duration(milliseconds: 1), curve: Curves.bounceIn);
});
},
child: SizedBox(
height: Curves.easeOut.transform(value) * 300,
width: Curves.easeOut.transform(value) * 350,
child: child,
),
),
);
},
child: Container(
child: Center(
child: GestureDetector(
onTap: () {
print('Dismiss this item');
},
child: Text(
cards[index].id.toString(),
style: TextStyle(
fontSize: 60.0, color: Colors.white, fontWeight: FontWeight.bold),
),
)),
margin: const EdgeInsets.all(8.0),
color: cards[index].color),
);
}
}
For those who got in same situation, I've found the workaround. First, its better to use AnimatedList instead of PageView. Detailed information about AnimatedList you can find here and here. Then you have to specify Dismissible in ItemBuilder class. And that's it, hope it
Related
I have a ListView containing string items. I want to make it so that ListView's items show one by one with delay. And after each item appears, I want to wait about 2 seconds with a typing indicator and then the next item will appear. How will I do? Please give me the solution. Thanks!
You can use AnimatedListView like this code below
https://www.youtube.com/watch?v=ZtfItHwFlZ8
// main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Test',
theme: ThemeData(
primaryColor: Colors.green,
colorScheme:
ColorScheme.fromSwatch().copyWith(secondary: Colors.greenAccent)),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// Items in the list
final _items = ["Item 0"];
// The key of the list
final GlobalKey<AnimatedListState> _key = GlobalKey();
// Add a new item to the list
// This is trigger when the floating button is pressed
void _addItem() {
_items.insert(0, "Item ${_items.length + 1}");
_key.currentState!.insertItem(0, duration: const Duration(seconds: 1));
}
// Remove an item
// This is trigger when an item is tapped
void _removeItem(int index, BuildContext context) {
AnimatedList.of(context).removeItem(index, (_, animation) {
return FadeTransition(
opacity: animation,
child: SizeTransition(
sizeFactor: animation,
child: SizedBox(
height: 150,
child: Card(
margin: const EdgeInsets.symmetric(vertical: 20),
elevation: 10,
color: Colors.red[400],
child: const Center(
child: Text("I am going away",
style: const TextStyle(fontSize: 28)),
),
),
),
),
);
}, duration: const Duration(seconds: 1));
_items.removeAt(index);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Test list'),
actions: [
IconButton(
onPressed: _addItem,
icon: const Icon(Icons.plus_one_outlined),
)
],
),
body: AnimatedList(
key: _key,
initialItemCount: 1,
padding: const EdgeInsets.all(10),
itemBuilder: (context, index, animation) {
return SlideTransition(
key: UniqueKey(),
position: Tween<Offset>(
begin: const Offset(-1, -0.5),
end: const Offset(0, 0),
).animate(animation),
child: RotationTransition(
turns: animation,
child: SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: SizedBox(
height: 150,
child: InkWell(
onTap: () => _removeItem(index, context),
child: Card(
margin: const EdgeInsets.symmetric(vertical: 20),
elevation: 10,
color: Colors.primaries[
(index * 100) % Colors.primaries.length][300],
child: Center(
child: Text(_items[index],
style: const TextStyle(fontSize: 28)),
),
),
),
),
),
),
);
},
),
);
}
}
Try wrapping your widget around this widget https://pub.dev/packages/delayed_display. Please confirm if this works.
In my app I am trying to implement Badoo-like sort/filter showBottomModalSheet feature. I managed to create 2 separate pages, which I can navigate back and forth. However, the problem I'm facing is the second page in showBottomModalSheet. Back button works fine until I try to touch outside of the modal, which takes back to the first page. Instead it should close modal.
User navigates to sort users modal, which shows the 1st page in showBottomModalSheet When user taps "Show gender" it navigates to the second page (the one with different genders). When back button is pressed it navigates to 1st screen until it closes modal completely. Touching outside of the modal also closes the modal
The best stackoverflow answer that I tried:
https://stackoverflow.com/questions/63602999/how-can-i-do-navigator-push-in-a-modal-bottom-sheet-only-not-the-parent-page/63603685#63603685
I also tried using modal_bottom_sheet package, but had no luck.
https://pub.dev/packages/modal_bottom_sheet/example
Most of my code behind showBottomModalSheet:
class Page1 extends StatefulWidget {
const Page1({
Key? key
}) : super(key: key);
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
int _currentView = 0;
late List<Widget> pages;
#override
void initState() {
pages = [
page1(),
page2(),
];
super.initState();
}
#override
Widget build(BuildContext context) {
print("LOG build _currentView ${_currentView}");
return pages[_currentView];
}
Widget page1() {
return WillPopScope(
onWillPop: () async {
return true;
},
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(60), topLeft: Radius.circular(60))),
height: 400,
width: double.maxFinite,
child: Center(
child: Column(
children: [
Text("First page"),
ElevatedButton(
onPressed: () {
setState(() {
_currentView = 1;
print("LOG page1 _currentView ${_currentView}");
});
},
child: Text("tap to navigate to 2nd page"),
),
],
)),
));
}
Widget page2() {
return WillPopScope(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(60), topLeft: Radius.circular(60))),
height: 400,
width: double.maxFinite,
child: Center(
child: InkWell(
onTap: () {
setState(() {
_currentView = 0;
print("LOG page2 _currentView ${_currentView}");
});
},
child: Text("tap to navigate to 1st screen"),
),
),
),
onWillPop: () async {
print("LOG currentView jot $_currentView");
if (_currentView == 0) {
return true;
}
setState(() {
_currentView = 0;
});
return false;
});
}
}
Solution 1
Use the standard DraggableScrollableSheet or a 3rd-party widget to do it, there are a bunch of them. Here are some from https://pub.dev/
awesome_select
backdrop_modal_route
bottom_sheet_expandable_bar
bottom_sheet
cupertino_modal_sheet
modal_bottom_sheet
just_bottom_sheet
sheet
Solution 2
Anyway, if you'd like to do it manually I'd do it with Navigator.push/Navigator.pop instead with PageRouteBuilder with barrierDismissible=true.
It would like the following. Check out the live demo on DartPad.
Here's the code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void _show() async {
await Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
barrierDismissible: true,
pageBuilder: (_, __, ___) => const Page1(),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _show,
tooltip: 'Settings',
child: const Icon(Icons.settings),
),
);
}
}
class Page1 extends StatefulWidget {
const Page1({Key? key}) : super(key: key);
#override
State<Page1> createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Stack(
children: [
Align(
alignment: Alignment.bottomCenter,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topRight: Radius.circular(60), topLeft: Radius.circular(60)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
height: 400,
width: double.maxFinite,
child: Center(
child: Material(
type: MaterialType.transparency,
child: Column(
children: [
const Text("First page"),
ElevatedButton(
onPressed: () async {
final backButton =
await Navigator.of(context).push<bool?>(
PageRouteBuilder(
opaque: false,
barrierDismissible: true,
pageBuilder: (_, __, ___) => const Page2(),
),
);
if (backButton == null || backButton == false) {
if (mounted) Navigator.of(context).pop();
}
},
child: const Text("tap to navigate to 2nd page"),
),
],
),
),
),
),
),
],
);
}
}
class Page2 extends StatelessWidget {
const Page2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(60), topLeft: Radius.circular(60)),
),
height: 400,
width: double.maxFinite,
child: Center(
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () => Navigator.of(context).pop(true),
child: const Text("tap to navigate to 1st screen"),
),
),
),
),
);
}
}
on swipe how can I set text in the text field after that edit text and insert it again in that position. swipe in Dismissible and after swipe, I want my text on the input text field and change the text and add the text in the same position can you please help me strong text
enter image description here
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../models/model.dart';
import 'list_provider.dart';
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late GlobalKey<FormState> _formKey;
late TextEditingController _controller;
var taskItems;
int counter = 0;
late DynamicList listClass;
bool isUpdate =false;
#override
void initState() {[enter image description here][1]
// TODO: implement initState
super.initState();
_formKey = GlobalKey();
_controller = TextEditingController();
taskItems = Provider.of<ListProvider>(context, listen: false);
listClass = DynamicList(taskItems.list);
}
#override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: TextFormField(
controller: _controller,
onSaved: (val) {
taskItems.addItem(val);
},
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState?.save();
_controller.clear();
}
},
child: Text('Add'),
),
),
const Padding(padding: EdgeInsets.all(8.0),
),
Consumer<ListProvider>(builder: (context, provider, listTile) {
return Expanded(
child: ListView.builder(
itemCount: listClass.list.length,
itemBuilder: buildList,
),
);
}),
],
),
**
-
> Heading
**
));
}
Widget buildList(BuildContext context, int index) {
counter++;
return Dismissible(
key: Key(counter.toString()),
direction: DismissDirection.startToEnd,
onDismissed: (direction) {
taskItems.deleteItem(index);
},
child: Container(
margin: EdgeInsets.all(4),
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue,
width: 2,
),
borderRadius: BorderRadius.circular(10)),
child: ListTile(
title: Text(listClass.list[index].toString()),
trailing: IconButton(iconSize: 30, icon: Icon(Icons.delete),
onPressed: () {
setState(() {
taskItems.deleteItem(index);
},
);
},
),
),
),
);
}
}
I'm trying to remove the debug banner from my application and I added debugShowCheckedModeBanner: false in my main.dart and every activity but still showing the debug banner.
Main.dart
void main() async {
runApp(MaterialApp(home:SplashScreen(), debugShowCheckedModeBanner: false,));
}
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => new _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
startTime() async {
var _duration = new Duration(seconds: 1);
return new Timer(_duration, navigationPage);
}
void navigationPage() {
Navigator.pushReplacement(
context,
new CupertinoPageRoute(
builder: (BuildContext context) => MyHomesApp()));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Stack(
children: <Widget>[
Container(
decoration: new BoxDecoration(color: colorGreen),
),
Container(
padding: EdgeInsets.all(30.0),
child: Center(
child: new Image.asset('assets/green_h_logo.png',color: Colors.white,height: 150,width: 150,)
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 50,
child:new Image.asset('assets/h.gif')),
),
],
),
);
}
}
The above code is my main.dart, it loads a splash screen and after a second it navigates to myhomesapp which has bottomnavigationbar as below:
home.dart
class MyHomesApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'App Name',
theme: ThemeData(
primarySwatch: Colors.green,
fontFamily: "Montserrat", //my custom font
),
builder: (context, child) {
return ScrollConfiguration(
behavior: MyBehavior(),
child: child,
);
},
home: Homes(),
);
}
}
class Homes extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Homes> {
int _currentIndex;
List<Widget> _children;
#override
void initState() {
_currentIndex = 0;
_children = [
MyDealApp(),
MyRedemptionApp(),
MyProfileApp()
];
_loadCounter();
super.initState();
}
_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
myInt = prefs.getInt('id') ?? 0;
_email = (prefs.getString('email') ?? '');
_fullname = (prefs.getString('fullname') ?? '');
currentTabs = prefs.getInt('currentTab') ?? 0;
debugPrint("currentTabMain:$currentTabMain");
debugPrint("emailr:$_email");
});
}
#override
Widget build(BuildContext context) {
const assetHome = 'assets/home_off.svg';
const assetRedemptions = 'assets/redeemed_off.svg';
const assetCommunity = 'assets/community_off.svg';
const assetProfile = 'assets/profile_off.svg';
const assetHome1 = 'assets/home_on.svg';
const assetRedemptions1 = 'assets/redeemed_on.svg';
const assetCommunity1 = 'assets/community_on.svg';
const assetProfile1 = 'assets/profile_on.svg';
return Container(
height: 30,
child:CupertinoTabScaffold(
tabBar: CupertinoTabBar(
backgroundColor: colorGreen,
currentIndex: _currentIndex,
onTap: onTabTapped,
items: [
BottomNavigationBarItem(
icon: _currentIndex == 0 ?SvgPicture.asset(assetHome1,
color: Colors.white,
width: 20,
height: 20,
semanticsLabel: 'Home'):SvgPicture.asset(assetHome,
color: Colors.white,
width: 20,
height: 20,
semanticsLabel: 'Home'),
),
BottomNavigationBarItem(
icon: _currentIndex == 1 ?SvgPicture.asset(assetRedemptions1,
color: Colors.white,
width: 20,
height: 20,
semanticsLabel: 'Redemptions'):SvgPicture.asset(assetRedemptions,
color: Colors.white,
width: 20,
height: 20,
semanticsLabel: 'Redemptions'),
),
BottomNavigationBarItem( icon: _currentIndex == 2 ? SvgPicture.asset(assetProfile1,
color: Colors.white,
width: 20,
height: 20,
semanticsLabel: 'Profile'):SvgPicture.asset(assetProfile,
color: Colors.white,
width: 20,
height: 20,
semanticsLabel: 'Profile'),
),
],
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
builder: (BuildContext context) {
return SafeArea(
top: false,
bottom: false,
child: CupertinoApp(
home: CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
child: _children[_currentIndex],
),
),
);
},
);
}
));
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
debugPrint("tabbottom:$_currentIndex");
});
}
}
I used the same for MyRedemptionApp() and MyProfileApp() respectively. The major issue is debugShowCheckedModeBanner: false is not removing the debug banner because the debug banner shows on every page. How to remove the debug banner?
This normally happens when you have multiple MaterialApp widgets used. If you don't think you can reduce to one just use debugShowCheckedModeBanner:false whenever you use MaterialApp widgets throughout your application.
I have a ListView that contains dynamic items, generated via StreamBuilder. Currently, these items show all at once like normal ListView. However, I want to make it so the items of the ListView show one by one with delay.
Is it even possible?
StreamBuilder(
stream: greetingBloc.list,
builder: (BuildContext context,AsyncSnapshot<List<Greeting>> snapshot){
if(snapshot.hasData){
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (ctx,i){
return Padding(
padding: EdgeInsets.all(8),
child: Container(
padding: EdgeInsets.all(16),
color: Colors.green,
child: Text(snapshot.data[i].description,
style: TextStyle(
color: Colors.white
),),
),
);
},
);
}else{
return GreetingShimmer();
}
},
),
Set default delay.
Think how to calculate delay for a item, if previous item hasn't appeared yet.
Use opacity widget.
I've made a simple example, how you can do it.
import 'dart:math' as math;
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
backgroundColor: Colors.blue,
body: Test(),
),
);
}
}
class Test extends StatelessWidget {
DateTime lastRender;
get _duration {
var now = DateTime.now();
var defaultDelay = Duration(milliseconds: 100);
Duration delay;
if (lastRender == null) {
lastRender = now;
delay = defaultDelay;
} else {
var difference = now.difference(lastRender);
if (difference > defaultDelay) {
lastRender = now;
delay = defaultDelay;
} else {
var durationOffcet = difference - defaultDelay;
delay = defaultDelay + (-durationOffcet);
lastRender = now.add(-durationOffcet);
}
return delay;
}
return defaultDelay;
}
#override
Widget build(BuildContext context) {
return ListView(
children: List.generate(
100,
(index) => Item('item $index', _duration),
),
);
}
}
class Item extends StatefulWidget {
const Item(this.text, this.duration);
final Duration duration;
final String text;
#override
_ItemState createState() => _ItemState();
}
class _ItemState extends State<Item> with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController animationController;
bool visible = false;
#override
void initState() {
super.initState();
animationController = AnimationController(
duration: widget.duration,
vsync: this,
);
animation = Tween(begin: 0.0, end: 1.0).animate(animationController);
animation.addStatusListener((status) {
setState(() {});
});
animationController.forward();
super.initState();
}
#override
void dispose(){
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Opacity(
opacity: animation.value,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
width: 4,
color: Colors.blueAccent,
),
),
height: 80,
child: Text(widget.text, style: TextStyle(fontSize: 20.0)),
),
);
}
}
I know it's too late but let me give you a simple solution. for this one use the animate_do package for the animation.
StreamBuilder(
stream: greetingBloc.list,
builder: (BuildContext context,AsyncSnapshot<List<Greeting>> snapshot){
if(snapshot.hasData){
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (ctx,i){
return Padding(
padding: EdgeInsets.all(8),
child: FadeIn(
delay: Duration(milliseconds: (1500 + (150 * (i + 1)))),
Container(
padding: EdgeInsets.all(16),
color: Colors.green,
child: Text(snapshot.data[i].description,
style: TextStyle(
color: Colors.white
),),
)),
);
},
);
}else{
return GreetingShimmer();
}
},
),
customize it to your likings.
Inside the listview widget
instead of doing:
return Padding(
padding: EdgeInsets.all(8),
child: Container(
padding: EdgeInsets.all(16),
color: Colors.green,
child: Text(snapshot.data[i].description,
style: TextStyle(
color: Colors.white
),),
),
);
You can do this:
return Future.delayed(const Duration(milliseconds: 500), () => Padding(
padding: EdgeInsets.all(8),
child: Container(
padding: EdgeInsets.all(16),
color: Colors.green,
child: Text(snapshot.data[i].description,
style: TextStyle(
color: Colors.white
),),
),
);
);