大家好,今天要做的是捕捉ListView裡面顯示在螢幕上的是哪一個Item,今天會用到NotificationListener、HitTestResult、MetaData、ShaderMask
先為ListView
添加監聽的NotificationListener
onNotification
會傳出很多滾動的訊息,例如在列表的ScrollStartNotification
開始滾動、ScrollUpdateNotification
滾動更新、ScrollEndNotification
滾動結束的狀態等
NotificationListener(
onNotification: (noti) {
return true;
},
child: ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, index) {
return _buildItem(index);
},
),
),
要找到在列表的什麼位置是簡單的事情,因為可以透過ScrollController.offset
取得,但是單純位置不足以判斷現在顯示的哪一個Widget。除非知道每一個Item的高度,去算現在是第幾個。不過,今天我使用的方法是用MetaData。
元數據,用MetaData
可以把一些訊息放在渲染樹中,讓用戶交互的時候可以提供訊息HitTestBehavior
是一個枚舉類,具體請參閱這裡
_buildItem(int index) {
return MetaData(
metaData: index, //這邊希望放入的是index資訊
behavior: HitTestBehavior.translucent,
child: _buildComingSoonItem(index),
);
}
T getMeta<T>(double x, double y) {
//這兩行主要目的是找出一個Offset,要取得訊息的位置
var renderBox = context.findRenderObject() as RenderBox;
var offset = renderBox.localToGlobal(Offset(x, y));
//這兩行是對這個位置做點擊測試
HitTestResult result = HitTestResult();
WidgetsBinding.instance.hitTest(result, offset);
//從測試結果取出metaData訊息
for (var i in result.path) {
if (i.target is RenderMetaData) {
var d = i.target as RenderMetaData;
if (d.metaData is T) {
return d.metaData as T;
}
}
}
return null;
}
在onNotification
裡面調用getMeta,取得訊息就可以對其他UI做控制了
Future.microtask
onNotification: (noti) {
Future.microtask(() {
var info = getMeta(0, MediaQuery.of(context).size.height * .5);
print("scrolling to ${info}");
if (info != null) {
setState(() {
visibleIndex = info;
});
}
});
if (noti is ScrollEndNotification) {
print('開始播放');
}
return true;
},
直接將原本的每個Item包起來,可以透過LinearGradient快速定義shader
LinearGradient(colors: [_color, _color]).createShader(rect)
_buildComingSoonItem(int index) {
//判斷是否是中間正在顯示的Widget,來使用遮罩
Color _color = Colors.white.withOpacity(visibleIndex != index ? 0.4 : 1.0);
return ShaderMask(
shaderCallback: (rect) {
return LinearGradient(colors: [_color, _color]).createShader(rect);
},
child: Padding(...),
);
}
今日完成的效果,可以看到除了正中間的正常顯示,其他的都是被遮住的
GitHub連結: flutter-netflix-clone