下拉放大背景在個人主頁的設(shè)計中比較常見,但是我沒在Flutter的文章中找到實現(xiàn)方式。
結(jié)合了幾個文章的方案后析珊,我嘗試修改實現(xiàn)方式潮售,最終結(jié)合CustomScrollView 和Listener的方法實現(xiàn)了以下的方案
CustomScrollView
可以說列表視圖想要自定義滾動效果,繞不過CustomScrollView
詳細的定義可以參見其他博客https://juejin.im/post/5bceb534e51d457aa4596f9a
這里不詳細贅述
SliverPersistentHeader 可高度自定制的頭部伸縮視圖
很多文章里推薦大家用SliverAppBar來實現(xiàn)頭部乳绕,這種方案并不是很靈活
SliverPersistentHeader 可以說對下拉放大背景圖的支持更好一些
SliverPersistentHeader(
delegate: _SVPersonalAppBarDelegate(
minHeight: defaultHeight,
maxHeight: maxHeight,
child: _createImageWidget()),
)
_createImageWidget() {
return Container(
child: Image.network(
'https://pic4.zhimg.com/80/v2-b02e601349241df0e3f25fd1ec622155_1440w.jpg',
fit: BoxFit.cover,
),
);
}
SliverPersistentHeader 需要我們傳入一個SliverPersistentHeaderDelegate對象俺祠,SliverPersistentHeaderDelegate是一個抽象類公给,所以需要我們自己去繼承實現(xiàn)一個子類
class _SVPersonalAppBarDelegate extends SliverPersistentHeaderDelegate {
_SVPersonalAppBarDelegate({
@required this.minHeight,
@required this.maxHeight,
@required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
@override
double get minExtent => minHeight;
@override
double get maxExtent => max(maxHeight, minHeight);
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new SizedBox.expand(child: child);
}
@override
bool shouldRebuild(_SVPersonalAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
完成頭部視圖的定制后,我們需要關(guān)心的幾個問題
- 放大后蜘渣,解除觸摸,如何讓他自動重置到正常狀態(tài)?
- 從頂部快速滑下后肺然,如何制造彈性放大回置的動畫蔫缸?
我通過引入Listener 和狀態(tài)監(jiān)聽來完成了這兩部份的實現(xiàn)
完整代碼如下
import 'dart:math';
import 'package:flutter/material.dart';
enum SVDragState {
SVDragStateIdle,
SVDragStateBegin,
SVDrageStateEnd,
}
class SVPersonalInfoPage extends StatefulWidget {
@override
_SVPersonalInfoPageState createState() => _SVPersonalInfoPageState();
}
class _SVPersonalInfoPageState extends State<SVPersonalInfoPage> {
double startOffsetY;
static double defaultHeight = 250;
static double maxHeight = 350.0;
static double offsetY = 0;
static double distance = maxHeight - defaultHeight;
SVDragState dragState = SVDragState.SVDragStateIdle;
final ScrollController controller =
ScrollController(initialScrollOffset: distance);
@override
void initState() {
controller.addListener(() {
offsetY = controller.offset;
if (offsetY <= 0) {
controller.jumpTo(0);
_resetWithAnimation(true);
}
});
super.initState();
}
_resetWithAnimation(bool delay) {
if (!delay) {
if (controller.offset < distance && dragState == SVDragState.SVDrageStateEnd) {
dragState = SVDragState.SVDragStateIdle;
controller.animateTo(distance,
duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
return;
}
}
Future.delayed(Duration(milliseconds: 200)).then((value) {
if (controller.offset < distance && dragState == SVDragState.SVDrageStateEnd) {
dragState = SVDragState.SVDragStateIdle;
controller.animateTo(distance,
duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
return;
}
});
}
@override
Widget build(BuildContext context) {
return Listener(
onPointerDown: (e) { dragState = SVDragState.SVDragStateBegin; },
onPointerUp: (e) {
dragState = SVDragState.SVDrageStateEnd;
_resetWithAnimation(false);
},
child: CustomScrollView(
controller: controller,
slivers: <Widget>[
SliverPersistentHeader(
delegate: _SVPersonalAppBarDelegate(
minHeight: defaultHeight,
maxHeight: maxHeight,
child: _createImageWidget()),
),
SliverList(delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
child: Text(
"This is item $index",
style: TextStyle(fontSize: 20),
),
color: Colors.redAccent,
);
},
))
],
));
}
_createImageWidget() {
return Container(
child: Image.network(
'https://pic4.zhimg.com/80/v2-b02e601349241df0e3f25fd1ec622155_1440w.jpg',
fit: BoxFit.cover,
),
);
}
}
class _SVPersonalAppBarDelegate extends SliverPersistentHeaderDelegate {
_SVPersonalAppBarDelegate({
@required this.minHeight,
@required this.maxHeight,
@required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
@override
double get minExtent => minHeight;
@override
double get maxExtent => max(maxHeight, minHeight);
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new SizedBox.expand(child: child);
}
@override
bool shouldRebuild(_SVPersonalAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}