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"]);
  }

}
相关推荐
恋猫de小郭1 小时前
Android 禁止侧载将正式实施,需要等待 24 小时冷静期
android·flutter·harmonyos
FFF-X1 小时前
解决 Flutter Gradle 下载报错:修改默认 distributionUrl
flutter
程序员Ctrl喵21 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难1 天前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡1 天前
flutter列表中实现置顶动画
flutter
始持1 天前
第十二讲 风格与主题统一
前端·flutter
始持1 天前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持1 天前
第十三讲 异步操作与异步构建
前端·flutter
新镜1 天前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴1 天前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter