2019/12/5 早
Flutter - 分頁(yè)滑動(dòng)的ListView
在類(lèi)似圖片查看的應(yīng)用場(chǎng)景中,通常需要讓ListView在滑動(dòng)結(jié)束后停留在指定的分頁(yè)位置自沧,本篇就是使用Flutter ListView來(lái)實(shí)現(xiàn)這一需求
1.思路
- 創(chuàng)建一個(gè)
ListView
拇厢,且指定ScrollController
:頁(yè)面的移動(dòng)停留都需要ScrollController
來(lái)操作. - 對(duì)
ListView
的滑動(dòng)進(jìn)行監(jiān)聽(tīng):在ListView
外部包裹一個(gè)Listener
控件孝偎,同過(guò)其提供的api進(jìn)行觸摸監(jiān)聽(tīng). - 在滑動(dòng)監(jiān)聽(tīng)的同時(shí)寺旺,對(duì)觸摸釋放進(jìn)行判斷阻塑,并設(shè)置對(duì)應(yīng)的事件叮姑。
2.創(chuàng)建ListView
創(chuàng)建一個(gè)基本的ListView
,并對(duì)其設(shè)置指定的滑動(dòng)方向(這很重要,因?yàn)楹罄m(xù)的滑動(dòng)計(jì)算和滑動(dòng)方向是有著直接關(guān)聯(lián)的)据悔,指定的ScrollController
:
ScrollController _scrollController = new ScrollController();
List<T> data = buildDefaultList();
ListView.builder(
physics: BouncingScrollPhysics(),
controller: _scrollController,
scrollDirection:Axis.horizontal,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return buildImageItemView(index);
},
),
3.創(chuàng)建觸摸監(jiān)聽(tīng)部件Listener
3.1 創(chuàng)建Listener
:
Offset pointerStart;
Offset pointerEnd;
Listener(
onPointerDown: (event) {
//保存觸摸按下的位置信息
pointerStart = event.position;
},
onPointerUp: getPonitUpListenerInHorizontal()
child: child,
);
這里舉例僅僅使用橫向正向滑動(dòng)传透,所以我們只需要關(guān)心觸摸按下事件與觸摸離開(kāi)事件,其實(shí)Listener
也就提供了如下參數(shù):
const Listener({
Key key,
this.onPointerDown,//觸摸按下
this.onPointerMove,//觸摸滑動(dòng)
this.onPointerUp,//觸摸抬起
this.onPointerCancel,//觸摸取消
this.behavior = HitTestBehavior.deferToChild,
Widget child
}) : assert(behavior != null),
super(key: key, child: child);
3.2實(shí)現(xiàn)觸摸抬起監(jiān)聽(tīng)
? onPointerUp
的類(lèi)型為PointerUpEventListener
极颓,暴露出一個(gè)PointerUpEvent
,通過(guò)讀取PointerUpEvent
的position
來(lái)獲取當(dāng)前的位置朱盐,進(jìn)而判斷并執(zhí)行需要處理的操作。
代碼如下:
//獲取屏幕寬度
Size screenSize = MediaQuery.of(context).size;
screenWidth = screenSize.width;
/**
* 構(gòu)造橫向滑動(dòng)時(shí)候的觸摸抬起監(jiān)聽(tīng)
*/
PointerUpEventListener getPonitUpListenerInHorizontal() {
return (event) {
pointerEnd = event.position;
touchRangeX = pointerStart.dx - pointerEnd.dx;
touchRangeY =pointerStart.dy - pointerEnd.dy;
//所有的操作必須要滿足滑動(dòng)距離>10才算是滑動(dòng)
if (touchRangeX.abs() < 10) {
nextOffset = screenWidth * lastPage;
scrollAnimToOffset(_scrollController, nextOffset, () {
if (lastPage < 0) {
lastPage = 0;
}
});
return;
}
//縱向操作大于橫向操作三倍視為縱向操作
//這個(gè)判斷攔截只有在縱向操作距離大于20.0的時(shí)候才生效
if (touchRangeX.abs() < touchRangeY.abs() && touchRangeY > 20) {
nextOffset = screenWidth * lastPage;
scrollAnimToOffset(_scrollController, nextOffset, () {
if (lastPage < 0) {
lastPage = 0;
}
});
return;
}
//如果滑動(dòng)小于當(dāng)前屏幕1/8菠隆,那么就回彈復(fù)原兵琳,超過(guò)則移動(dòng)到下一頁(yè)
//跳轉(zhuǎn)到下一頁(yè)或者上一頁(yè)或者不動(dòng)
if (touchRangeX > screenWidth / 8) {
nextOffset = screenWidth * (lastPage + 1);
print("animate to ${nextOffset}");
scrollAnimToOffset(_scrollController, nextOffset, () {
lastPage++;
if (lastPage >= _chapterDetail.data.length - 1) {
lastPage = _chapterDetail.data.length - 1;
}
});
} else if (touchRangeX < -1 * screenWidth / 8) {
nextOffset = screenWidth * (lastPage - 1);
print("animate to ${nextOffset}");
scrollAnimToOffset(_scrollController, nextOffset, () {
lastPage--;
if (lastPage < 0) {
lastPage = 0;
}
});
} else {
scrollAnimToOffset(_scrollController, screenWidth * lastPage, null);
}
};
}
列表滑動(dòng)方法scrollAnimToOffset
:
/**
* 滑動(dòng)到指定位置
*/
void scrollAnimToOffset(ScrollController controller, double offset,
void Function() onScrollCompleted) {
controller
.animateTo(offset,
duration: Duration(
milliseconds: 200,
),
curve: Curves.easeIn)
.then((v) {
if (onScrollCompleted != null) {
onScrollCompleted();
}
}).catchError((e) {
print(e);
});
}