How to add a asset images using Image_stack package - flutter

I am trying to add a asset images using image_stack package. I can't add images to a list in image_stack. I can only add a network images. I can't add a asset image.

Yes, you can not add any asset image because image_stack doesn't support that.
But you create your own widget and use it. Something like below.
custom_image_stack.dart
import 'package:flutter/material.dart';
class CustomImageStack extends StatelessWidget {
final List<String> imageList;
final double imageRadius;
final int imageCount;
final int totalCount;
final double imageBorderWidth;
final Color imageBorderColor;
final TextStyle extraCountTextStyle;
final Color backgroundColor;
CustomImageStack({
Key key,
#required this.imageList,
this.imageRadius = 25,
this.imageCount = 3,
this.totalCount,
this.imageBorderWidth = 2,
this.imageBorderColor = Colors.grey,
this.extraCountTextStyle = const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
),
this.backgroundColor = Colors.white,
}) : assert(imageList != null),
assert(extraCountTextStyle != null),
assert(imageBorderColor != null),
assert(backgroundColor != null),
assert(totalCount != null),
super(key: key);
#override
Widget build(BuildContext context) {
var images = List<Widget>();
int _size = imageCount;
if (imageList.isNotEmpty) images.add(circularImage(imageList[0]));
if (imageList.length > 1) {
if (imageList.length < _size) {
_size = imageList.length;
}
images.addAll(imageList
.sublist(1, _size)
.asMap()
.map((index, image) => MapEntry(
index,
Positioned(
left: 0.8 * imageRadius * (index + 1.0),
child: circularImage(image),
),
))
.values
.toList());
}
return Container(
child: Row(
children: <Widget>[
images.isNotEmpty
? Stack(
overflow: Overflow.visible,
textDirection: TextDirection.rtl,
children: images.reversed.toList(),
)
: SizedBox(),
Container(
margin: EdgeInsets.only(left: imageRadius / 2 * imageCount + 5),
child: totalCount - images.length > 0
? Container(
constraints: BoxConstraints(minWidth: imageRadius),
padding: EdgeInsets.all(3),
height: imageRadius,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(imageRadius),
border: Border.all(color: imageBorderColor, width: imageBorderWidth),
color: backgroundColor),
child: Center(
child: Text(
'+${totalCount - images.length}',
textAlign: TextAlign.center,
style: extraCountTextStyle,
),
),
)
: SizedBox(),
),
],
),
);
}
Widget circularImage(String imageUrl) {
return Container(
height: imageRadius,
width: imageRadius,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: imageBorderWidth,
),
),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
image: DecorationImage(
image: isLink(imageUrl) ? NetworkImage(imageUrl) : AssetImage(imageUrl),
fit: BoxFit.cover,
),
),
),
);
}
bool isLink(String str) {
var regex = RegExp('^(http|https):.*\.(co|org|in)');
return regex.hasMatch(str);
}
}
main.dart
import 'package:flutter/material.dart';
import 'custom_image_stack.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> images = [
"assets/ajay.png",
"https://i.stack.imgur.com/IJ8Ep.jpg?s=48&g=1",
"assets/ajay.png",
"https://i.stack.imgur.com/IJ8Ep.jpg?s=48&g=1",
"assets/ajay.png",
"https://i.stack.imgur.com/IJ8Ep.jpg?s=48&g=1",
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: CustomImageStack(
imageList: images,
imageCount: 3,
imageBorderWidth: 3,
totalCount: images.length,
)),
);
}
}
Hope it helps :)

Related

Flutter: Bad state: Stream has already been listened to

I am building a desktop app whereby I use Firebase for login. To implement this I am using the firedart package to be able to do it. The login system works perfectly. I would like to switch between the login page and a homepage which I have randomly named FirstPage() depending on the sign in state. So when the user is logged out he is taken to login page and if logged in he is taken to the FirstPage(). Whenever I reload the FirstPage() I get the error "Bad state: Stream has already been listened to."
I have gone through multiple solutions on Stackoverflow as well as GitHub and haven't found anything that works for me. Maybe I am not implementing the solutions properly or there is something I am missing.
The following is my code:
main.dart
import 'package:ame/screens/firstPage.dart';
import 'package:ame/screens/loginPage.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:firedart/auth/firebase_auth.dart';
import 'package:firedart/auth/token_store.dart';
import 'package:firedart/firestore/firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:google_fonts/google_fonts.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Window.initialize();
await Window.setEffect(
effect: WindowEffect.aero,
color: const Color.fromARGB(50, 0, 0, 0),
);
FirebaseAuth.initialize(
"AIzaSyBk76lyEHpyDgMot7csMmDiIKnPS_5QiYE", VolatileStore());
var auth = FirebaseAuth.instance;
// auth.signInState.listen((state) => print("Signed ${state ? "in" : "out"}"));
// var user = await auth.getUser();
// print(user);
runApp(const MyApp());
doWhenWindowReady(() {
var initialSize = const Size(600, 450);
// appWindow.size = initialSize;
appWindow.minSize = initialSize;
});
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
key: UniqueKey(),
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
fontFamily: GoogleFonts.poppins().fontFamily,
colorScheme: ColorScheme.fromSwatch().copyWith(
primary: const Color.fromRGBO(7, 96, 49, 1),
secondary: Colors.white,
),
),
routes: {
'/firstPage': (ctx) => const FirstPage(),
'/loginPage': (ctx) => const LoginPage(),
},
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseAuth.instance.signInState,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data == true) {
return const FirstPage();
} else {
return const LoginPage();
}
});
}
}
LoginPage
import 'dart:ui';
import 'package:ame/widgets/rightWindowBar.dart';
import 'package:firedart/auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
#override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
List bottomNavTitles = ["Home", "Tour", "Courses", "Articles", "Blog"];
final emailController = TextEditingController();
final passwordController = TextEditingController();
final auth = FirebaseAuth.instance;
Future<void> login() async {
await auth.signIn(
emailController.text.trim(), passwordController.text.trim());
}
#override
Widget build(BuildContext context) {
// double deviceHeight = MediaQuery.of(context).size.height;
double deviceWidth = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: Colors.transparent,
body: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/login.jpeg'),
fit: BoxFit.cover,
),
),
child: Stack(
children: [
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Container(
decoration: BoxDecoration(color: Colors.black.withOpacity(0.5)),
),
),
Column(
// ignore: prefer_const_literals_to_create_immutables
children: [
const RightWindowBar(),
const Spacer(),
Container(
margin: EdgeInsets.symmetric(horizontal: deviceWidth * 0.35),
child: Column(
children: [
Image.asset('assets/images/ame.png', scale: 9),
TextField(
controller: emailController,
style: const TextStyle(
color: Colors.black,
),
decoration: const InputDecoration(
prefixIcon: Icon(FontAwesomeIcons.envelopesBulk,
size: 15, color: Colors.black),
hintText: "Email",
hintStyle: TextStyle(
color: Colors.black,
),
filled: true,
contentPadding: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10.0),
fillColor: Color.fromARGB(31, 255, 255, 255),
),
),
const SizedBox(height: 8.0),
TextField(
controller: passwordController,
obscureText: true,
style: const TextStyle(
color: Colors.black,
),
decoration: const InputDecoration(
prefixIcon: Icon(FontAwesomeIcons.lock,
size: 15, color: Colors.black),
hintText: "Password",
hintStyle: TextStyle(
color: Colors.black,
),
filled: true,
contentPadding: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10.0),
fillColor: Color.fromARGB(31, 255, 255, 255),
),
),
const SizedBox(height: 16.0),
Row(
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: BeveledRectangleBorder(),
padding: const EdgeInsets.all(16.0),
),
onPressed: login,
child: const Text("Login"),
),
),
],
),
],
),
),
const Spacer(),
],
),
],
),
),
);
}
}
FirstPage
import 'package:ame/widgets/leftWindowBar.dart';
import 'package:ame/widgets/menu_list.dart';
import 'package:ame/widgets/rightWindowBar.dart';
import 'package:firedart/auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class FirstPage extends StatelessWidget {
const FirstPage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
const Expanded(child: MenuLlist()),
Expanded(
flex: 2,
child: Container(
color: Theme.of(context).colorScheme.secondary,
child: Column(
children: const [RightWindowBar()],
),
),
),
],
),
);
}
}
The problem is, as the error says, that FirebaseAuth.instance.signInState stream has been listened to more that one time.
FirebaseAuth.instance.signInState, under the hood, is a simple StreamController(source code of package, see also broadcast constructor of StreamController, and notice the difference). And it can be listened to only once. Intended or unintended, the developer of this package made it this way(so the signInState stream can be listened only once).
Okay, but what can you do to solve the problem?
You can change your code that way, to listen the stream once. For example, call the listen() function in main:
void main() {
// ...
final auth = FirebaseAuth.instance;
final notifier = ValueNotifier<bool>(false);
// Notice that usually it is a good practice to dispose
// subscriptions, streams, notifiers etc. when they
// are no longer needed, but in this case it does not play a big role
auth.signInState.listen((state) => notifier.value = state);
// And then you can pass down the tree this notifier whatever way you like
// E.g. by using provider, or simply pass through constructor:
runApp(const MyApp(signInNotifier: notifier));
}
then, in MyHomePage() you can use this notifier like so:
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.signInNotifier});
#protected
final ValueNotifier<bool> signInNotifier;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: signInNotifier,
builder: (context, signedIn, child) {
if (signedIn) return const FirstPage();
return const LoginPage();
},
);
}
}
By some way make the FirebaseAuth.instance.signInState stream to be a broadcast. There are many options. For example, you can open an issue, and ask the author replace StreamController() constructor with StreamController.broadcast() constructor(or make an option for developers to choose whether to use the default constructor, or broadcast), or you can make a PR, etc.

How to call a variable or function from a different file to main.dart in flutter?

I've been trying to implement a similar function like the NavigationBar widget in flutter.
However, I don't want to use Icons instead I wanted to make a custom navbar with desired pics and everything was going well until I couldn't switch the middle section of my app (change different pages) when I tap/press the the textbutton.
You can check the UI here...crappy I know...am mimicking the till from my workplace...so the red section is the part I wanted to update when pressed
The side_buttons.dart file
import 'package:flutter/material.dart';
// ignore: unused_import
import 'package:timtill/main.dart';
class SideButtons extends StatefulWidget {
final String text;
final String imgUrl;
const SideButtons({required this.text, required this.imgUrl});
#override
State<SideButtons> createState() => SideButtonsState();
}
class SideButtonsState extends State<SideButtons> {
//
final List sideBtnLabels = [
'HOT DRINKS',
'COLD DRINKS',
'DONUTS',
'TIMBITS',
'MUFFINS',
'BAGELS',
'SOUP',
'LUNCH',
'BREAK FAST',
'BAKED',
'TAKE-HOME',
'Timmies'
];
#override
Widget build(BuildContext context) {
return Transform.rotate(
angle: -11,
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF9A9DAD), Color(0xFF4E4C56)])),
height: 80,
width: 80,
child: TextButton(
onPressed: () {
int currentPageIndex = 0;
int index = sideBtnLabels.indexOf(widget.text);
setState(() {
currentPageIndex = index;
});
int navMiddleIndex(int index) {
return index;
}
print(sideBtnLabels.indexOf(widget.text));
// print('index is changed to: ${navMiddleIndex(index).toString()}');
},
//////here Instead of text you can replace Node and import the dart:html
//import 'dart:html';
// text works because in the side_btn_page.dart we have specified the list of menu to it
child: Stack(
alignment: const AlignmentDirectional(0.0, 0.9),
children: [
Image.asset(
'imgs/' + widget.imgUrl,
//imgurl
),
Text(
widget.text, //text
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3
..color = const Color.fromARGB(255, 63, 63, 63),
),
),
Text(
widget.text, //text
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color(0xFFEBEBEB),
),
),
],
)),
),
);
}
}
'''
The Main.dart file
Note I wanted to update the currentPageIndex value from zero to the index number When I press the buttons please help me I'm beginner
import 'package:flutter/material.dart';
import 'package:timtill/pages/side_btn_page.dart';
import 'package:timtill/pages/middle_btn_page.dart';
import 'package:timtill/pages/middle_btn_page2.dart';
// ignore: unused_import
import 'package:timtill/util/side_buttons.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'TimsTill',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentPageIndex = 0;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
children: [
SizedBox(height: 80, child: SideButtonPage()),
Expanded(
flex: 12,
child: Container(
color: Colors.red,
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.all(8),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: <Widget>[
MiddleButtonPage(),
MiddleButtonPage2(),
Container(
color: Colors.green,
alignment: Alignment.center,
child: const Text('Page 2'),
),
Container(
color: Colors.blue,
alignment: Alignment.center,
child: const Text('Page 3'),
),
][currentPageIndex],
),
),
),
),
)),
Expanded(
flex: 6,
child: Container(
color: Colors.purple,
))
],
),
);
}
}
First of all, you should implement a callback in your SideButtons widget, second, you should implement the defaultPageIndex. This way, SideButtons will return the selected index to its parent widget while maintening its state incase the widget try is rebuilt.
class SideButtons extends StatefulWidget {
final String text;
final String imgUrl;
final int defaultPageIndex;
final ValueChanged<int>? onChanged;
const SideButtons({required this.text, required this.imgUrl, this.defaultPageIndex = 0, this.onChanged});
#override
State<SideButtons> createState() => SideButtonsState();
}
class SideButtonsState extends State<SideButtons> {
//
final List sideBtnLabels = [
'HOT DRINKS',
'COLD DRINKS',
'DONUTS',
'TIMBITS',
'MUFFINS',
'BAGELS',
'SOUP',
'LUNCH',
'BREAK FAST',
'BAKED',
'TAKE-HOME',
'Timmies'
];
late int currentPageIndex;
#override
initState(){
currentPageIndex = defaultPageIndex;
super.initState();
}
#override
Widget build(BuildContext context) {
return Transform.rotate(
angle: -11,
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF9A9DAD), Color(0xFF4E4C56)])),
height: 80,
width: 80,
child: TextButton(
onPressed: () {
int index = sideBtnLabels.indexOf(widget.text);
setState(() {
currentPageIndex = index;
if( widget.onChanged != null) widget.onChanged(index);
});
int navMiddleIndex(int index) {
return index;
}
print(sideBtnLabels.indexOf(widget.text));
// print('index is changed to: ${navMiddleIndex(index).toString()}');
},
//////here Instead of text you can replace Node and import the dart:html
//import 'dart:html';
// text works because in the side_btn_page.dart we have specified the list of menu to it
child: Stack(
alignment: const AlignmentDirectional(0.0, 0.9),
children: [
Image.asset(
'imgs/' + widget.imgUrl,
//imgurl
),
Text(
widget.text, //text
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3
..color = const Color.fromARGB(255, 63, 63, 63),
),
),
Text(
widget.text, //text
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color(0xFFEBEBEB),
),
),
],
)),
),
);
}
}

How to make scrollable list using array

I am trying to make my code simple by making it into an array instead I have to write it one by one, but I do not have an idea on how to convert it into an array list. Here for what I have been done. My output is I want to generate the list in a container with scrollable to the right using axis horizontal scroll direction.
class YearSort extends StatelessWidget {
final String title;
const YearSort({
Key? key,
required this.title,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
ByYear(year: 'This year'),
ByYear(year: '2021'),
ByYear(year: '2020'),
ByYear(year: '2019'),
ByYear(year: '2018'),
],
),
);
}
}
class ByYear extends StatefulWidget {
final String year;
const ByYear({
Key? key,
required this.year,
}) : super(key: key);
#override
State<ByYear> createState() => _ByYearState();
}
class _ByYearState extends State<ByYear> {
bool iselected = true;
#override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 30,
decoration: BoxDecoration(
color: iselected
? Color.fromARGB(255, 215, 237, 255)
//Colors.white
: Color.fromARGB(255, 215, 237, 255),
borderRadius: BorderRadius.circular(20),
),
child: Center(child: Text(widget.year, style: const TextStyle(fontFamily: 'Poppins'),)),
);
}
}
Your syntax is kind of not correct for a few widgets and incorrect use of Expanded. This one will help you.
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _title.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return Container(
width: 70,
height: 30,
decoration: BoxDecoration(
color: isSelected
? Colors.yellow
: Colors.blue,
borderRadius: BorderRadius.circular(20),
),
child: Text(_title[index], style: TextStyle(fontFamily: 'Poppins'),),
);
},
);
}
Since you edit the question in middle of nowhere,
class YearSort extends StatefulWidget {
final String title;
const YearSort({
Key? key,
required this.title,
}) : super(key: key);
#override
State<YearSort> createState() => _YearSortState();
}
class _YearSortState extends State<YearSort> {
int selectedIndex = 0;
static List chips = [
DateTime.now().year,
DateTime.now().year - 1,
DateTime.now().year - 2,
DateTime.now().year - 3,
DateTime.now().year - 4
];
#override
Widget build(BuildContext context) {
return Column(
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(children: [
for(int index = 0; index < chips.length; index++)
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
child: ByYear(
year: chips[index].toString(),
isSelected: selectedIndex == index,
),
onTap: () {
selectedIndex = index;
setState(() {});
},
),
)
],),
),
Expanded(child: Container())
],
);
}
}
class ByYear extends StatefulWidget {
final String year;
final bool isSelected;
const ByYear({Key? key, required this.year, this.isSelected = false})
: super(key: key);
#override
State<ByYear> createState() => _ByYearState();
}
class _ByYearState extends State<ByYear> {
#override
Widget build(BuildContext context) {
Color color = widget.isSelected
? const Color.fromARGB(255, 25, 27, 25)
: const Color.fromARGB(255, 215, 237, 255);
Color textColor = !widget.isSelected
? const Color.fromARGB(255, 25, 27, 25)
: const Color.fromARGB(255, 215, 237, 255);
return Container(
height: 30,
width: 100,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(10),
),
child: Center(
child: Text(
widget.year,
style: TextStyle(
fontSize: 16, color: textColor, decoration: TextDecoration.none),
),
),
);
}
}
Note: You can also use Listview.builder instead of singlechildscrollview
If you have an array to show in a scrollable widget, you can use Listview instead of SingleChildScrollView. Like this:
#override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.horizontal,
children: const [
ByYear(year: 'This year'),
ByYear(year: '2021'),
ByYear(year: '2020'),
ByYear(year: '2019'),
ByYear(year: '2018'),
],
shrinkWrap: true,
);
}
hope it helps you.

Flutter - Round out corners Tab Bars

I want to add this kind of tabBars in flutter.
Please suggest any way to achieve this using a library or code or any idea. That would be helpful.
Thanks for the help!
Not the perfect one also not even optimized. I will update in my free time. At the moment you can play with the code and make a change according to your requirement.
Live playground/demo: https://dartpad.dev/?id=610bea5fb086bf495550f99e9a9db839
Gist link: https://gist.github.com/omishah/610bea5fb086bf495550f99e9a9db839
Complete code:
import 'package:flutter/material.dart';
void main() {
runApp(const CodeCyanApp());
}
class CodeCyanApp extends StatelessWidget {
const CodeCyanApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int activeTabIndex = 0;
int totalTabs = 5;
static const bgColor = Colors.black;
static const tabBgColor = Colors.orange;
static const activeTabBgColor = Colors.white;
static const tabCornerRadiusColor = bgColor;
static const tabMinWidth = 90.0;
static const tabRadius = 17.0;
static const tabCornerRadius = 10.0;
static const tabContentPadding = 12.0;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: bgColor,
bottomNavigationBar: roundOutCornersTabBar(),
body: const Text("DEMO"),
// This trailing comma makes auto-formatting nicer for build methods.
);
}
Widget roundOutCornersTabBar() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children:
List<Widget>.generate(totalTabs, (index) => buildSingleTab(index)));
}
Widget buildSingleTab(int index) {
return Wrap(crossAxisAlignment: WrapCrossAlignment.end, children: [
(index == 0
? Stack(children: [
Container(
decoration: BoxDecoration(
color: index == activeTabIndex
? activeTabBgColor
: tabBgColor),
child: Container(
decoration: const BoxDecoration(
color: tabCornerRadiusColor,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(tabCornerRadius),
))),
width: 10,
height: 10,
)
])
: Container()),
InkWell(
focusColor: Colors.transparent,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
onTap: () => setState(() {
activeTabIndex = index;
}),
child: Stack(
alignment: (index == activeTabIndex - 1
? Alignment.bottomRight
: index == activeTabIndex + 1
? Alignment.bottomLeft
: Alignment.bottomCenter),
children: [
(index == activeTabIndex - 1 || index == activeTabIndex + 1
? Container(width: 15, height: 15, color: activeTabBgColor)
: Container()),
Container(
constraints: const BoxConstraints(minWidth: tabMinWidth),
child: Text("Tab ${index + 1}", textAlign: TextAlign.center),
padding: const EdgeInsets.all(tabContentPadding),
decoration: BoxDecoration(
color:
index == activeTabIndex ? Colors.white : tabBgColor,
borderRadius: BorderRadius.only(
bottomLeft: (index == activeTabIndex + 1
? const Radius.circular(tabRadius)
: const Radius.circular(0)),
bottomRight: (index != totalTabs - 1 &&
index == activeTabIndex - 1
? const Radius.circular(tabRadius)
: const Radius.circular(0)),
topLeft: const Radius.circular(tabRadius),
topRight: const Radius.circular(tabRadius))),
),
])),
(index == totalTabs - 1
? Stack(children: [
Container(
decoration: BoxDecoration(
color: index == activeTabIndex
? activeTabBgColor
: tabBgColor),
child: Container(
decoration: const BoxDecoration(
color: tabCornerRadiusColor,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(tabCornerRadius),
))),
width: 10,
height: 10,
)
])
: Container())
]);
}
}
Ps: Will optimise the code and fix any bugs in my free time. Thank you

Having trouble with my horizontal scroll - flutter

I hope you are doing well today. I'm having an issue with this horizontal scroll in flutter. The images are supposed to scroll left and right and depending on the picture, you will press the button and have the ability to guess the type of pic. For some reason, images and tags don't match with images. The image names are linked to the vehicleNames list in _MyHomePageState. I have also included image_card.dart to show how ImageCard works. Thank you for the second set of eyes.
main.dart
import 'dart:ui';
import 'package:flutter/material.dart';
import 'image_card.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Guess the car!'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin{
String curVehName = "";
double scrollPercent = 0.0;
Offset startDrag;
double startDragPercentScroll;
double finishScrollStart;
double finishScrollEnd;
AnimationController finishScrollController;
List<String> vehicleNames = [
'bmw',
'ford',
'rover',
'toyota'
];
#override
initState(){
super.initState();
finishScrollController = AnimationController(
duration: Duration(milliseconds: 150),
vsync: this,
)..addListener(() {
setState(() {
scrollPercent = lerpDouble(finishScrollStart, finishScrollEnd,
finishScrollController.value);
});
});
#override
dispose(){
finishScrollController.dispose();
super.dispose();
}
}
List<Widget> buildCards(){
List<Widget> cardList = [];
for(int i = 0; i < vehicleNames.length;i++){
cardList.add(buildCard(i,scrollPercent));
print("index: ${i}");
}
return cardList;
}
Widget buildCard(int cardIndex, double scrollPercent){
final cardScrollPercent = scrollPercent / ( 1 / vehicleNames.length);
return FractionalTranslation(
translation: Offset(cardIndex-cardScrollPercent,0.0),
child: Padding(
padding: EdgeInsets.all(8.0),
child: ImageCard(imageName: vehicleNames[cardIndex],
),
),
);
}
onHorizontalDragStart(DragStartDetails details){
startDrag = details.globalPosition;
startDragPercentScroll = scrollPercent;
}
onHorizontalDragUpdate(DragUpdateDetails details){
final currentDrag = details.globalPosition;
final dragDistance = currentDrag.dx - startDrag.dx;
final singleCardDragPercent = dragDistance / context.size.width;
setState(() {
scrollPercent = ( startDragPercentScroll + ( -singleCardDragPercent
/ vehicleNames.length)).clamp(0.0, 1.0-(1/vehicleNames.length));
});
}
onHorizontalDragEnd(DragEndDetails details){
finishScrollStart = scrollPercent;
finishScrollEnd = (scrollPercent * vehicleNames.length).round()
/vehicleNames.length;
finishScrollController.forward(from: 0.0);
setState(() {
startDrag = null;
startDragPercentScroll = null;
curVehName = '';
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
GestureDetector(
onHorizontalDragStart: onHorizontalDragStart,
onHorizontalDragUpdate: onHorizontalDragUpdate,
onHorizontalDragEnd: onHorizontalDragEnd,
behavior: HitTestBehavior.translucent ,
child: Stack(
children: buildCards(),
),
),
OutlineButton(
padding: EdgeInsets.all(10.0),
onPressed: (){
setState((){
this.curVehName = vehicleNames[(scrollPercent*10).round()];
});
},
child: Text(
'Show Answer',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
borderSide: BorderSide(
color: Colors.black,
width: 4.0,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
highlightedBorderColor: Colors.black,
),
Text(
curVehName,
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: Colors.blue,
letterSpacing: 2,
),
),
],
),
),
);
}
}
image_card.dart
import 'package:flutter/material.dart';
class ImageCard extends StatelessWidget{
final String imageName;
ImageCard({this.imageName});
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
border: Border.all(
color: Colors.black,
width: 4.0,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Image.asset(
'assets/images/$imageName.jpg',
height: 300,
fit: BoxFit.fitHeight,
),
),
);
}
}
I believe I found the issue. It seems that the
this.curVehName = vehicleNames[(scrollPercent*10).round()];
hard-coded the value of numbers needed in my vehicle names list. Once I added 10 pictures and added names to the list, it then worked as directed. The goal now is to see if I can make this a dynamic list.