Flutter移动应用开发 - 商品界面

2023-11-01

0. 项目简介

项目想法脱胎于2023年服务外包大赛A18题 随手买(详情

整个APP思路如下:

在这里插入图片描述

这篇博客主要服务于乘客界面的商品界面,模仿京东的商品界面、淘宝的商品详情界面和相关商品界面、探探的翻牌界面、得物的购买记录界面。

1. 效果展示

商品展示(图片轮播)+ toast 下边提示框

在这里插入图片描述

模仿淘宝商品详情(长图)

在这里插入图片描述

模仿探探卡片的评论

在这里插入图片描述

模仿得物的最近购买

在这里插入图片描述

相关商品

在这里插入图片描述

2. 代码

依赖如下

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  # 获取设备大小
  flutter_screenutil: ^3.1.0
  # 配置轮播图插件
  flutter_swiper: ^1.1.6
  # 下边提示
  fluttertoast: ^4.0.1
  # 最近购买标签旋转动画
  toggle_rotate: ^0.0.5

相关文件如下

// 商品界面
commodity.dart
// 卡片评论界面
CardTry.dart
// 最近购买界面
recentPurchase.dart

commodity.dart


import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
import 'CardTry.dart';
import 'recentPurchase.dart';


class _Page {
  _Page({required this.label});

  final String label;

  String get id => label[0];

  @override
  String toString() => '$runtimeType("$label")';
}

class _CardData {
  const _CardData({required this.title, required this.imageAsset, required this.price});

  final String title;
  final String imageAsset;
  final String price;
}
class _CardInfo {
  _CardInfo({
    required this.images,
    required this.who,
    required this.saywhat,
    required this.time,
  });

  final String images;
  final String who;
  final String saywhat;
  final String time;
}
List <_CardInfo> cardinfo=[
  _CardInfo(
      images:'assets/images/feedback1.jpg',
      who: "水之凝落",
      saywhat: "内外包装无珀斯按!生产日期是2022年10月8日!",
      time: "2022-10-30 18:32"
  ),
  _CardInfo(
      images:'assets/images/feedback2.jpg',
      who: "咚咚呛喇",
      saywhat: "还是那个喜欢的喂到",
      time: "2022-08-10 15:13"
  ),
  _CardInfo(
      images:'assets/images/feedback3.jpg',
      who: "匿名买家",
      saywhat: "味道和超市卖的没什么差别,但是瓶子材质很软,偏细,给人一种廉价感",
      time: "2022-09-22 16:24"
  ),
  _CardInfo(
      images:'assets/images/feedback4.jpg',
      who: '6',
      saywhat: "666666",
      time: "2022-12-31 23:11"
  ),
  _CardInfo(
      images:'assets/images/feedback5.jpg',
      who: "迪士尼在逃铖铖的公主",
      saywhat: "感觉没有超市里买的好喝,口感像是太甜,像糖精水吧。买都买了凑合着喝吧",
      time: "2022-08-20 13:30"
  ),
];


// 生成卡片数组
List<Widget> cards = List.generate(
  cardinfo.length,
      (int index) {
    return Container(
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(16.0),
          boxShadow: [
            BoxShadow(
              offset: Offset(0, 17),
              blurRadius: 23.0,
              spreadRadius: -13.0,
              color: Colors.black54,
            )
          ],
        ),
        child: Stack(
            children: [
              ClipRRect(
                borderRadius: BorderRadius.circular(16.0),
                child: Image.asset(
                  cardinfo[index].images,
                  fit: BoxFit.cover,
                ),
              ),
              Align(
                child: ClipRRect(
                  borderRadius: BorderRadius.vertical(bottom: Radius.circular(16.0)),
                  child: ListTile(
                    title: Text(cardinfo[index].who, style: TextStyle(fontSize: 18, color: Colors.black ,fontWeight: FontWeight.bold)),
                    subtitle: RichText(
                      textDirection: TextDirection.ltr,
                      text: TextSpan(
                          children: <TextSpan>[
                            TextSpan(
                              text: cardinfo[index].saywhat,
                              style: TextStyle(fontSize: 14, color: Colors.black),
                            ),
                            TextSpan(
                              text: "\n"+cardinfo[index].time,
                              style: TextStyle(fontSize: 12, height: 2, color:  Colors.grey),
                            ),
                            TextSpan(
                              text: "\n ",
                              style: TextStyle(fontSize: 1, color: Colors.grey),
                            ),
                          ]
                      ),
                    ),
                    trailing: Icon(Icons.more_vert),
                    isThreeLine: true,
                  ),
                ),
                alignment: Alignment.bottomCenter,
              ),
            ]
        )
    );
  },
);


final Map<_Page, List<_CardData>> _allPages = <_Page, List<_CardData>>{
  new _Page(label: '详情'): <_CardData>[
    // const _CardData(
    //   title: 'Old Binoculars',
    //   imageAsset: 'shrine/products/binoculars.png',
    // ),
    // const _CardData(
    //   title: 'Teapot',
    //   imageAsset: 'shrine/products/teapot.png',
    // ),
    // const _CardData(
    //   title: 'Blue suede shoes',
    //   imageAsset: 'shrine/products/chucks.png',
    // ),
  ],
  new _Page(label: '评论'): <_CardData>[
    // const _CardData(
    //   title: 'Beachball',
    //   imageAsset: 'shrine/products/beachball.png',
    // ),
    // const _CardData(
    //   title: 'Dipped Brush',
    //   imageAsset: 'shrine/products/brush.png',
    // ),
    // const _CardData(
    //   title: 'Perfect Goldfish Bowl',
    //   imageAsset: 'shrine/products/fish_bowl.png',
    // ),
  ],

  new _Page(label: '最近购买'): <_CardData>[
    // const _CardData(
    //   title: 'Beachball',
    //   imageAsset: 'shrine/products/beachball.png',
    // ),
    // const _CardData(
    //   title: 'Dipped Brush',
    //   imageAsset: 'shrine/products/brush.png',
    // ),
    // const _CardData(
    //   title: 'Perfect Goldfish Bowl',
    //   imageAsset: 'shrine/products/fish_bowl.png',
    // ),
  ],
  new _Page(label: '相关商品'): <_CardData>[
    const _CardData(
      title: '统一冰红茶',
      imageAsset: 'assets/images/tongyi1.jpg',
      price: '3.50'
    ),
    const _CardData(
      title: '统一阿萨姆奶茶500ml',
      imageAsset: 'assets/images/tongyi2.jpg',
      price: '49.49'
    ),
    const _CardData(
      title: '元气森林奶茶',
      imageAsset: 'assets/images/tongyi3.jpg',
      price: '11.99'
    ),
    const _CardData(
      title: '依然乳矿气泡水',
      imageAsset: 'assets/images/tongyi4.png',
      price: '6.46'
    ),
    const _CardData(
      title: '可口可乐',
      imageAsset: 'assets/images/tongyi5.jpeg',
      price: '3.20'
    ),
    const _CardData(
      title: '雪碧',
      imageAsset: 'assets/images/tongyi6.jpg',
      price: '3.19'
    ),
  ],
};

class _CardDataItem extends StatelessWidget {
  const _CardDataItem({required this.page, required this.data});

  static const double cardheight = 272.0;
  final _Page page;
  final _CardData data;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            // Align(
            //   alignment:
            //   page.id == 'L' ? Alignment.centerLeft : Alignment.centerRight,
            //   child: new CircleAvatar(child: new Text('${page.id}')),
            // ),
            SizedBox(
              width: 144.0,
              height: 160.0,
              child: Image.asset(
                data.imageAsset,
                fit: BoxFit.contain,
              ),
            ),
            Spacer(),
            Column(
              crossAxisAlignment:CrossAxisAlignment.start,
              children: <Widget>[
                Padding(
                    padding: EdgeInsets.only(left: 10.0,top: 0.0),
                    child: Text("¥${data.price}",
                      style: TextStyle(fontSize: 16.0,
                          color: Color(0xFFe9546b)),)),
                Padding(
                    padding: EdgeInsets.only(left: 12.0,top: 0.0),
                    child: Text("${data.title}",
                      style: TextStyle(fontSize: 16.0,
                          color: Color(0xFF333333)),)),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class commodity extends StatefulWidget {
  const commodity({Key? key}) : super(key: key);

  @override
  State<commodity> createState() => _commodityState();
}

class _commodityState extends State<commodity> {
  List bannerDatas = [
    'assets/images/asm1.png',
    'assets/images/asm2.jpg',
    'assets/images/asm3.jpg',
  ];
  late SwiperController _swiperController;


  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _swiperController = SwiperController();
    _swiperController.startAutoplay();
  }

  @override
  void dispose(){
    _swiperController.stopAutoplay();
    _swiperController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    ScreenUtil.init(context, allowFontScaling: false);

    return DefaultTabController(
      length: _allPages.length,
      child: Scaffold(
        appBar: AppBar(
          title: Text("识别结果", style: TextStyle(color: Colors.black),),
          backgroundColor: Colors.white70,
          elevation: 0,
          actions: <Widget>[
            IconButton(
              onPressed: (){
                Fluttertoast.showToast(
                  msg: "正在建设中11111111...",
                  toastLength: Toast.LENGTH_SHORT,
                  gravity: ToastGravity.BOTTOM,
                  // timeInSecForIos:1
                );
              },
              icon: Icon(Icons.more_horiz, color: Colors.black,),
            ),
          ],
        ),
          body: NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
            return <Widget>[
              SliverOverlapAbsorber(
                handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                sliver: SliverAppBar(
                  backgroundColor: Colors.transparent,
                  elevation: 0,
                  pinned: true,
                  // 悬浮框初始高度
                  expandedHeight: 380.0,
                  forceElevated: innerBoxIsScrolled,
                  bottom: PreferredSize(
                    child: Container(
                      child: TabBar(
                        indicatorColor: Colors.white,//选中下划线颜色,如果使用了indicator这里设置无效
                        labelColor: Colors.white,
                        labelStyle: TextStyle(fontSize: 16),
                        unselectedLabelStyle:TextStyle(fontSize: 14) ,
                        indicatorWeight: 3,
                        tabs: _allPages.keys.map(
                          (_Page page) => Tab(
                            child: Tab(text: page.label),
                          ),
                        ).toList(),
                      ),
                      color: Colors.redAccent[100],
                    ),
                    // 悬浮框锁定高度
                    preferredSize: Size(double.infinity, 0.0)
                  ),
                  flexibleSpace: FlexibleSpaceBar(
                    background:Column(
                      children: <Widget>[
                        Container(
                          width: MediaQuery.of(context).size.width,
                          height: 240.0,
                          margin: EdgeInsets.only(bottom: 10.0),
                          child: Swiper(
                            itemBuilder: (BuildContext context,int index){
                              return Image.asset(bannerDatas[index],fit: BoxFit.fill);
                            },
                            itemCount: bannerDatas.length,
                            autoplayDisableOnInteraction: true,
                            pagination: SwiperPagination(
                              builder: DotSwiperPaginationBuilder(size: 8, activeSize: 12,activeColor:Color(0xFFe9546b)),
                            ),
                            controller: _swiperController,
                          ),
                        ),
                        Container(
                          width: MediaQuery.of(context).size.width,
                          height:80.0,
                          decoration: BoxDecoration(
                            color: Colors.white,
                          ),
                          child: Column(
                            crossAxisAlignment:CrossAxisAlignment.start,
                            children: <Widget>[
                              Padding(
                                  padding: EdgeInsets.only(left: 10.0,top: 0.0),
                                  child: Text("¥4.50",
                                    style: TextStyle(fontSize: 16.0,
                                        color: Color(0xFFe9546b)),)),
                              Padding(
                                  padding: EdgeInsets.only(left: 10.0,top: 0.0),
                                  child: Text("¥5.00",
                                    style: TextStyle(fontSize: 12.0,
                                        color:Color(0xFFaaaaaa)),)),
                              Padding(
                                  padding: EdgeInsets.only(left: 12.0,top: 0.0),
                                  child: Text("统一阿萨姆奶茶",
                                    style: TextStyle(fontSize: 16.0,
                                        color: Color(0xFF333333)),)),
                            ],
                          ),
                        ),
                      ],
                    ),

                  ),

                ),
              )
            ];
          },
          body: Stack(
            children: [
              TabBarView(
                children: _allPages.keys.map((_Page page) {
                  if (page.label=="相关商品"){
                    return SafeArea(
                      top: false,
                      bottom: false,
                      child: Builder(
                        builder: (BuildContext context){
                          return CustomScrollView(
                            key: PageStorageKey<_Page>(page),
                            slivers: <Widget>[
                              SliverOverlapInjector(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context)),
                              SliverPadding(
                                padding: const EdgeInsets.symmetric(
                                  vertical: 8.0,
                                  horizontal: 16.0,
                                ),
                                sliver: SliverFixedExtentList(
                                  itemExtent: _CardDataItem.cardheight,
                                  delegate: SliverChildBuilderDelegate(
                                    (BuildContext context, int index){
                                      final _CardData data = _allPages[page]![index];
                                      return Padding(
                                        padding: const EdgeInsets.symmetric(
                                          vertical: 8.0,
                                        ),
                                        child: _CardDataItem(
                                          page: page,
                                          data: data,
                                        ),
                                      );
                                    },
                                    childCount:_allPages[page]?.length,
                                  ),
                                ),
                              ),
                            ],
                          );
                        },
                      ),
                    );
                  }
                  else if(page.label=="评论"){
                    return Padding(
                      padding: EdgeInsets.fromLTRB(20, 70, 20, 20),
                      child: Container(
                        height: 400,
                        child: CardTry(cards: cards,),
                      ),
                    );
                  }
                  else if(page.label=="详情"){
                    String imagename = "assets/images/asm.png";
                    print(Image.asset(imagename).height);
                    return SafeArea(
                      top: false,
                      bottom: false,
                      child: Builder(
                        builder: (BuildContext context){
                          return CustomScrollView(
                            slivers: <Widget>[
                              SliverOverlapInjector(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context)),
                              SliverFixedExtentList(
                                itemExtent: Image.asset(imagename).height == null? 3600 : Image.asset(imagename).height!,
                                delegate: SliverChildBuilderDelegate(
                                  (BuildContext context, int index){
                                    return Image.asset(imagename, width: MediaQuery.of(context).size.width,);
                                  },
                                  childCount: 1,
                                ),
                              ),
                            ],
                          );
                        },
                      ),
                    );
                  }
                  else if(page.label=="最近购买"){
                    return SafeArea(
                      top: false,
                      bottom: false,
                      child: TolyExpandTile(),
                    );
                  }
                  else{
                    return SafeArea(
                      top: false,
                      bottom: false,
                      child: Container(
                        height: 1000,
                      )
                    );
                  }
                }).toList()
              ),
              Positioned(
                width: ScreenUtil().setWidth(1070),
                height: ScreenUtil().setHeight(110),
                bottom: 0,
                child: Container(
                  decoration: BoxDecoration(
                    border: Border(
                      top: BorderSide(color: Color(0xFFe5e5e5), width: 1),
                    ),
                    color: Colors.white,
                  ),
                  child: Row(
                    children: [
                      Container(
                        padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)),
                        width: 60,
                        height: ScreenUtil().setHeight(88),
                        child:
                        InkWell(
                          onTap: () {
                            Fluttertoast.showToast(
                                msg: "正在建设中22222222...",
                                toastLength: Toast.LENGTH_SHORT,
                                gravity: ToastGravity.BOTTOM,
                                // timeInSecForIos:1
                            );
                          },
                          child:Column(
                            children: <Widget>[
                              Icon(
                                Icons.message,
                                size: 15,
                              ),
                              Text('联系客服',  style: new TextStyle(fontSize: 12.0,
                                  color:const Color(0xFF666666)))
                            ],
                          ),
                        ),
                      ),
                      Container(
                        padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)),
                        width: 60,
                        height: ScreenUtil().setHeight(88),
                        child:
                        InkWell(
                          onTap: () {
                            Fluttertoast.showToast(
                              msg: "正在建设中22222222...",
                              toastLength: Toast.LENGTH_SHORT,
                              gravity: ToastGravity.BOTTOM,
                              // timeInSecForIos:1
                            );
                          },
                          child:Column(
                            children: <Widget>[
                              Icon(
                                Icons.message,
                                size: 15,
                              ),
                              Text('联系客服',  style: new TextStyle(fontSize: 12.0,
                                  color:const Color(0xFF666666)))
                            ],
                          ),
                        ),
                      ),
                      Container(
                        padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)),
                        width: 60,
                        height: ScreenUtil().setHeight(88),
                        child:
                        InkWell(
                          onTap: () {
                            Fluttertoast.showToast(
                              msg: "正在建设中22222222...",
                              toastLength: Toast.LENGTH_SHORT,
                              gravity: ToastGravity.BOTTOM,
                              // timeInSecForIos:1
                            );
                          },
                          child:Column(
                            children: <Widget>[
                              Icon(
                                Icons.message,
                                size: 15,
                              ),
                              Text('联系客服',  style: new TextStyle(fontSize: 12.0,
                                  color:const Color(0xFF666666)))
                            ],
                          ),
                        ),
                      ),
                      Expanded(
                        flex: 1,
                        child: ElevatedButton (
                          style: ButtonStyle(
                            backgroundColor: MaterialStateProperty.all(Color.fromRGBO(253, 1, 0, 0.9)),
                          ),
                          child: Text('加入购物车'),
                          onPressed: () {
                            Fluttertoast.showToast(
                              msg: "正在建设中...",
                              toastLength: Toast.LENGTH_SHORT,
                              gravity: ToastGravity.BOTTOM,
                              // timeInSecForIos:1
                            );
                          },
                        ),
                      ),
                    ],
                  ),
                ),

              ),
            ],
          ),


        ),


      ),

    );


  }
}


CardTry.dart


import 'package:flutter/material.dart';
import 'dart:math';
import 'package:flutter/physics.dart';


class _CardInfo {
  _CardInfo({
    required this.images,
    required this.who,
    required this.saywhat,
    required this.time,
  });

  final String images;
  final String who;
  final String saywhat;
  final String time;
}
List <_CardInfo> cardinfo=[
  _CardInfo(
    images:'assets/images/feedback1.jpg',
    who: "yonghu1",
    saywhat: "saywhat1",
    time: "time1"
  ),
  _CardInfo(
      images:'assets/images/feedback2.jpg',
      who: "yonghu1",
      saywhat: "saywhat1",
      time: "time1"
  ),
  _CardInfo(
      images:'assets/images/feedback3.jpg',
      who: "yonghu1",
      saywhat: "saywhat1",
      time: "time1"
  ),
  _CardInfo(
      images:'assets/images/feedback4.jpg',
      who: "yonghu1",
      saywhat: "saywhat1",
      time: "time1"
  ),
  _CardInfo(
      images:'assets/images/feedback5.jpg',
      who: "yonghu1",
      saywhat: "saywhat1",
      time: "time1"
  ),
];
// 图片
// List<String> images = [
//   'assets/images/feedback1.jpg',
//   'assets/images/feedback2.jpg',
//   'assets/images/feedback3.jpg',
//   'assets/images/feedback4.jpg',
//   'assets/images/feedback5.jpg',
// ];


// 生成卡片数组
List<Widget> cards = List.generate(
  cardinfo.length,
      (int index) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16.0),
        boxShadow: [
          BoxShadow(
            offset: Offset(0, 17),
            blurRadius: 23.0,
            spreadRadius: -13.0,
            color: Colors.black54,
          )
        ],
      ),
      child: Stack(
        children: [
          ClipRRect(
            borderRadius: BorderRadius.circular(16.0),
            child: Image.asset(
              cardinfo[index].images,
              fit: BoxFit.cover,
            ),
          ),
          Align(
            child: ClipRRect(
              borderRadius: BorderRadius.vertical(bottom: Radius.circular(16.0)),
              child: ListTile(
                title: Text(cardinfo[index].who, style: TextStyle(fontSize: 18, color: Colors.black)),
                subtitle: RichText(
                  textDirection: TextDirection.ltr,
                  textAlign: TextAlign.center,
                  text: TextSpan(
                    children: <TextSpan>[
                      TextSpan(
                        text: cardinfo[index].saywhat,
                        style: TextStyle(fontSize: 18, color: Colors.black),
                      ),
                      TextSpan(
                        text: cardinfo[index].time,
                        style: TextStyle(fontSize: 16, height: 2),
                      ),
                    ]
                  ),
                ),
                trailing: Icon(Icons.more_vert),
                isThreeLine: true,
              ),
            ),
            alignment: Alignment.bottomCenter,
          ),
        ]
      )
    );
  },
);


void main() {
  // 使用生成的卡片数组
  runApp(CardTry(cards: cards));
}


/// 卡片尺寸
class CardSizes {
  static Size front(BoxConstraints constraints) {
    return Size(constraints.maxWidth * 0.9, constraints.maxHeight * 0.9);
  }

  static Size middle(BoxConstraints constraints) {
    return Size(constraints.maxWidth * 0.85, constraints.maxHeight * 0.9);
  }

  static Size back(BoxConstraints constraints) {
    return Size(constraints.maxWidth * 0.8, constraints.maxHeight * .9);
  }
}

/// 卡片位置
class CardAlignments {
  static Alignment front = Alignment(0.0, -0.5);
  static Alignment middle = Alignment(0.0, 0.0);
  static Alignment back = Alignment(0.0, 0.5);
}

/// 卡片运动动画
class CardAnimations {
  /// 最前面卡片的消失动画值
  static Animation<Alignment> frontCardDisappearAnimation(
      AnimationController parent,
      Alignment beginAlignment,
      ) {
    return AlignmentTween(
      begin: beginAlignment,
      end: Alignment(
        beginAlignment.x > 0
            ? beginAlignment.x + 30.0
            : beginAlignment.x - 30.0,
        0.0,
      ),
    ).animate(
      CurvedAnimation(
        parent: parent,
        curve: Interval(0.0, 0.5, curve: Curves.easeIn),
      ),
    );
  }

  /// 中间卡片位置变换动画值
  static Animation<Alignment> middleCardAlignmentAnimation(
      AnimationController parent,
      ) {
    return AlignmentTween(
      begin: CardAlignments.middle,
      end: CardAlignments.front,
    ).animate(
      CurvedAnimation(
        parent: parent,
        curve: Interval(0.2, 0.5, curve: Curves.easeIn),
      ),
    );
  }

  /// 中间卡片尺寸变换动画值
  static Animation middleCardSizeAnimation(
      AnimationController parent,
      BoxConstraints constraints,
      ) {
    return SizeTween(
      begin: CardSizes.middle(constraints),
      end: CardSizes.front(constraints),
    ).animate(
      CurvedAnimation(
        parent: parent,
        curve: Interval(0.2, 0.5, curve: Curves.easeIn),
      ),
    );
  }

  /// 最后面卡片位置变换动画值
  static Animation<Alignment> backCardAlignmentAnimation(
      AnimationController parent,
      ) {
    return AlignmentTween(
      begin: CardAlignments.back,
      end: CardAlignments.middle,
    ).animate(
      CurvedAnimation(
        parent: parent,
        curve: Interval(0.4, 0.7, curve: Curves.easeIn),
      ),
    );
  }

  /// 最后面卡片尺寸变换动画值
  static Animation backCardSizeAnimation(
      AnimationController parent,
      BoxConstraints constraints,
      ) {
    return SizeTween(
      begin: CardSizes.back(constraints),
      end: CardSizes.middle(constraints),
    ).animate(
      CurvedAnimation(
        parent: parent,
        curve: Interval(0.4, 0.7, curve: Curves.easeIn),
      ),
    );
  }
}


class CardTry extends StatefulWidget {
  final List<Widget> cards;
  const CardTry({required this.cards});

  @override
  _CardTryState createState() => _CardTryState();
}

class _CardTryState extends State<CardTry>  with TickerProviderStateMixin {
  // 卡片列表
  final List<Widget> _cards = [];
  // 最前面卡片的索引
  int _frontCardIndex = 0;
  // 保存最前面卡片的定位
  Alignment _frontCardAlignment = Alignment(0.0, -0.5);
  // 保存最前面卡片的旋转角度
  double _frontCardRotation = 0.0;
  // 卡片回弹动画
  late Animation<Alignment> _reboundAnimation;
  // 卡片回弹动画控制器
  late AnimationController _reboundController;
  // 卡片位置变换动画控制器
  late AnimationController _cardChangeController;


  //  前面的卡片,使用 Align 定位
  Widget _frontCard(BoxConstraints constraints) {
    // 判断是否还有卡片
    Widget card =
    _frontCardIndex < _cards.length ? _cards[_frontCardIndex] : Container();
    // 判断动画是否在运行
    bool forward = _cardChangeController.status == AnimationStatus.forward;

    // 使用 Transform.rotate 旋转卡片
    Widget rotate = Transform.rotate(
      angle: (pi / 180.0) * _frontCardRotation,
      // 使用 SizedBox 确定卡片尺寸
      child: SizedBox.fromSize(
        size: CardSizes.front(constraints),
        child: card,
      ),
    );

    // 在动画运行时使用动画值
    if (forward) {
      return Align(
        alignment: CardAnimations.frontCardDisappearAnimation(
          _cardChangeController,
          _frontCardAlignment,
        ).value,
        child: rotate,
      );
    }

    // 否则使用默认值
    return Align(
      alignment: _frontCardAlignment,
      child: rotate,
    );
  }

  // 中间的卡片,使用 Align 定位
  Widget _middleCard(BoxConstraints constraints) {
    // 判断是否还有两张卡片
    Widget card = _frontCardIndex < _cards.length - 1
        ? _cards[_frontCardIndex + 1]
        : Container();
    // 判断动画是否在运行
    bool forward = _cardChangeController.status == AnimationStatus.forward;

    // 在动画运行时使用动画值
    if (forward) {
      return Align(
        alignment: CardAnimations.middleCardAlignmentAnimation(
          _cardChangeController,
        ).value,
        child: card
      );
    }

    // 否则使用默认值
    return Align(
      alignment: CardAlignments.middle,
      child: SizedBox.fromSize(
        size: CardSizes.middle(constraints),
        child: card,
      ),
    );
  }

  // 后面的卡片,使用 Align 定位
  Widget _backCard(BoxConstraints constraints) {
    // 判断数组中是否还有三张卡片
    Widget card = _frontCardIndex < _cards.length - 2
        ? _cards[_frontCardIndex + 2]
        : Container();
    // 判断动画是否在运行
    bool forward = _cardChangeController.status == AnimationStatus.forward;

    // 在动画运行时使用动画值
    if (forward) {
      return Align(
        alignment: CardAnimations.backCardAlignmentAnimation(
          _cardChangeController,
        ).value,
        child: card
      );
    }

    // 否则使用默认值
    return Align(
      alignment: CardAlignments.back,
      child: SizedBox.fromSize(
        size: CardSizes.back(constraints),
        child: card,
      ),
    );
  }

  // 改变位置的动画
  void _runChangeOrderAnimation() {
    _cardChangeController.reset();
    _cardChangeController.forward();
  }


  // 卡片回弹的动画
  void _runReboundAnimation(Offset pixelsPerSecond, Size size) {
    // 创建动画值
    _reboundAnimation = _reboundController.drive(
      AlignmentTween(
        // 起始值是卡片当前位置,最终值是卡片的默认位置
        begin: _frontCardAlignment,
        end: Alignment(0.0, -0.5),
      ),
    );
    // 计算卡片运动速度
    final double unitsPerSecondX = pixelsPerSecond.dx / size.width;
    final double unitsPerSecondY = pixelsPerSecond.dy / size.height;
    final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
    final unitVelocity = unitsPerSecond.distance;
    // 创建弹簧模拟的定义
    const spring = SpringDescription(mass: 30, stiffness: 1, damping: 1);
    // 创建弹簧模拟
    final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
    // 根据给定的模拟运行动画
    _reboundController.animateWith(simulation);
    // 重置旋转值
    _frontCardRotation = 0.0;
    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    // 初始化卡片数组
    _cards.addAll(widget.cards);
    // 初始化回弹的动画控制器
    _reboundController = AnimationController(vsync: this)
      ..addListener(() {
        setState(() {
          // 动画运行时更新最前面卡片的 alignment 属性
          _frontCardAlignment = _reboundAnimation.value;
        });
      });
    // 初始化卡片换位动画控制器
    _cardChangeController = AnimationController(
      duration: Duration(milliseconds: 1000),
      vsync: this,
    )
      ..addListener(() => setState(() {}))
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          // 动画结束后将最前面卡片的索引向前移动一位
          _frontCardIndex++;
          // 动画运行结束后重置位置和旋转
          _frontCardRotation = 0.0;
          _frontCardAlignment = CardAlignments.front;
          setState(() {});
        }
      });
  }


  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'TCards demo',
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: SizedBox(
            width: 350,
            height: 590,
            child: LayoutBuilder(
              builder: (context, constraints) {
                // 使用 LayoutBuilder 获取容器的尺寸,传个子项计算卡片尺寸
                Size size = MediaQuery.of(context).size;
                double speed = 10.0;
                // 卡片横轴距离限制
                final double limit = 5.0;

                return Stack(
                  children: [
                    // 后面的子项会显示在上面,所以前面的卡片放在最后
                    _backCard(constraints),
                    _middleCard(constraints),
                    _frontCard(constraints),
                    // 使用一个占满父元素的 GestureDetector 监听手指移动
                    // 如果动画在运行中就不在响应手势
                    _cardChangeController.status != AnimationStatus.forward
                      ?
                    SizedBox.expand(
                      child: GestureDetector(
                        onPanDown: (DragDownDetails details) {},
                        onPanUpdate: (DragUpdateDetails details) {
                          // 手指移动就更新最前面卡片的 alignment 属性
                          _frontCardAlignment += Alignment(
                            details.delta.dx / (size.width / 4) * speed,
                            details.delta.dy / (size.height / 4) * speed,
                          );
                          // 设置最前面卡片的旋转角度
                          _frontCardRotation = _frontCardAlignment.x;
                          // setState 更新界面
                          setState(() {});
                        },
                        onPanEnd: (DragEndDetails details) {
                          // 如果最前面的卡片横轴移动距离超过限制就运行换位动画,否则运行回弹动画
                          if (_frontCardAlignment.x > limit ||
                              _frontCardAlignment.x < -limit) {
                            _runChangeOrderAnimation();
                          } else {
                            _runReboundAnimation(
                              details.velocity.pixelsPerSecond,
                              size,
                            );
                          }
                        },
                      ),
                    )
                    : IgnorePointer(),
                  ],
                );
              },
            ),
          ),
        ),
      ),
    );
  }
}

recentPurchase.dart

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:toggle_rotate/toggle_rotate.dart';


class _RecentBuy {
  _RecentBuy({
    required this.images,
    required this.name,
    required this.count,
    required this.getprice,
    required this.time,
  });

  final String images;
  final String name;
  final double getprice;
  final int count;
  final String time;
}




void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TolyExpandTile(),
    );
  }
}


class TolyExpandTile extends StatefulWidget {

  @override
  _TolyExpandTileState createState() => _TolyExpandTileState();
}

class _TolyExpandTileState extends State<TolyExpandTile> with SingleTickerProviderStateMixin {
  var _crossFadeState = CrossFadeState.showFirst;
  bool get isFirst => _crossFadeState == CrossFadeState.showFirst;



  List<_RecentBuy> rec = [
    _RecentBuy(
      images: 'assets/images/bg1.png',
      name: '爱*想',
      getprice: 4.50,
      count: 1,
      time: '1分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '1分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '1分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '2分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/hjh1.jpg',
      name: 's*w',
      getprice: 8.99,
      count: 2,
      time: '2分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '7*H',
      getprice: 4.50,
      count: 1,
      time: '4分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/hjh5.jpg',
      name: '6*6',
      getprice: 4.50,
      count: 1,
      time: '4分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '撑*铖',
      getprice: 4.50,
      count: 1,
      time: '4分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '事*发',
      getprice: 4.52,
      count: 1,
      time: '5分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '5分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '6分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '6分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 9.04,
      count: 2,
      time: '9分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '10分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '10分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '10分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '10分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/tongyi4.png',
      name: '爱*饿',
      getprice: 4.50,
      count: 1,
      time: '12分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '鳄*虚',
      getprice: 4.50,
      count: 1,
      time: '12分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '我*去',
      getprice: 4.50,
      count: 1,
      time: '13分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 8.99,
      count: 2,
      time: '13分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '14分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '二*粉',
      getprice: 4.50,
      count: 1,
      time: '16分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '20分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 8.89,
      count: 2,
      time: '20分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/bg1.png',
      name: '如*9',
      getprice: 4.50,
      count: 1,
      time: '24分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 17.59,
      count: 4,
      time: '25分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '的*2',
      getprice: 4.50,
      count: 1,
      time: '29分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '小*我',
      getprice: 8.69,
      count: 2,
      time: '40分钟前',
    ),

    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.49,
      count: 1,
      time: '41分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 8.49,
      count: 2,
      time: '41分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.49,
      count: 1,
      time: '43分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '匿*户',
      getprice: 4.50,
      count: 1,
      time: '46分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/comm7.jpg',
      name: '疯*疯',
      getprice: 4.50,
      count: 1,
      time: '51分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '狂*狂',
      getprice: 4.50,
      count: 1,
      time: '52分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '星*星',
      getprice: 4.50,
      count: 1,
      time: '52分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '期*期',
      getprice: 4.50,
      count: 1,
      time: '54分钟前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '四*四',
      getprice: 4.50,
      count: 1,
      time: '1小时前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: 'V*V',
      getprice: 4.49,
      count: 1,
      time: '1小时前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '我*我',
      getprice: 4.49,
      count: 1,
      time: '1小时前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '5*5',
      getprice: 4.49,
      count: 1,
      time: '1小时前',
    ),
    _RecentBuy(
      images: 'assets/images/none.png',
      name: '0*0',
      getprice: 4.49,
      count: 1,
      time: '1小时前',
    ),
  ];

  List<Widget> smallline(){
    List<Widget> ret = [];
    for(var i=0; i<4;i++){
      ret.add(
        Padding(
          padding: EdgeInsets.fromLTRB(20, 10, 20, 10),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Row(
                children: [
                  CircleAvatar(
                    backgroundImage: AssetImage(rec[i].images),
                    radius: 15,
                  ),
                  Text(
                    "   ${rec[i].name}",
                  ),
                ],
              ),
              Text(
                "¥${rec[i].getprice}",
              ),
              Text(
                "${rec[i].time}",
              ),
            ],
          ),
        )
      );
    }
    return ret;
  }


  Widget bigline(){
    List<Widget> ret = [];
    for(var i=0; i<rec.length;i++){
      ret.add(
        Padding(
          padding: EdgeInsets.fromLTRB(20, 10, 20, 10),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Row(
                children: [
                  CircleAvatar(
                    backgroundImage: AssetImage(rec[i].images),
                    radius: 15,
                  ),
                  Text(
                    "   ${rec[i].name}",
                  ),
                ],
              ),
              Text(
                "¥${rec[i].getprice}",
              ),
              Text(
                "${rec[i].time}",
              ),
            ],
          ),
        )
      );
    }
    return Column(
      children: ret,
    );
  }


  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.fromLTRB(20,70,20,90),
      child: Card(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadiusDirectional.circular(10)),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Padding(
              padding: EdgeInsets.fromLTRB(15,20,15,20),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    "最近购买(4.7万+)",
                    style: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.w700
                    ),
                  ),
                  Row(
                    children: [
                      Text(
                        "全部   ",
                        style: TextStyle(
                          color: Colors.grey
                        ),
                      ),
                      ToggleRotate(
                        curve: Curves.decelerate,
                        rad: pi/2,
                        durationMs: 400,//动画时长
                        clockwise: false, //是否是顺时针
                        child: Icon(Icons.code,size: 30,color: Colors.grey),
                        onTap: _togglePanel,
                      ),
                    ],
                  ),
                ],
              ),
            ),
            _buildPanel()
          ],
        ),
      ),
    );
  }

  void _togglePanel() {
    setState(() {
      _crossFadeState =
      !isFirst ? CrossFadeState.showFirst : CrossFadeState.showSecond;
    });
  }

  Widget _buildPanel() => AnimatedCrossFade(
    firstCurve: Curves.easeInCirc,
    secondCurve: Curves.easeInToLinear,
    firstChild: Container(
      // color: Colors.cyan,
      child: Column(
        children: smallline(),
      ),
    ),
    secondChild:
    // Container(
    //   color: Colors.blue,
    //   height: 400,
    //   child: Column(
    //     children: smallline(),
    //   ),
    // ),
    Container(
      height: 540,
      child: CustomScrollView(
        scrollDirection: Axis.vertical,
        slivers: [
          SliverList(
              delegate: SliverChildListDelegate(
                  [bigline()]
              )
          )
        ],
      ),
    ),
    duration: Duration(milliseconds: 400),
    crossFadeState: _crossFadeState,
  );
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Flutter移动应用开发 - 商品界面 的相关文章

随机推荐

  • DB2数据库连接(jdbc连接)encoding not supported

    在进行db2数据库连接过程中发现了一些问题 报如下错误 com ibm db2 jcc b DisconnectException encoding not supported 该问题的出现是IBM JDK和sun JDK之间相互不支持 解
  • 【滤波器】7. 带通滤波器

    将低通滤波器和高通滤波器串联 如下图所示 就可得到带通滤波器 设低通滤波器的截止频率为 f p 1 f p1 fp1 高通滤波器的截止频率为
  • Spark 的Shuffle过程详解

    一 Shuffle的作用是什么 Shuffle的中文解释为 洗牌操作 可以理解成将集群中所有节点上的数据进行重新整合分类的过程 其思想来源于hadoop的mapReduce Shuffle是连接map阶段和reduce阶段的桥梁 由于分布式
  • if与if else与if else if else之间的用法与区别(C++)

    1 if 满足这个条件 执行语句操作 不满足条件 不操作 结构 if 条件 语句 2 if else 满足这个条件 执行语句1操作 不满足 执行语句2操作 结构 if 条件 语句1 else 语句2 备注 通俗说就是两者取其一 注意 if
  • 简单方法恢复linux以及windows启动引导

    1 恢复linux启动引导 以ubuntu为例 很多小孩喜欢用wubi装linux 确实这种方法比较简单 比较安全 但是这种安装方法是基于windows的 也就是说 如果windows挂了 比如重装了或还原了 那么原来安装的linux也就没
  • cvFindContours函数使用

    CV IMPL intcvFindContours void img CvMemStorage storage CvSeq firstContour int cntHeaderSize int mode int method CvPoint
  • Servlet 实现重定向几种方法

    servlet重定向 在servlet JSP编程中 服务器端重定向可以通过下面两个方法来实现 1 运用javax servlet RequestDispatcher接口的forward方法 2 或者运用javax servlet http
  • 基于FPGA的串口通讯设计与实现

    繁體 基于FPGA的串口通讯设计与实现 日期 2012 03 26 来源 作者 字体 大 中 小 随着多微机系统的应用和微机网络的发展 通信功能越来起重要 串行通信是在一根传输线上一位一位传送信息 这根线既作数据线又作联络线 串行通信作为一
  • uniapp实现支付功能 和 可视化拖拽工具

    1 支付功能 https blog csdn net weixin 37787674 article details 103012041 2 分享一个 uniapp uview ui 可视化 完全自由拖拽 一键生成flex代码网站 http
  • 你必须知道的495个C语言问题整理三

    1 为什么大家都说不要使用gets 跟fgets 不同 gets 不能被告知输入缓冲区的大小 因此不能避免缓冲区的溢出 标准库的fgets 函数对gets 作了很大的改进 尽管它仍然不完善 2 fgetops fsetops 和ftell
  • python入门笔记——函数①

    python入门笔记 函数 def function 定义一个名为function的函数 定义函数时用下面来解释该函数的用处 这个函数是用来重复输出4次 你好 的 return for i in range 1 5 print 你好 pas
  • SSL、TLS、HTTPS的关系

    SSL TLS HTTPS的关系 SSL Secure Sockets Layer 安全套接字协议 TLS Transport Layer Security 传输层安全性协议 TLS是SSL的升级版 两者几乎是一样的 HTTPS Hyper
  • Java 多线程 -- 从入门到精通

    持续更新中 欢迎收藏 关注 以便查看后续 Java 多线程 从入门到精通 Java线程与线程的区别 多线程的实现方法 Thread中start和run方法的区别 Thread和Runnable的关系 使用Callable和Future创建线
  • 神经网络的三种可视化方法——使用keras和MXNet(gluon)实现

    神经网络的三种可视化方法 用keras和MXNet gluon 实现 目录 神经网络的三种可视化方法 用keras和MXNet gluon 实现 概述 keras实现 keras特征图可视化 keras可视化滤波器 卷积核 的最大响应图 k
  • 清除内存/缓存

    1 查看缓存 free h 2 清理缓存 sync echo 3 gt proc sys vm drop caches 我们在清理缓存前应该先sync下 因为系统在操作的过程当中 会把你的操作到的文件资料先保存到buffer中去 因为怕你在
  • 刷脸支付产业也迎来新的跃升时刻

    2019年刷脸支付异常火爆 一种创投人青睐有加 纷纷入局 一项新事物从诞生到发展总要经历很多波折 对于刷脸支付行业来说 更是如此 2020年初新冠疫情肆虐 人们被围在城里 圈在村里 各类实体门店到店率急剧下降 刷脸支付一度遇冷 而在疫情期间
  • C++星罗万象时钟罗盘

    编写思路 该程序的难点在于字符串的书写角度 理解了这个就已经理解了这个程序的核心内容 需要注意的地方是这个程序的每一圈代表一个 for 循环 我开始写的时候认为一个 for 循环嵌套一个 for 循环 但是后面我发现 for 循环嵌套的太多
  • vue-chat项目之重构与体验优化

    前言 vue chat 也就是我的几个月之前写的一个基于vue的实时聊天项目 到目前为止已经快满400star了 注册量也已经超过了1700 消息量达2000 由于一直在实习 没有时间对它频繁地更新 趁着这个国庆 对他进行了一次重构 希望我
  • 国内研发!适用于安卓应用程序的 Word文档功能开发组件来啦!

    你是否在寻找一款工具能够在Android应用程序中的Word文档管理的开发工具 那么 好消息来啦 Spire系列文档开发组件又添新成员 专门用于在 Android 手机应用程序中创建 读取 操作和转换 Word 文档 Spire Doc f
  • Flutter移动应用开发 - 商品界面

    目录 0 项目简介 1 效果展示 2 代码 commodity dart CardTry dart recentPurchase dart 0 项目简介 项目想法脱胎于2023年服务外包大赛A18题 随手买 详情 整个APP思路如下 这篇博