티스토리 뷰
[1] 용어
보통 Marquee 라고 부르는 것은 두가지 타입이 있습니다.
1) 계속 한 방향으로 흘러가는 Marquee

2) 왼쪽 갔다가 오른쪽 갔다가(?) 를 계속 반복하는 Marquee

이 두개를 각각 나눠 말하는 일반적인 용어를 찾고 싶었는데 못찾았습니다,,,
보통은(특히 웹에서) 1번 타입을 Marquee라고 많이 부르고 있기는 합니다,, (marquee는 현수막이라는 뜻이에요)
그러면 2번을 PingpongMarquee 라고 네이밍해서 일단 만들어보겠습니다. (이름이 잘 안떠오르네요,,😳)
===> (추가) 여기 블로그 에서 봤는데 html에서는 1번을 scroll, 2번을 alternate 속성이라고 부른다고 합니다
[2] PingpongMarquee 만들기
# Step 1.
우선 statefulWidget을 만들고 items를 받게 만들어줍니다.
import 'package:flutter/cupertino.dart'; | |
class PingpongMarquee extends StatefulWidget { | |
final List<Widget> items; | |
PingpongMarquee({ | |
Key key, | |
@required this.items, | |
}): super(key: key); | |
@override | |
_PingpongMarqueeState createState() => _PingpongMarqueeState(); | |
} | |
class _PingpongMarqueeState extends State<PingpongMarquee> { | |
@override | |
Widget build(BuildContext context) { | |
return Container(); | |
} | |
} |
# Step 2.
그 다음 SingleChildScrollView를 사용해줍니다.
주입받은 items를 스크롤뷰로 감싸줍니다.
사용자가 스크롤할 수 있는 것을 막기 위해 NeverScrollableScrollPhysics 를 설정해줍니다.
import 'package:flutter/cupertino.dart'; | |
class PingpongMarquee extends StatefulWidget { | |
final List<Widget> items; | |
PingpongMarquee({ | |
Key key, | |
@required this.items, | |
}): super(key: key); | |
@override | |
_PingpongMarqueeState createState() => _PingpongMarqueeState(); | |
} | |
class _PingpongMarqueeState extends State<PingpongMarquee> { | |
@override | |
Widget build(BuildContext context) { | |
return SingleChildScrollView( | |
scrollDirection: Axis.horizontal, | |
child: Row(children: widget.items), | |
physics: NeverScrollableScrollPhysics(), // not allow the user to scroll. | |
); | |
} | |
} |
# Step 3.
그 다음 scroll을 직접 컨트롤해주고 싶으니까 scrollController를 넣어줍니다.
class _PingpongMarqueeState extends State<PingpongMarquee> { | |
ScrollController _scrollController; | |
@override | |
void initState() { | |
_scrollController = ScrollController(); | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return SingleChildScrollView( | |
scrollDirection: Axis.horizontal, | |
child: Row(children: widget.items), | |
controller: _scrollController, | |
physics: NeverScrollableScrollPhysics(),// not allow the user to scroll. | |
); | |
} | |
@override | |
void dispose(){ | |
_scrollController.dispose(); | |
super.dispose(); | |
} | |
} |
# Step 4.
그 다음 addPostFrameCallback 을 등록해줍니다.
build 메소드가 끝난 후, 한번 불리는 콜백입니다. 보통 위젯들 setup이 다 끝난 후 처음 해줘야하는 작업에 사용합니다. (참고)
그리고 여기 콜백(FrameCallback)으로 Future.doWhile 을 넘겨줄 건데,
False가 반환될 때까지 작업을 반복적으로 수행해준다고 합니다.

반복시킬 작업으로는 스크롤 시키는 것을 넣을 것이기 때문에
위젯 setup이 끝나고 작업이 시작되도록 addPostFrameCallback 를 사용한 것입니다!
(안그러면 scrollController가 scrollView에 아직 안붙었다~~ 이런 에러 나더라구요!)
이렇게 하면
true를 반환하니까 계속 작업이 무한반복됩니다.
class _PingpongMarqueeState extends State<PingpongMarquee> { | |
ScrollController _scrollController; | |
@override | |
void initState() { | |
_scrollController = ScrollController(); | |
WidgetsBinding.instance.addPostFrameCallback((_) async { | |
print("addPostFrameCallback"); | |
Future.doWhile(_scroll); | |
}); | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return SingleChildScrollView( | |
scrollDirection: Axis.horizontal, | |
child: Row(children: widget.items), | |
controller: _scrollController, | |
physics: NeverScrollableScrollPhysics(),// not allow the user to scroll. | |
); | |
} | |
@override | |
void dispose(){ | |
_scrollController.dispose(); | |
super.dispose(); | |
} | |
Future<bool> _scroll() async { | |
print("scroll"); | |
return true; | |
} | |
} |
App에서 Marquee를 만들어서 테스트해보겠습니다.
import 'package:flutter/cupertino.dart'; | |
import 'pingpongmarquee.dart'; | |
void main() { | |
runApp(MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return CupertinoApp( | |
home: _RootWidget() | |
); | |
} | |
} | |
class _RootWidget extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return PingpongMarquee(items: []); | |
} | |
} |
이렇게 출력 결과가 나오는 것을 볼 수 있습니다. (scroll이 계속 프린트 됩니다)
addPostFrameCallback
scroll
scroll
scroll
scroll
...
# Step 5.
이제 테스트를 위해 넣었던 print문을 빼고
scrollView을 left, right 끝으로 scroll 시켜주는 코드를 넣습니다.
class _PingpongMarqueeState extends State<PingpongMarquee> { | |
ScrollController _scrollController; | |
@override | |
void initState() { | |
_scrollController = ScrollController(); | |
WidgetsBinding.instance.addPostFrameCallback((_) async { | |
Future.doWhile(_scroll); | |
}); | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return SingleChildScrollView( | |
scrollDirection: Axis.horizontal, | |
child: Row(children: widget.items), | |
controller: _scrollController, | |
physics: NeverScrollableScrollPhysics(),// not allow the user to scroll. | |
); | |
} | |
@override | |
void dispose(){ | |
_scrollController.dispose(); | |
super.dispose(); | |
} | |
Future<bool> _scroll() async { | |
var pauseDuration = Duration(seconds: 1); | |
var animationDuration = Duration(seconds: 1); | |
var backDuration = Duration(seconds: 1); | |
await Future.delayed(pauseDuration); | |
_scrollController.animateTo(_scrollController.position.maxScrollExtent, duration: animationDuration, curve: Curves.easeOut); | |
await Future.delayed(pauseDuration); | |
_scrollController.animateTo(_scrollController.position.minScrollExtent, duration: backDuration, curve: Curves.easeOut); | |
return true; | |
} | |
} |
# Step 6.
마지막으로 duration도 주입 가능하게 바꿔주면 끝!
import 'package:flutter/cupertino.dart'; | |
class PingpongMarquee extends StatefulWidget { | |
final List<Widget> items; | |
final Duration animationDuration, backDuration, pauseDuration; | |
PingpongMarquee({ | |
Key key, | |
@required this.items, | |
this.animationDuration = const Duration(seconds: 1), | |
this.backDuration = const Duration(seconds: 1), | |
this.pauseDuration = const Duration(seconds: 1), | |
}): super(key: key); | |
@override | |
_PingpongMarqueeState createState() => _PingpongMarqueeState(); | |
} | |
class _PingpongMarqueeState extends State<PingpongMarquee> { | |
ScrollController _scrollController; | |
@override | |
void initState() { | |
_scrollController = ScrollController(); | |
WidgetsBinding.instance.addPostFrameCallback((_) async { | |
Future.doWhile(_scroll); | |
}); | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return SingleChildScrollView( | |
scrollDirection: Axis.horizontal, | |
child: Row(children: widget.items), | |
controller: _scrollController, | |
physics: NeverScrollableScrollPhysics(),// not allow the user to scroll. | |
); | |
} | |
@override | |
void dispose(){ | |
_scrollController.dispose(); | |
super.dispose(); | |
} | |
Future<bool> _scroll() async { | |
await Future.delayed(widget.pauseDuration); | |
_scrollController.animateTo(_scrollController.position.maxScrollExtent, duration: widget.animationDuration, curve: Curves.easeOut); | |
await Future.delayed(widget.pauseDuration); | |
_scrollController.animateTo(_scrollController.position.minScrollExtent, duration: widget.backDuration, curve: Curves.easeOut); | |
return true; | |
} | |
} |
# 테스트
아래의 코드로 테스트해볼 수 있습니다!
이미지는 여기에서 가져왔고 (>_<..)
카카오 폰트체를 사용하였습니다.

import 'package:flutter/cupertino.dart'; | |
import 'pingpongmarquee.dart'; | |
void main() { | |
runApp(MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return CupertinoApp( | |
home: _RootWidget() | |
); | |
} | |
} | |
class _RootWidget extends StatelessWidget { | |
var _textStyle = TextStyle(fontFamily: "Kakao", color: CupertinoColors.white, fontSize: 100); | |
@override | |
Widget build(BuildContext context) { | |
return SafeArea( | |
right: false, // 가로모드에서는 bottom이 아니라 right를 false로 해줘야함,, | |
child: PingpongMarquee( | |
items: [ | |
Image.asset("images/joon1.png", width: 100, height: 100), | |
Text(" 카페사장 ", style: _textStyle), | |
Image.asset("images/joon2.png", width: 100, height: 100), | |
Text(" 최준 ", style: _textStyle), | |
Image.asset("images/joon1.png", width: 100, height: 100), | |
Text(" ☕️ ", style: _textStyle), | |
Image.asset("images/joon2.png", width: 100, height: 100), | |
], | |
animationDuration: Duration(seconds: 2), | |
backDuration: Duration(seconds: 2), | |
pauseDuration: Duration(seconds: 2), | |
) | |
); | |
} | |
} |

Reference
marquee | Flutter Package
⏩ A Flutter widget that scrolls text infinitely. Provides many customizations including custom scroll directions, durations, curves as well as pauses after every round.
pub.dev
How to make a Text widget act like marquee when the text overflows in Flutter
I'm looking for a way to implement Marquee style on a Text widget so that it automatically start scrolling when the text is overflowed from the screen. Is there a way to do it. I've tried all the
stackoverflow.com
Resource
much-merch.com/product/최준-귀여워-핀자석-버튼-3개-세트-발렌타인-한정/768/category/213/display/1/
최준 귀여워 핀/자석 버튼 3개 세트 (발렌타인 한정)
PLEASE SELECT THE DESTINATION COUNTRY AND LANGUAGE : SHIPPING TO : 가나(GHANA) SHIPPING TO : 가봉(GABON) SHIPPING TO : 가이아나(GUYANA) SHIPPING TO : 감비아(GAMBIA) SHIPPING TO : 과테말라(GUATEMALA) SHIPPING TO : 그레나다(GRENADA) SHIPPI
much-merch.com
- 카카오 폰트 (링크를 못찾았어요)
'🤼♀️ > Flutter' 카테고리의 다른 글
[Flutter] pub.dev에 flutter package를 publish 하기 (2) | 2021.04.27 |
---|---|
[Flutter] Marquee Widget 만들기 (2) - scroll (0) | 2021.04.24 |
[Flutter] 기기 정보, 앱 정보 구하기 (Device Info, App Info) (3) | 2021.03.02 |
[Flutter] 이메일 보내기 (문의하기) (1) | 2021.03.02 |
[Flutter] Text에 Gradient Color 넣기 (1) | 2020.11.27 |
- Total
- Today
- Yesterday
- flutter deep link
- Flutter Clipboard
- 장고 URL querystring
- 장고 Custom Management Command
- Flutter Text Gradient
- flutter build mode
- Django Heroku Scheduler
- PencilKit
- DRF APIException
- Python Type Hint
- 구글 Geocoding API
- flutter 앱 출시
- Django Firebase Cloud Messaging
- METAL
- Flutter getter setter
- SerializerMethodField
- Watch App for iOS App vs Watch App
- Sketch 누끼
- 플러터 싱글톤
- cocoapod
- github actions
- flutter dynamic link
- ribs
- Flutter Spacer
- drf custom error
- Dart Factory
- Django FCM
- 플러터 얼럿
- ipad multitasking
- Flutter 로딩
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |