101 lines
3.3 KiB
Dart
101 lines
3.3 KiB
Dart
import 'package:flutter/material.dart';
|
||
|
||
import '../l10n/tr.dart';
|
||
import 'theme.dart';
|
||
|
||
/// 옆으로 미는 줄 (폰 세로의 버튼 줄 등) 에 "더 있음" 표시.
|
||
/// 줄이 화면보다 길면 끝 쪽에 흐려지는 덮개와 [›] (왼쪽으로 밀었으면 [‹]) 를 보이고, 누르면 그쪽으로 한 화면쯤 민다.
|
||
/// (MKV 화면 폭 412: 세 번째 버튼이 화면 밖에서 잘렸는데 더 있다는 표시가 없었다 - 15 지적)
|
||
class ScrollMore extends StatefulWidget {
|
||
final Widget child;
|
||
|
||
/// 덮개 색 (줄 뒤 바탕과 같게)
|
||
final Color? background;
|
||
const ScrollMore({super.key, required this.child, this.background});
|
||
|
||
@override
|
||
State<ScrollMore> createState() => _ScrollMoreState();
|
||
}
|
||
|
||
class _ScrollMoreState extends State<ScrollMore> {
|
||
final _scroll = ScrollController();
|
||
bool _left = false, _right = false;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_scroll.addListener(_update);
|
||
WidgetsBinding.instance.addPostFrameCallback((_) => _update());
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_scroll.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _update() {
|
||
if (!mounted || !_scroll.hasClients) return;
|
||
final p = _scroll.position;
|
||
final left = p.pixels > 1;
|
||
final right = p.pixels < p.maxScrollExtent - 1;
|
||
if (left != _left || right != _right) {
|
||
setState(() {
|
||
_left = left;
|
||
_right = right;
|
||
});
|
||
}
|
||
}
|
||
|
||
void _jump(int dir) {
|
||
final p = _scroll.position;
|
||
final to = (p.pixels + dir * p.viewportDimension * 0.7).clamp(0.0, p.maxScrollExtent);
|
||
_scroll.animateTo(to, duration: const Duration(milliseconds: 220), curve: Curves.easeOut);
|
||
}
|
||
|
||
Widget _edge(bool right) {
|
||
final bg = widget.background ?? Theme.of(context).scaffoldBackgroundColor;
|
||
return Positioned(
|
||
top: 0,
|
||
bottom: 0,
|
||
left: right ? null : 0,
|
||
right: right ? 0 : null,
|
||
child: DecoratedBox(
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
begin: right ? Alignment.centerLeft : Alignment.centerRight,
|
||
end: right ? Alignment.centerRight : Alignment.centerLeft,
|
||
colors: [bg.withValues(alpha: 0), bg.withValues(alpha: 0.95), bg],
|
||
stops: const [0, 0.45, 1],
|
||
),
|
||
),
|
||
child: Padding(
|
||
padding: EdgeInsets.only(left: right ? 18 : 0, right: right ? 0 : 18),
|
||
child: Center(
|
||
child: IconButton(
|
||
tooltip: right ? tr('버튼 더 보기') : tr('앞의 버튼 보기'),
|
||
visualDensity: VisualDensity.compact,
|
||
icon: Icon(right ? Icons.chevron_right : Icons.chevron_left, color: JjColors.accent),
|
||
onPressed: () => _jump(right ? 1 : -1),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) => NotificationListener<ScrollMetricsNotification>(
|
||
// 줄 안의 버튼 글이 바뀌어 길이가 달라져도 다시 본다
|
||
onNotification: (_) {
|
||
WidgetsBinding.instance.addPostFrameCallback((_) => _update());
|
||
return false;
|
||
},
|
||
child: Stack(children: [
|
||
SingleChildScrollView(controller: _scroll, scrollDirection: Axis.horizontal, child: widget.child),
|
||
if (_left) _edge(false),
|
||
if (_right) _edge(true),
|
||
]),
|
||
);
|
||
}
|