flutter 仿商场_首页

flutter + getx 搭建项目架构

pub global activete get_cli flutter pub global activete get_cli

使用get_cli 命令行 1.初始化项目 get init

2.创建页面命令 get create page:tab

3.创建底部导航栏 tab

js 复制代码
import 'package:flutter/material.dart';

import 'package:get/get.dart';

import '../controllers/tabs_controller.dart';

class TabsView extends GetView<TabsController> {
  const TabsView({super.key});
  
  @override
  Widget build(BuildContext context) {
    // 使用 Obx 包装整个界面以响应状态变化
    return Obx(() => Scaffold(
      // 主体部分使用 PageView 实现页面切换
      body: PageView(
        controller: controller.pageController, // 使用控制器中的 PageController
        physics: const NeverScrollableScrollPhysics(), // 禁止用户手动滑动页面,只能通过底部导航栏切换
        children: controller.pages, // 页面列表,来自控制器中的 pages 数组
        // 当页面改变时的回调函数
        onPageChanged: (index) {
          controller.setCurrentIndex(index); // 更新当前选中的索引
        },
      ),
      // 底部导航栏
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.red, // 选中项的颜色
        currentIndex: controller.currentIndex.value, // 当前选中的索引
        type: BottomNavigationBarType.fixed, // 固定类型,当底部有4个或以上菜单时需要配置此参数
        // 点击底部导航项时的回调
        onTap: (index) {
          controller.setCurrentIndex(index); // 更新控制器中的当前索引
          controller.pageController.jumpToPage(index); // 跳转到对应的页面
        },
        // 底部导航项列表
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home), // 图标
            label: '首页', // 标签文字
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.category), // 图标
            label: '分类', // 标签文字
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.room_service), // 图标
            label: '服务', // 标签文字
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.shopping_cart), // 图标
            label: '购物车', // 标签文字
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person), // 图标
            label: '用户', // 标签文字
          ),
        ]
      )
      ),
    );
  }
}

不同终端屏幕适配方案,以及自定义适配类

flutter_screenutil 屏幕适配方案,用于屏幕大小和字体大小的插件,

自定义适配类

js 复制代码
import 'package:flutter_screenutil/flutter_screenutil.dart';

class ScreenAdapter{

  static width(num v){
    return v.w;
  }
  static height(num v){
    return v.h;
  }
  static fontSize(num v){
    return v.sp;
  }
  static getScreenWidth(){
      // return  ScreenUtil().screenWidth;
      return 1.sw;
  }
  static getScreenHeight  (){
      // return  ScreenUtil().screenHeight;
      return 1.sh;
  }
  static getStatusBarHeight(){
       return  ScreenUtil().statusBarHeight;
      //return 1.sh;
  }

}

在main.dart 中使用

js 复制代码
// 导入 Flutter Material Design 组件库
import 'package:flutter/material.dart';
// 导入系统 UI 控制包,用于设置状态栏样式
import 'package:flutter/services.dart';
// 导入屏幕适配工具包,用于响应式设计
import 'package:flutter_screenutil/flutter_screenutil.dart';

// 导入 GetX 状态管理包
import 'package:get/get.dart';

// 导入应用路由页面配置
import 'app/routes/app_pages.dart';

/// 应用程序入口点
void main() {
  // 配置透明的状态栏样式
  SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle(
    statusBarColor: Colors.transparent  // 设置状态栏背景为透明
  );
  // 应用系统 UI 样式配置
  SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
  
  runApp(
    // 屏幕适配初始化包装器
    ScreenUtilInit(
      designSize: const Size(1080, 2400), // 设计稿尺寸,宽度1080px,高度2400px
      minTextAdapt: true,                 // 最小文本适配,确保文本在不同屏幕密度下可读性
      splitScreenMode: true,              // 分屏模式支持
      builder: (context, child) {
        // GetX Material 应用程序主入口
        return GetMaterialApp(
          title: "商场",              // 应用标题
          initialRoute: AppPages.INITIAL,    // 初始路由页面,从 AppPages 获取
          getPages: AppPages.routes,         // 路由页面列表,从 AppPages 获取
        );
      },
    )
    
  );
}

透明状态栏,透明顶部导航栏,首页导航布局

flutter 透明状态栏设置

js 复制代码
void main() {
  // 配置透明的状态栏样式
  SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle(
    statusBarColor: Colors.transparent  // 设置状态栏背景为透明
  );
  // 应用系统 UI 样式配置
  SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);

flutter 透明顶部导航 通过backgroudcolor:Colors.transparent 结合 elevation:0 可以实现透明导航,要实现透明浮动导航的话需要使用定位

js 复制代码
 // 顶部导航栏组件
  Widget _appBar() {
    return Positioned(
      top: 0,
      left: 0,
      right: 0,
      child: Obx(() => AppBar(
            // 根据 flag 状态控制左侧图标显示
            leading: controller.flag.value
                ? const Text("")  // flag 为 true 时显示空白占位
                : const Icon(
                    ItyingFonts.xiaomi,  // flag 为 false 时显示小米图标
                    color: Colors.white,
                  ),
            // 根据 flag 状态调整 leading 宽度
            leadingWidth: controller.flag.value
                ? ScreenAdapter.width(40)     // flag 为 true 时较窄
                : ScreenAdapter.width(140),   // flag 为 false 时较宽
            // 中间搜索框组件
            title: InkWell(
              child: AnimatedContainer(
                // 根据 flag 状态动态调整搜索框宽度
                width: controller.flag.value
                    ? ScreenAdapter.width(800)   // flag 为 true 时较宽
                    : ScreenAdapter.width(620),  // flag 为 false 时较窄
                height: ScreenAdapter.height(96),  // 固定高度
                decoration: BoxDecoration(
                  color: const Color.fromARGB(230, 252, 243, 236), // 搜索框背景色
                  borderRadius: BorderRadius.circular(30),         // 圆角
                ),
                duration: const Duration(milliseconds: 600),  // 动画持续时间
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Padding(
                      padding: EdgeInsets.fromLTRB(
                          ScreenAdapter.width(34), 0, ScreenAdapter.width(10), 0),
                      child: const Icon(Icons.search),  // 搜索图标
                    ),
                    Text("手机",
                        style: TextStyle(
                            color: Colors.black54,  // 文字颜色
                            fontSize: ScreenAdapter.fontSize(32)))  // 字体大小
                  ],
                ),
              ),
              // 点击搜索框跳转到搜索页面
              onTap: (){
                Get.toNamed("/search");
              },
            ),
            centerTitle: true,  // 标题居中
            // 根据 flag 状态设置 AppBar 背景色
            backgroundColor:
                controller.flag.value ? Colors.white : Colors.transparent,
            elevation: 0,  // 去除阴影
            // 右侧操作按钮
            actions: [
              IconButton(
                  onPressed: () {},  // 二维码按钮点击事件
                  icon: Icon(
                    Icons.qr_code,  // 二维码图标
                    // 根据 flag 状态设置图标颜色
                    color:
                        controller.flag.value ? Colors.black87 : Colors.white,
                  )),
              IconButton(
                  onPressed: () {},  // 消息按钮点击事件
                  icon: Icon(Icons.message,  // 消息图标
                      // 根据 flag 状态设置图标颜色
                      color: controller.flag.value
                          ? Colors.black87
                          : Colors.white))
            ],
          )),
    );
  }

在controller中

js 复制代码
 /// 状态标志位,用于控制UI元素的显示状态(如顶部导航栏透明度等)
  RxBool flag = false.obs;
/// 滚动控制器,用于监听首页滚动事件
  final ScrollController scrollController = ScrollController();
/// 控制器初始化方法
  /// 在这里设置滚动监听器并加载所有首页数据
  @override
  void onInit() {
    super.onInit();
    
    // 添加滚动监听器,用于根据滚动位置改变UI状态
    scrollController.addListener(() {
      // 当滚动位置超过10像素时,改变flag状态为true
      if (scrollController.position.pixels > 10) {
        if (flag.value == false) {
          print("position");
          flag.value = true;
          update(); // 更新UI
        }
      }
      
      // 当滚动位置回到10像素以内时,改变flag状态为false
      if (scrollController.position.pixels < 10) {
        if (flag.value == true) {
          print("position");
          flag.value = false;
          update(); // 更新UI
        }
      }
    });

轮播图 flutter_swiper_view: ^1.1.8 # 轮播

js 复制代码
// 顶部主轮播图
            SizedBox(
              width: ScreenAdapter.width(1080),
              height: ScreenAdapter.height(682),
              child: Obx(() => Swiper(
                itemBuilder: (context, index) {
                  // 构造轮播图图片 URL
                  String picUrl="https://xiaomi.itying.com/${controller.swiperList[index].pic}";
                  return Image.network(
                   picUrl.replaceAll("\\", "/"),  // 处理路径分隔符
                    fit: BoxFit.fill,             // 填充模式
                  );
                },
                itemCount:controller.swiperList.length,  // 轮播图数量
                pagination: const SwiperPagination(
                  builder: SwiperPagination.rect  // 矩形分页指示器
                ),
                autoplay:true,  // 自动播放
                loop:true,      // 循环播放
              )),
            ),
js 复制代码
{
  "result": [
    {
      "_id": "6332b1beb1ba6105c889734f",
      "title": "redmi k40S",
      "status": "1",
      "pic": "public\\upload\\woudSGBv9MYfp01_sEILWG3s.png",
      "url": "12",
      "position": 2
    },
    {
      "_id": "6332b1dcb1ba6105c8897350",
      "title": "MIX Fold2",
      "status": "1",
      "pic": "public\\upload\\fWneGqwSQaBpJMt2WYLpbG8E.png",
      "url": "23",
      "position": 2
    },
    {
      "_id": "6332b1f5b1ba6105c8897351",
      "title": "全能扫拖机器人",
      "status": "1",
      "pic": "public\\upload\\K4-G_kiT90fftfBo0tZpYSSC.png",
      "url": "23",
      "position": 2
    }
  ]
}

json 字符串生成模型类 autocode.icu/jsontodart

jsontodart.com/

js 复制代码
class FocusModel {
  List<FocusItemModel>? result;

  FocusModel({this.result});

  FocusModel.fromJson(Map<String, dynamic> json) {
    if (json['result'] != null) {
      result = <FocusItemModel>[];
      json['result'].forEach((v) {
        result?.add(FocusItemModel.fromJson(v));
      });
    }
  }

  Map<String, dynamic> toJson() {
    final data = <String, dynamic>{};
    if (result != null) {
      data['result'] = result?.map((v) => v.toJson()).toList();
    }
    return data;
  }
}

class FocusItemModel {
  String? sId;
  String? title;
  String? status;
  String? pic;
  String? url;

  FocusItemModel({this.sId, this.title, this.status, this.pic, this.url});

  FocusItemModel.fromJson(Map<String, dynamic> json) {
    sId = json['_id'];
    title = json['title'];
    status = json['status'];
    pic = json['pic'];
    url = json['url'];
  }

  Map<String, dynamic> toJson() {
    final data = <String, dynamic>{};
    data['_id'] = sId;
    data['title'] = title;
    data['status'] = status;
    data['pic'] = pic;
    data['url'] = url;
    return data;
  }
}

在controller 请求

js 复制代码
 /// 获取热销轮播图数据
  /// 通过position=2参数获取特定位置的轮播图
  /// API地址: https://miapp.itying.com/api/focus?position=2
  getSellingSwiperData() async {
    try {
      // 发起网络请求获取热销轮播图数据
      var response = await Dio().get("https://miapp.itying.com/api/focus?position=2");
      
      // 将返回的JSON数据转换为FocusModel对象
      var sellingSwiper = FocusModel.fromJson(response.data);
      
      // 更新最佳销售轮播图数据列表
      bestSellingSwiperList.value = sellingSwiper.result!;
      
      // 通知UI更新
      update();
    } catch (e) {
      // 错误处理
      print("获取热销轮播图数据失败: $e");
    }
  }

分类导航

相关推荐
少卿3 小时前
react-native图标替换
前端·react native
熊猫钓鱼>_>3 小时前
TypeScript前端架构与开发技巧深度解析:从工程化到性能优化的完整实践
前端·javascript·typescript
JYeontu3 小时前
肉眼难以分辨 UI 是否对齐,写个插件来辅助
前端·javascript
fox_3 小时前
别再踩坑!JavaScript的this关键字,一次性讲透其“变脸”真相
前端·javascript
盛夏绽放3 小时前
uni-app Vue 项目的规范目录结构全解
前端·vue.js·uni-app
少卿4 小时前
React Native Vector Icons 安装指南
前端·react native
国家不保护废物4 小时前
Vue组件通信全攻略:从父子传到事件总线,玩转组件数据流!
前端·vue.js
写不来代码的草莓熊4 小时前
vue前端面试题——记录一次面试当中遇到的题(9)
前端·javascript·vue.js
JinSo5 小时前
pnpm monorepo 联调:告别 --global 参数
前端·github·代码规范