달력

11

« 2024/11 »

  • 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
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")
                ],
              ),
            )
          ]),
        )
      ),
    );
  }
}

 

왼쪽 부터 순서대로 Filck Frame이 최저/중간/최고 높이일 때 화면

반응형
:
Posted by 리샤씨