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();
  }
}
相关推荐
硬件学长森哥10 分钟前
Android影像基础--cameraAPI2核心流程
android·计算机视觉
Icoolkj3 小时前
VuePress 与 VitePress 深度对比:特性、差异与选型指南
前端·javascript·vue.js
浮生若茶80884 小时前
Flutter环境搭建全攻略之-Macos环境搭建
flutter·macos
^Rocky4 小时前
JavaScript性能优化实战
开发语言·javascript·性能优化
西陵5 小时前
Nx带来极致的前端开发体验——任务编排
前端·javascript·架构
前行的小黑炭5 小时前
Android 协程的使用:结合一个环境噪音检查功能的例子来玩玩
android·java·kotlin
阿华的代码王国5 小时前
【Android】内外部存储的读写
android·内外存储的读写
笑鸿的学习笔记5 小时前
JavaScript笔记之JS 和 HTML5 的关系
javascript·笔记·html5
萌萌哒草头将军7 小时前
10个 ES2025 新特性速览!🚀🚀🚀
前端·javascript·vue.js
gnip7 小时前
http缓存
前端·javascript