flutter实现顶部吸附效果

2,327 阅读2分钟

前言

flutter实现顶部吸附效果,和大家一起学习探讨。

先上效果图:

概述

CustomScrollView是Flutter提供的可以用来自定义滚动效果的组件,它可以像胶水一样将多个Sliver粘合在一起。
NestedScrollView 与 ScrollView 的区别就在于 NestedScrollView 支持 嵌套滑动,
无论是作为父控件还是子控件,嵌套滑动都支持,且默认开启。
        body:NestedScrollView(
          headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
            return <Widget>[
              SliverPersistentHeader(
                pinned: true,
                delegate: SliverCustomHeaderDelegate(
                    context: context,
                    coverImgUrl: 'assets/images/sliver_bg.png',
                    collapsedHeight: ScreenUtil().setHeight(90),
                    expandedHeight: ScreenUtil().setHeight(280),
                    title: '吸附效果测试',
                    popCallBack:callback,
                    paddingTop: 0),
              )
            ];
          },

此demo用到了Sliver家族的SliverPersistentHeader,
SliverPersistentHeader最重要的一个属性是SliverPersistentHeaderDelegate,为此我们需要实现一个类继承自SliverPersistentHeaderDelegate 这里需要自己实现SliverPersistentHeaderDelegate,SliverAppBar也是基于这个实现的,只是逻辑更复杂.

SliverPersistentHeader属性:
    delegate:SliverPersistentHeaderDelegate 是否固定头布局(默认false)
    pinned: 是否固定头布局(默认false)
    floating: 是否浮动头布局(默认false)

SliverPersistentHeaderDelegate实现代码如下: 可以看到,SliverPersistentHeaderDelegate的实现类必须实现其4个方法。其中:
minExtent:收起状态下组件的高度;
maxExtent:展开状态下组件的高度;
shouldRebuild:类似于react中的shouldComponentUpdate;
build:构建渲染的内容。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/screenutil.dart';

class SliverCustomHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double collapsedHeight;
  final double expandedHeight;
  final double paddingTop;
  final String coverImgUrl;
  final String title;
  final Function popCallBack;
  BuildContext context;

  SliverCustomHeaderDelegate({
    this.context,
    this.collapsedHeight,
    this.expandedHeight,
    this.paddingTop,
    this.coverImgUrl,
    this.title,
    this.popCallBack
  });

  @override
  double get minExtent =>
      this.collapsedHeight +
          this.paddingTop +
          MediaQuery.of(context).padding.top;

  @override
  double get maxExtent => this.expandedHeight;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    return true;
  }

  Color makeStickyHeaderBgColor(shrinkOffset) {
    final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255)
        .clamp(0, 255)
        .toInt();
    return Color.fromARGB(alpha, 255, 255, 255);
  }

  Color makeStickyHeaderTextColor(shrinkOffset, isIcon) {
    if (shrinkOffset <= 50) {
      return isIcon ? Colors.white : Colors.transparent;
    } else {
      final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255)
          .clamp(0, 255)
          .toInt();
      return Color.fromARGB(alpha, 0, 0, 0);
    }
  }

  Color makeStickyHeaderTitleColor(shrinkOffset) {
    //它代表当前头部的滚动偏移量
    if (shrinkOffset > 50) {
      return Colors.transparent;
    } else {
      return Color(0xFFFF542C);
    }
  }

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      height: this.maxExtent,
      width: MediaQuery.of(context).size.width,
      child: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          // // 背景图
          Container(child: Image.asset(this.coverImgUrl, fit: BoxFit.fill)),
          // 收起头部
          Positioned(
            left: 0,
            right: 0,
            top: 0,
            child: Container(
              color: this.makeStickyHeaderBgColor(shrinkOffset), // 背景颜色
              child:
              // SafeArea(
              //   bottom: false,
              //   child:
              Container(
                height:
                this.collapsedHeight + MediaQuery.of(context).padding.top,
                padding: EdgeInsets.only(top: 20),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    IconButton(
                      onPressed: () async{
                        await popCallBack();
                      },
                      padding: const EdgeInsets.all(16.0),
                      icon: Image.asset(
                        'assets/images/ic_back.png',
                        color:
                        this.makeStickyHeaderTextColor(shrinkOffset, true),
                      ),
                    ),
                    Container(
                      width: ScreenUtil().setWidth(400),
                      child: Text(
                        this.title,
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.w500,
                          color: this.makeStickyHeaderTextColor(
                              shrinkOffset, false), // 标题颜色
                        ),
                      ),
                    ),
                    AnimatedOpacity(
                      opacity: 0,
                      duration: Duration(microseconds: 200),
                      child: IconButton(
                        padding: const EdgeInsets.all(16.0),
                        icon: Image.asset(
                          'assets/images/ic_back.png',
                          color: this
                              .makeStickyHeaderTextColor(shrinkOffset, true),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              // ),
            ),
          ),
          Positioned(
              bottom: ScreenUtil().setHeight(40),
              left: ScreenUtil().setWidth(30),
              child: Container(
                width: ScreenUtil().setWidth(500),
                child: Text(
                  title,
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                  style: TextStyle(
                    fontSize: ScreenUtil().setSp(34),
                    color: this.makeStickyHeaderTitleColor(
                        shrinkOffset), // Color(0xFFFF542C)),
                  ),
                ),
              ))
        ],
      ),
    );
  }
}


欢迎大家和我一起学习分享flutter,项目会持续更新新的学习demo

此项目的github地址:项目地址

下面是我们的公众号:flutter编程笔记(code9871)

公众号 不定期分享自己的学习想法

往期回顾:

flutter实现数字刻度尺

flutter实现简单的旋转动画

flutter实现简单的抽屉效果