Flutter:导航固定背景图,滚动时导航颜色渐变



view

js 复制代码
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
import 'package:happy/common/index.dart';
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';

import 'index.dart';

class NoticeDetailPage extends GetView<NoticeDetailController> {
  const NoticeDetailPage({super.key});

  // 头部
  Widget _buildHeader() {
    return <Widget>[
      TextWidget.body(
        controller.title,
        size: 28.sp,
        weight: FontWeight.w600,
        color: AppTheme.color000,
      ),
      SizedBox(
        height: 20.w,
      ),
      <Widget>[
        Icon(
          Icons.access_time_outlined,
          size: 28.sp,
          color: AppTheme.color000,
        ),
        SizedBox(
          width: 10.w,
        ),
        TextWidget.body(
          controller.time,
          color: AppTheme.color666,
        ),
      ].toRow(mainAxisAlignment: MainAxisAlignment.center)
    ]
        .toColumn(crossAxisAlignment: CrossAxisAlignment.center)
        .paddingOnly(top: 20.w, bottom: 20.w)
        .card(color: AppTheme.dividerColor2);
  }
  // 内容详情
  Widget _buildContent() {
    return <Widget>[
      HtmlWidget(
        controller.content,
        // 设置渲染模式
        renderMode: RenderMode.column,
        // 设置文本样式
        textStyle: TextStyle(
          fontSize: 28.sp,
          color: Colors.white,
          height: 1.5,
        ),
      ),
    ]
        .toColumn(crossAxisAlignment: CrossAxisAlignment.center)
        .paddingAll(30.w)
        .card(color: AppTheme.dividerColor2);
  }

  // 主视图
  Widget _buildView() {
    // 系统状态栏占位高度
    double systemStatusBarHeight = MediaQuery.of(Get.context!).padding.top;
    // 总占位高度
    double systemTotalHeight = 44 + systemStatusBarHeight;
    return SingleChildScrollView(
      controller: controller.scrollController,
      child: <Widget>[
        SizedBox(height: systemTotalHeight,),
        _buildHeader(),
        SizedBox(height: 20.w,),
        _buildContent(),
      ].toColumn(crossAxisAlignment: CrossAxisAlignment.start).paddingHorizontal(30.w),
    );
  }

  @override
  Widget build(BuildContext context) {
    return GetBuilder<NoticeDetailController>(
      init: NoticeDetailController(),
      id: "notice_detail",
      builder: (_) {
        return Scaffold(
          backgroundColor: AppTheme.pageBgColor,
          body: <Widget>[
            ImgWidget(path: 'assets/images/home11.png',width: 750.w,height: 500.w,),
            _buildView().expanded(),
            // 只刷新导航栏
            GetBuilder<NoticeDetailController>(
              id: "notice_detail_navbar",
              builder: (_) => AnimatedContainer(
                duration: const Duration(milliseconds: 100),
                color: Colors.white.withOpacity(controller.opacity),
                child: TDNavBar(
                  height: 44,
                  title: '消息详情',
                  titleColor: AppTheme.color000,
                  titleFontWeight: FontWeight.w600,
                  backgroundColor: Colors.transparent,
                  screenAdaptation: true,
                  useDefaultBack: false,
                  leftBarItems: [
                    TDNavBarItem(
                      icon: TDIcons.chevron_left,
                      iconSize: 24,
                      iconColor: AppTheme.color000,
                      action: () {
                        Get.back();
                      }
                    ),
                  ],
                ),
              ),
            ),
          ].toStack(),
        );
      },
    );
  }
}

controller

js 复制代码
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:happy/common/index.dart';

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

  
  int id = 0;
  String title = '';
  String content = '测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试';
  String time = '';


  _initData() async {
    id = Get.arguments['id'];
    var res = await HomeApi.noticeDetail(id);
    content = res.content ?? '';
    title = res.title ?? '';
    time = res.updatedAt ?? '';
    update(["notice_detail"]);
  }


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

  @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(["notice_detail_navbar"]);
      }
    });
    _initData();
  }

  @override
  void onClose() {
    scrollController.dispose();
    super.onClose();
  }
}
相关推荐
leobertlan35 分钟前
痛苦系列 | DSP-02 频域切片:DTFT与DFT的探索
android·后端
用户117953654781 小时前
TRAE vs Claude Code 性能对比:从代码生成、多文件重构到底层吞吐,谁更适合你的日常开发?
javascript
陆断枫1 小时前
Flutter 3.47 动画引擎盘点:Impeller 全平台,和那些"新特性"
flutter
leobertlan2 小时前
痛苦系列 | DSP-01 从连续到离散:DSP基础与采样
android·后端
kill5222 小时前
useRef
前端·javascript·react.js
程序员老刘2 小时前
Flet 1.0 思路很好,可惜来晚了
flutter·ai编程·客户端
米多科技2 小时前
我做了一个 3D 虚拟世界基底,在上面再搭 3D 世界或元宇宙,事半功倍
javascript·人工智能·github
霍营_托尼3 小时前
纯前端播 40GB 本地视频?我把浏览器改造成了「磁盘流式」播放器
前端·javascript·音视频开发
西柚小萌新3 小时前
【LLM&&AI应用开发 八股文】--4.2.Agent智能体(中)
前端·javascript·react.js
事圆则缓4 小时前
Android 视频压缩上传实战:从 content Uri、转码到断点续传
android