Flutter:布局:NestedScrollView+SliverAppBar+SmartRefresher(分页),实现顶部背景图+导航栏渐变+分页列表


dart 复制代码
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:ayidaojia/common/index.dart';
import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';
import 'index.dart';

class TeamPage extends GetView<TeamController> {
  const TeamPage({super.key});

  // 头部背景图(放在 SliverAppBar 的 flexibleSpace 中)
  Widget _buildHeaderBackground() {
    return Stack(
      children: [
        // 背景图
        Positioned(
          left: 0,
          right: 0,
          top: 0,
          child: ImgWidget(
            path: 'assets/img/team1.png', 
            width: 750.w, 
            height: 496.w,
            fit: BoxFit.cover,
          ),
        ),
        // 自定义内容(可选)
        Positioned(
          left: 0,
          right: 0,
          bottom: 0,
          child: <Widget>[
            TextWidget.body('自定义顶部内容1', size: 32.sp, weight: FontWeight.w600,),
            TextWidget.body('自定义顶部内容2', size: 32.sp, weight: FontWeight.w600,),
            TextWidget.body('自定义顶部内容3', size: 32.sp, weight: FontWeight.w600,),
          ].toColumn(),
        ),
      ],
    );
  }

  // 列表项
  Widget _buildListItem(int index) {
    return Container(
      padding: EdgeInsets.symmetric(vertical: 20.w),
      child: TextWidget.body(
        'item = $index', 
        size: 30.sp,
        color: Colors.black,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    // 状态栏高度
    final double statusBarHeight = MediaQuery.of(context).padding.top;
    // 导航栏高度
    final double navBarHeight = 88.w;
    // SliverAppBar 展开后的总高度(状态栏 + 导航栏 + 背景图高度)
    final double expandedHeight = statusBarHeight + navBarHeight + 400.w;

    return GetBuilder<TeamController>(
      init: TeamController(),
      id: "team",
      builder: (_) {
        return Scaffold(
          backgroundColor: AppTheme.pageBgColor,
          body: NestedScrollView(
            controller: controller.scrollController,
            headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
              return [
                SliverAppBar(
                  expandedHeight: expandedHeight,
                  floating: false,
                  pinned: true,
                  backgroundColor: Colors.white.withOpacity(controller.opacity),
                  elevation: 0,
                  // 左侧返回按钮
                  leading: IconButton(
                    icon: Icon(
                      Icons.arrow_back_ios,
                      color: AppTheme.colorfff,
                      size: 40.w,
                    ),
                    onPressed: () => Get.back(),
                  ),
                  // 标题(跟随透明度渐变显示)
                  title:TextWidget.body(
                    '我的粉丝', 
                    size: 32.sp, 
                    color: AppTheme.colorfff, 
                    weight: FontWeight.w600,
                  ),
                  // 背景图区域
                  flexibleSpace: FlexibleSpaceBar(
                    background: _buildHeaderBackground(),
                    // 渐变遮罩(可选,让导航栏过渡更自然)
                    collapseMode: CollapseMode.pin,
                  ),
                ),
              ];
            },
            // body 部分使用 SmartRefresher
            body: SmartRefresher(
              controller: controller.refreshController,
              enablePullUp: true,
              onRefresh: controller.onRefresh,
              onLoading: controller.onLoading,
              header: const SmartRefresherHeaderWidget(),
              footer: SmartRefresherFooterWidget(textColor: AppTheme.colorfff),
              child: ListView.separated(
                padding: EdgeInsets.symmetric(horizontal: 30.w, vertical: 20.w),
                itemCount: 30,
                itemBuilder: (context, index) => _buildListItem(index),
                separatorBuilder: (context, index) => Divider(
                  height: 1,
                  color: AppTheme.dividerColor,
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}
dart 复制代码
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';

class TeamController extends GetxController {
  TeamController();
  List items = [];
  // 滚动控制器
  final ScrollController scrollController = ScrollController();
  
  // 渐变系数 0-1
  double opacity = 0.0;
  
  // 滚动开始变化的位置
  final double scrollStartPoint = 20.0;
  
  // 滚动结束变化的位置
  final double scrollEndPoint = 120.0;

  _initData() {
    update(["team"]);
  }


  @override
  void onInit() {
    super.onInit();
    // 监听滚动
    scrollController.addListener(() {
      // 计算 0-1 之间的渐变系数
      double newOpacity;
      
      if (scrollController.offset <= scrollStartPoint) {
        // 开始点之前完全透明
        newOpacity = 0.0;
      } else if (scrollController.offset >= scrollEndPoint) {
        // 结束点之后完全不透明
        newOpacity = 1.0;
      } else {
        // 在开始点和结束点之间线性计算
        newOpacity = (scrollController.offset - scrollStartPoint) /  (scrollEndPoint - scrollStartPoint);
        
        // 确保值在0-1范围内并保留更多小数位精度
        newOpacity = double.parse(newOpacity.toStringAsFixed(3)).clamp(0.0, 1.0);
      }
      
      // 只有当透明度变化时才更新UI
      if ((opacity - newOpacity).abs() > 0.001) {
        opacity = newOpacity;
        update(["team"]);
      }
    });
    _initData();
  }

  @override
  void onReady() {
    super.onReady();
    _initData();
  }

  @override
  void onClose() {
    super.onClose();
    scrollController.dispose();
    refreshController.dispose();
  }



  /*
  * 分页
  * refreshController:分页控制器
  * _page:分页
  * _limit:每页条数
  * _loadNewsSell:拉取数据(是否刷新)
  * onLoading:上拉加载新商品
  * onRefresh:下拉刷新
  * */
  final RefreshController refreshController = RefreshController(
    initialRefresh: true,
  );
  // int _page = 1;
  Future<bool> _loadNewsSell(bool isRefresh) async {
    return false;
    // var result = await HomeApi.noticesList(PageListReq(
    //   pageNo:isRefresh ? 1:_page,
    //   pageSize:20
    // ));
    // if(isRefresh){
    //   _page = 1;
    //   items.clear();
    // }
    // if(result.isNotEmpty){
    //   _page++;
    //   items.addAll(result);
    // }
    // // 是否是空
    // return result.isEmpty;
  }

  // 上拉载入新商品
  void onLoading() async {
    if (items.isNotEmpty) {
      try {
        // 拉取数据是否为空 ? 设置暂无数据 : 加载完成
        var isEmpty = await _loadNewsSell(false);
        isEmpty
            ? refreshController.loadNoData()
            : refreshController.loadComplete();
      } catch (e) {
        refreshController.loadFailed(); // 加载失败
      }
    } else {
      refreshController.loadNoData(); // 设置无数据
    }
    update(["team"]);
  }

  // 下拉刷新
  void onRefresh() async {
    try {
      await _loadNewsSell(true);
      refreshController.refreshCompleted();
      // 重置加载状态,确保可以继续上拉加载
      refreshController.resetNoData();
    } catch (e) {
      refreshController.refreshFailed();
    }
    update(["team"]);
  }

}
相关推荐
西西学代码1 小时前
flutter---进度条
前端·javascript·flutter
阿桂有点桂1 小时前
Flutter使用VS Code打包app
vscode·flutter·安卓
心随雨下2 小时前
Flutter加载自定义CSS样式文件方法
前端·css·flutter
晚霞的不甘2 小时前
调试、测试与持续交付:构建面向 OpenHarmony 的 Flutter 工程化体系
flutter
kirk_wang2 小时前
鸿蒙分布式能力与Flutter集成:Flutter三方库鸿蒙适配实践
flutter·移动开发·跨平台·arkts·鸿蒙
克喵的水银蛇12 小时前
Flutter 动画实战:基础动画 + Hero 动画 + 自定义动画
flutter
松☆14 小时前
Flutter + OpenHarmony 实战:构建离线优先的跨设备笔记应用
笔记·flutter
_大学牲16 小时前
Flutter 勇闯2D像素游戏之路(一):一个 Hero 的诞生
flutter·游戏·游戏开发
kirk_wang16 小时前
Flutter插件在鸿蒙端的开发与部署:跨生态桥梁的架构与实现
flutter·移动开发·跨平台·arkts·鸿蒙