Skip to content

Instantly share code, notes, and snippets.

@tranductam2802
Created October 28, 2020 03:36
Show Gist options
  • Save tranductam2802/9a9f5b41bf0d5d4bd011f28a33026d6f to your computer and use it in GitHub Desktop.
Save tranductam2802/9a9f5b41bf0d5d4bd011f28a33026d6f to your computer and use it in GitHub Desktop.
Demo infinite scroll controller
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Tutorial by Tran Duc Tam',
home: ProcessPage(),
);
}
}
const colorsList = [
Colors.blue,
Colors.amber,
Colors.indigo,
];
class ProcessPage extends StatefulWidget {
const ProcessPage();
@override
_ProcessPageState createState() => _ProcessPageState();
}
class _ProcessPageState extends State<ProcessPage> {
@override
Widget build(BuildContext context) {
final pageController = CirclePageController(itemCounts: colorsList.length);
return Scaffold(
appBar: AppBar(title: Text('')),
body: Center(
child: Container(
width: 400,
height: 400,
padding: EdgeInsets.all(2),
color: Colors.red,
child: PageView.builder(
controller: pageController,
itemCount: null,
physics: PageScrollPhysics(),
itemBuilder: (context, index) {
final localIndex = pageController.toLocalIndex(index);
return Container(
color: colorsList[localIndex],
child: Center(
child: Text('$localIndex'),
),
);
},
),
),
),
);
}
}
/// 円環を制御PageController
class CirclePageController extends PageController {
/// コンストラクタ
CirclePageController({
@required this.itemCounts,
bool keepPage = true,
double viewportFraction = 1.0,
int initialPage,
}) : assert(itemCounts != null),
assert(itemCounts >= 0),
assert(keepPage != null),
assert(viewportFraction != null),
assert(viewportFraction > 0.0),
super(
initialPage: itemCounts < kEnableCircleMinItemCounts
? initialPage % itemCounts
: (itemCounts + 1) % itemCounts,
keepPage: keepPage,
viewportFraction: viewportFraction,
);
/// 円環機能が有効か
bool get isEnableCircle => itemCounts >= kEnableCircleMinItemCounts;
/// データ一覧のインデックス
int get localIndex => localPage.truncate();
/// データ一覧のスクロール位置
double get localPage => isEnableCircle ? toLocalPage(page) : page;
/// ページビューのインデックスからデータ一覧のインデックスに変換
int toLocalIndex(int index) {
if (isEnableCircle) {
return (index - initialPage) % itemCounts;
} else {
return index;
}
}
/// データ一覧のインデックスからのページビューのインデックスに変換
int toGlobalIndex(int index) {
if (isEnableCircle) {
return index + initialPage;
} else {
return index;
}
}
/// ページビューのスクロール位置からデータ一覧のスクロール位置に変換
double toLocalPage(double page) {
if (isEnableCircle) {
final iPage = page.truncate();
final decimal = page - iPage;
return toLocalIndex(iPage) + decimal;
} else {
return page;
}
}
/// ページビューのスクロール位置を変更する時、処理行う
void _circleScrollListener() {
if (page - initialPage < 0.1 - 1) {
// ページビューの一番左のページになったら
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
super.jumpToPage(itemCounts);
});
} else if (page - initialPage - itemCounts > -0.1) {
// ページビューの一番右のページになったら
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
super.jumpToPage(initialPage);
});
}
}
@override
void attach(ScrollPosition position) {
super.attach(position);
// ページビューのスクロール位置を変更イベント登記
if (isEnableCircle) {
position.addListener(_circleScrollListener);
}
}
@override
void detach(ScrollPosition position) {
super.detach(position);
// ページビューのスクロール位置を変更イベント解除
position.removeListener(_circleScrollListener);
}
@override
void dispose() {
// ページビューのスクロール位置を変更イベント解除
for (final position in positions) {
position.removeListener(_circleScrollListener);
}
super.dispose();
}
// データ数
final int itemCounts;
// データ数は2項目未満場合、円環制御をしないような定数
static const int kEnableCircleMinItemCounts = 2;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment