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();
  }
}
相关推荐
前端小巷子33 分钟前
Webpack 5模块联邦
前端·javascript·面试
晓得迷路了37 分钟前
栗子前端技术周刊第 91 期 - 新版 React Compiler 文档、2025 HTML 状态调查、Bun v1.2.19...
前端·javascript·react.js
江城开朗的豌豆1 小时前
前端路由傻傻分不清?route和router的区别,看完这篇别再搞混了!
前端·javascript·vue.js
LBJ辉3 小时前
2. Webpack 高级配置
前端·javascript·webpack
zepcjsj08017 小时前
简单实现支付密码的页面及输入效果
android
小阳睡不醒8 小时前
小白成长之路-部署Zabbix7(二)
android·运维
灵感__idea9 小时前
JavaScript高级程序设计(第5版):好的编程就是掌控感
前端·javascript·程序员
mmoyula10 小时前
【RK3568 PWM 子系统(SG90)驱动开发详解】
android·linux·驱动开发
ITfeib12 小时前
Flutter基础
flutter
hui函数12 小时前
掌握JavaScript函数封装与作用域
前端·javascript