2022. 8. 3. 11:22
Flutter에서 Flick 화면 만들기 개발/Flutter2022. 8. 3. 11:22
반응형
일전에 Flick(한 방향으로 미는 것을 말한다고 한다. 참고 : https://thankee.tistory.com/117) 동작을 하는 화면을 요청 받은 적이 있었다.
그때는 한 방향으로 밀면 화면이 밀은 그대로 높이를 이동시키는 화면이었는데
최근 또 다른 프로젝트를 하는데 비슷한 동작을 하는 화면이 있어서
일전에 만든 소스를 활용해서 좀 더 범용성 있게 작성해보았다.
이번에 필요한 화면은 똑같은 동작으로 화면의 높이를 변화시키지만,
고정적인 세 가지 위치(최저/중간/최대)로만 이동이 가능한 기능과
최대 크기(화면 전체였다)로 키웠을 때 그림자가 사라지게 하는 기능이 필요해서 그 부분만 추가로 작업해주었다.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class flickFrameCommon extends StatefulWidget {
var mode; //soft, hard
bool enabled;
double height;
double minHeight;
double maxHeight;
double middleHeight;
EdgeInsets padding;
Function changeHeight;//높이 값이 변화할 때 마다 flickFrameCommon를 호출한 곳에서 높이를 돌려 받을 수 있음
Widget child;
flickFrameCommon({
Key key,
this.mode = "soft",
this.enabled = true,
this.height = 52.0,
this.minHeight,
this.maxHeight,
this.middleHeight,
this.padding,
this.changeHeight,
@required this.child,
}) : super(key: key);
@override
_flickFrameCommonState createState() => _flickFrameCommonState();
}
class _flickFrameCommonState extends State<flickFrameCommon> {
int shadowAlpha = 125;
double prevHeight;
double Sensitivity = 100; //감도
@override
void initState() {
super.initState();
prevHeight = widget.height;
if (widget.minHeight == null) {
if (30 < widget.height && widget.height < 118.7 ) {
widget.minHeight = widget.height;
} else {
widget.minHeight = 118.7;
}
}
if (widget.mode != "hard" && widget.mode != "soft") {
throw ArgumentError("The mode is soft or hard");
}
if (widget.height < widget.minHeight) {
throw ArgumentError("The height is greater than the minimum value");
}
if (widget.maxHeight == null) {
if (333.7 < widget.height && widget.height <= Get.height) {
widget.maxHeight = widget.height;
} else {
widget.maxHeight = 333.7;
}
}
if (widget.height > widget.maxHeight) {
throw ArgumentError("The height is less than the maximum value");
}
if (widget.padding == null) {
widget.padding = EdgeInsets.only(left: 18.3, right: 18.3);
}
if (widget.mode == "hard") {
if (widget.middleHeight == null) {
throw ArgumentError("middleHeight is a must when in hard mode");
}
setHardHeight();
}
}
@override
Widget build(BuildContext context) {
if (widget.height == widget.maxHeight) {
shadowAlpha = 0;
} else {
shadowAlpha = 125;
}
return Positioned(
bottom: 0,
child: AnimatedContainer(
width: Get.width,
height: widget.height,
duration: Duration(milliseconds: 500),
padding: widget.padding,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)),
boxShadow: [BoxShadow(
color: Color.fromARGB(shadowAlpha, 0, 0, 0),
offset: Offset(0,3),
blurRadius: 5,
spreadRadius: 0,
)],
color: const Color(0xffffffff),
),
child: Listener(
behavior: HitTestBehavior.opaque,
onPointerMove: (details){
if (widget.enabled) {
setState(() {
//이동된 거리(details.delta.dy)는 위쪽으로 이동하면 -로 나오므로 현재 높이에 -를 해주면 +가 되어 높이가 커짐
if (widget.height - details.delta.dy < widget.minHeight) {//이동된 지점(현재 높이 - 이동된 거리)이 최저 높이보다 낮으면
widget.height = widget.minHeight;//높이를 최저 높이로 변경
} else
if (widget.height - details.delta.dy > widget.maxHeight) {//이동된 지점(현재 높이 - 이동된 거리)이 최대 높이보다 높으면
widget.height = widget.maxHeight;//높이를 최대 높이로 변경
} else {//최대와 최저 중간값이면
widget.height = widget.height - details.delta.dy;//높이를 이동된 지점으로 변경
}
});
}
},
onPointerDown: (details) {//Pointer 이벤트가 시작할 때
if (widget.enabled) {
prevHeight = widget.height;//이전 높이를 저장
}
},
onPointerUp: (details) {//Pointer 이벤트가 끝날 때
if (widget.enabled) {
if (widget.mode == "hard") {
setState(() {
setHardHeight();//hard mode이면 최종 높이를 최저/중간/최대로 지정
});
}
if (widget.changeHeight != null) {
widget.changeHeight(widget.height);
}
}
},
child: Column(children: [
Expanded(child: widget.child)
]),
),
),
);
}
setHardHeight () {
var minH_d = (widget.height - widget.minHeight).abs();//이벤트가 끝난 지점과 최저 높이의 거리 계산
var maxH_d = (widget.height - widget.maxHeight).abs();//이벤트가 끝난 지점과 최대 높이의 거리 계산
var middleH_d = (widget.height - widget.middleHeight).abs();//이벤트가 끝난 지점과 중간 높이의 거리 계산
var direction = widget.height - prevHeight;
if (prevHeight == widget.minHeight) {//이전 높이가 최저 높이였을 때
if (direction > Sensitivity) {//감도보다 위쪽으로 이동한 거리가 높으면
if (maxH_d < middleH_d) {//최대 높이의 거리가 중간 높이 거리보다 가까우면
widget.height = widget.maxHeight;//현재 높이를 최대 높이로 변경
} else {
widget.height = widget.middleHeight;//현재 높이를 중간 높이로 변경
}
} else {
widget.height = widget.minHeight;//감도가 낮으면 원래 높이로 이동
}
} else if (prevHeight == widget.maxHeight) {//이전 높이가 최대 높이였을 때
if (direction < -1 * Sensitivity) {//감도보다 아래쪽으로 이동한 거리가 높으면
if (minH_d < middleH_d) {//최저 높이의 거리가 중간 높이 거리보다 가까우면
widget.height = widget.minHeight;//현재 높이를 최저 높이로 변경
} else {
widget.height = widget.middleHeight;//현재 높이를 중간 높이로 변경
}
} else {
widget.height = widget.maxHeight;//감도가 낮으면 원래 높이로 이동
}
} else {
if (direction > Sensitivity) {//감도보다 위쪽으로 이동한 거리가 높으면
widget.height = widget.maxHeight;//현재 높이를 최대 높이로 변경
} else if (direction < -1 * Sensitivity) {//감도보다 아래쪽으로 이동한 거리가 높으면
widget.height = widget.minHeight;//현재 높이를 최저 높이로 변경
} else {//감도가 낮으면 원래 높이로 이동
widget.height = widget.middleHeight;//감도가 낮으면 원래 높이로 이동
}
}
}
}
크게 soft(기본값) / hard mode가 있는데 soft mode가 이전에 사용했던 최저/최대 높이 내에서 자유롭게 움직일 수 있는 방식이고
hard mode가 이번에 새로 개발한 최저/중간/최고 높이로 이동하는 방식이다.
get은 3.26.0 버전을 사용하였고
Get.height와 Get.wight 현재 디바이스의 높이/넓이를 말한다.
다른 방식으로 높이와 넓이를 구할 수 있으면 사용하지 않아도 된다.
ex)
import 'dart:async';
import 'package:blog/widget/flick_frame_common.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class Test extends StatefulWidget {
@override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double realMaxHeight = Get.height;
double height;
double minHeight = 52.0;
double middleHeight = 296.0;
@override
void initState() {
super.initState();
height = minHeight;
Future.delayed(Duration.zero, () async {
setState(() {
realMaxHeight = Get.height
- MediaQuery.of(context).padding.top
- MediaQuery.of(context).padding.bottom;
});
});
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xffffffff),
body: SafeArea(
child: Container(
width: Get.width,
height: Get.height,
child: Stack(children: [
Container(
width: Get.width,
height: Get.height,
alignment: Alignment.center,
child: Text("Content Area"),
),
flickFrameCommon(
mode: "hard",
height: height,
padding: EdgeInsets.only(top: 20),
minHeight: minHeight,
middleHeight: middleHeight,
maxHeight: realMaxHeight,
changeHeight: (height) {
setState(() {
this.height = height;
});
},
child: Column(
children: [
Text("Flick Frame Area")
],
),
)
]),
)
),
);
}
}
반응형
'개발 > Flutter' 카테고리의 다른 글
Flutter TextField에서 숫자 입력 시 천 단위 마다 구분자(콤마) 표시되도록 하는 기능 구현하기 (0) | 2022.09.28 |
---|---|
javascript/flutter(dart)에서 Byte 단위 값을 알맞는 용량 형식(KB/MB...)으로 변환하기 (0) | 2022.08.23 |
Flutter에서 TextField의 "붙여넣기" 도구 설명 이름을 한국어로 변경하는 방법 (0) | 2022.01.27 |
Flutter에서 new_version으로 업데이트 체크 시 Bad state: No element 오류 해결 (Android) (0) | 2021.12.27 |
webview_flutter로 alert/confirm 띄우기 (iOS) (4) | 2021.11.11 |