Des amis qui élèvent des chatons viennent dans ma boutique !
Le projet d'épicerie pour chats est continuellement mis à jour
Si l'idée de conception de l'APP est modifiée en catpedia, elle ne connaîtra pas le back-end, la source de données capture le paquet et l'enregistre localement
- Connectez-vous et inscrivez-vous, ajoutez des informations personnelles au stockage local et modifiez
- Version internationalisée
- modes jour et nuit
- Suppression d'un article du panier
- Processus de paiement de commande à commande
- Tri des outils (taille de la police, couleur de la police, couleur du thème, etc.)
- Traitement de l'outil de bibliothèque d'images
- optimisation de l'interception des pages webview
- Optimisation globale des performances
- Emballage
sp_util est une belle classe utilitaire
flutter_screenutil
Besoin d'enregistrer la largeur et la hauteur lors de l'utilisation
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(375, 681),
builder: (BuildContext context, Widget? child) => ChangeNotifierProvider(
create: (context) => CarModel(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const StartPage(),
),
),
);
}
}
vue_web_flutter
Ajouter un saut de vue Web
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebPage extends StatefulWidget {
final String url;
final String title;
const WebPage({super.key, required this.url, required this.title});
@override
State<WebPage> createState() => _WebPageState(this.url, this.title);
}
class _WebPageState extends State<WebPage> {
final String url;
final String title;
late final WebViewController controller;
_WebPageState(this.url, this.title);
@override
void initState() {
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// Update loading bar.
},
onPageStarted: (String url) {},
onPageFinished: (String url) {},
onWebResourceError: (WebResourceError error) {},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://pub.dev/')) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse(url));
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
Expanded(
child: WebViewWidget(controller: controller),
),
],
),
);
}
}
Annonces de compte à rebours, utilisez la minuterie pour compter à rebours,
second_index est l'heure définie
Définissez l'heure à modifier par vous-même, ou vous pouvez la modifier côté serveur
import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cat_1/pages/intro_page.dart';
import 'package:flutter_cat_1/tools/tools.dart';
import 'package:flutter_cat_1/web/web_page.dart';
import 'package:google_fonts/google_fonts.dart';
import '../main.dart';
class WelcomePage extends StatefulWidget {
const WelcomePage({super.key});
@override
State<WelcomePage> createState() => _WelcomePageState();
}
class _WelcomePageState extends State<WelcomePage> {
int second_index = 3;
int timeIndex = 0;
late Timer timer;
@override
void initState() {
super.initState();
timer = Timer.periodic(
Duration(milliseconds: 50),
(sd) {
setState(() {
timeIndex += 1;
if (timeIndex % 20 == 0) {
second_index -= 1;
}
});
if (timeIndex >= 100) {
_openMain();
}
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Image.asset(
"assets/IMG_8050.png",
fit: BoxFit.cover,
),
),
Container(
alignment: Alignment.bottomRight,
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
),
alignment: Alignment.center,
margin: const EdgeInsets.all(25),
child: GestureDetector(
onTap: () {
_openMain();
},
child: Stack(
children: <Widget>[
Container(
alignment: Alignment.center,
child: CircularProgressIndicator(
value: timeIndex / 100,
strokeWidth: 2,
),
),
Container(
alignment: Alignment.center,
child: Text(
"跳过",
style: TextStyle(fontSize: 12,color: Colors.white,fontWeight: FontWeight.bold,),
),
),
],
),
),
),
),
Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RichText(
text: TextSpan(
text: "Flutter开发日常练习",
style: GoogleFonts.notoSerif(
color: Colors.black,
fontSize: 17,
),
children: [
TextSpan(
text: "-小猫咪杂货店",
style: GoogleFonts.notoSerif(
color: Colors.black,
fontSize: 17,
// decoration: TextDecoration.underline,
// decorationColor: Colors.blue[800],
)),
],
),
textDirection: TextDirection.ltr,
),
SizedBox(
height: 10,
),
RichText(
text: TextSpan(
text: 'CSDN关注',
style: GoogleFonts.notoSerif(
fontSize: 17,
color: Colors.black,
),
children: [
TextSpan(
text: 'workersJiaDa',
style: GoogleFonts.notoSerif(
fontSize: 17,
color: Colors.blue[800],
decoration: TextDecoration.underline,
decorationColor: Colors.blue[800],
fontWeight: FontWeight.bold,
),
recognizer: TapGestureRecognizer()
..onTap = () {
timer.cancel();
Navigator.of(context)
.push(
MaterialPageRoute(
builder: (context) => WebPage(
url:
"https://blog.csdn.net/zxc8890304",
title: "workersJiaDa的博客"),
),
)
.then((value) => reGetTimer());
}),
],
),
),
SizedBox(
height: 10,
),
RichText(
text: TextSpan(
text: '抖音商城搜索',
style: GoogleFonts.notoSerif(
color: Colors.black,
fontSize: 17,
),
children: [
TextSpan(
text: "早睡早起的猫咪小铺子",
style: GoogleFonts.notoSerif(
fontSize: 17,
color: Colors.blue[800],
decoration: TextDecoration.underline,
decorationColor: Colors.blue[800],
fontWeight: FontWeight.bold,
),
recognizer: TapGestureRecognizer()
..onTap = () {
// timer.cancel();
launchURL('snssdk1128://user/profile/88486395468');
},
),
],
),
),
],
),
),
],
),
);
}
void _openMain() {
timer.cancel();
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const IntroPage(),
),
(route) => false);
}
void reGetTimer() {
timer = Timer.periodic(
Duration(milliseconds: 50),
(sd) {
setState(() {
timeIndex += 1;
if (timeIndex % 20 == 0) {
second_index -= 1;
}
});
if (timeIndex >= 100) {
_openMain();
}
},
);
}
}
Les mots sur la page d'accueil sont
flutter_swiper_plus
Très brut, simple pour obtenir l'effet, pour ré-optimiser et modifier
Le jugement de isFirst a été ajouté, et il ne sera affiché que lorsque l'APP est ouvert pour la première fois.
J'ai déjà réalisé des projets internationaux et la demande à l'étranger est
Tant que vous entrez dans la page d'accueil sans cliquer sur ignorer ou entrer dans l'APP , vous entrerez à nouveau dans la page d'accueil chaque fois que vous entrerez dans l'APP
Dans ce cas, les conditions de jugement doivent être révisées.
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cat_1/welcome/welcome_page.dart';
import 'package:flutter_swiper_plus/flutter_swiper_plus.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:sp_util/sp_util.dart';
class StartPage extends StatefulWidget {
const StartPage({super.key});
@override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
bool _isLogin = false;
bool _isFirst = false;
bool _islastPage = false;
int pageInt = 1;
final List _welcomeList = ["welcome_1.png", "welcome_2.png", "welcome_3.png"];
@override
void initState() {
super.initState();
_isFirst = SpUtil.getBool("first")!;
if (_isFirst) {
String? tokenStr = SpUtil.getString('token');
_isLogin = (tokenStr == null || tokenStr.isEmpty) ? false : true;
new Future.delayed(Duration(seconds: 0), () {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => WelcomePage(),
),
(route) => false);
});
}
}
@override
Widget build(BuildContext context) {
return
Container(
color: Colors.white,
height: ScreenUtil().screenHeight,
child: Stack(
children: [
Swiper(
itemBuilder: (context, index) {
return Image.asset(
"assets/${_welcomeList[index]}",
fit: BoxFit.cover,
);
},
itemCount: _welcomeList.length,
loop: false,
onIndexChanged: (index) {
pageInt = index + 1;
if (index == _welcomeList.length - 1) {
setState(() {
_islastPage = true;
});
} else {
setState(() {
_islastPage = false;
});
}
},
),
Container(
width: 100,
height: 40,
alignment: Alignment.center,
margin: EdgeInsets.only(
left: ScreenUtil().screenWidth / 2 - 50,
bottom: 40,
top: ScreenUtil().screenHeight - 80,
right: ScreenUtil().screenWidth / 2 - 50,
),
child: Text(
"$pageInt / 3",
style: const TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.bold,
decoration: TextDecoration.none),
),
),
Container(
width: 100,
height: 40,
margin: EdgeInsets.only(
left: ScreenUtil().screenWidth / 2 + 100,
bottom: 40,
top: ScreenUtil().screenHeight - 80,
// right: ScreenUtil().screenWidth / 2 - 10,
),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.blue[900], width: 2),
// borderRadius: BorderRadius.circular(8),
// ),
child: Center(
child: TextButton(
onPressed:() => _jumpWelcome(),
// Navigator.push(context, MaterialPageRoute(
// builder: (context) {
// },
// ));
child: _islastPage
? Text(
"立即进入",
style: GoogleFonts.notoSerif(
fontSize: 17,
color: Colors.blue[700],
fontWeight: FontWeight.bold),
)
: Text(
"跳过",
style: GoogleFonts.notoSerif(
fontSize: 17,
color: Colors.blue[700],
fontWeight: FontWeight.bold),
),
),
),
)
],
),
);
}
_jumpWelcome() {
SpUtil.putBool('first',true);
String? tokenStr = SpUtil.getString("token");
_isLogin = (tokenStr == null || tokenStr.isEmpty) ? false : true;
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => WelcomePage(),
),
(route) => false);
}
}
La barre coulissante latérale est également relativement simple
import 'package:flutter/material.dart';
import 'package:flutter_cat_1/welcome/start_page.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class DrawerPage extends StatelessWidget {
const DrawerPage({super.key});
@override
Widget build(BuildContext context) {
return Drawer(
elevation: 300,
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text("铲屎官铲屎官铲屎官"),
accountEmail: Text("饲养员饲养员饲养员"),
currentAccountPicture: CircleAvatar(
backgroundImage: AssetImage("assets/IMG_9406.png"),
),
arrowColor: Colors.red,
onDetailsPressed: () {},
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
offset: Offset(10, 10),
blurRadius: 45,
spreadRadius: 0.0,
),
],
image: DecorationImage(
image: AssetImage(
"assets/IMG_0259.png",
),
fit: BoxFit.cover),
),
),
Expanded(
child: ListView(
padding: EdgeInsets.symmetric(vertical: 10),
children: <Widget>[
ListTile(
title: Text('个人中心'),
),
Divider(),
ListTile(
title: Text('我的小猫咪'),
),
Divider(),
ListTile(
title: Text("我的订单"),
),
Divider(),
ListTile(
title: Text("关于我们"),
),
Divider(),
],
),
),
],
),
Positioned(
bottom: 20,
right: 20,
child: InkWell(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
GestureDetector(
onTap: () => Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) {
return StartPage();
},
), (route) => false),
child: MenuItemButton(
child: Column(
children: const <Widget>[
Icon(
Icons.power_settings_new_outlined,
color: Colors.red,
),
Text(
"退出登录",
style: TextStyle(
color: Colors.red,
fontSize: 12,
),
),
],
)),
),
],
),
),
),
],
),
);
}
}
Adresse DEMO