[Flutter] 지도 내 위치 선택 인터렉션

locked·2021년 11월 16일
13

Interaction

목록 보기
3/5
post-thumbnail

이번 포스트에서는 여러 앱에서 내 위치를 선택할 때 사용하는 마커(핀) 인터렉션에 대해 알아보려고 한다.
지도를 사용하는 앱은 굉장히 많고, 많은 앱들에서 현재 위치를 선택할 수 있는 기능을 제공한다.

현재 위치를 선택하는 화면에는 어떤 인터렉션이 숨어있을까?

많은 앱이 있겠지만 쏘카배달의민족 이미지를 준비했다.

쏘카에서 내 위치를 선택하는 핀

배달의민족에서 내 위치를 선택하는 핀

기능적으로 보면 두 앱에서는 동일하게 본인의 위치를 선택하는 기능을 한다.
다른 점이 있다면, 쏘카는 한강에 위치하게 될 경우 비활성화되고, 배달의 민족에서는 작업처리하는 과정을 사용자에게 노출한다. (ProgressBar)

..

그렇다면 위의 인터렉션은 어떻게 개발할까?.
천천히 시작해보자.

기획

앞서 본 두 경우에서는 사용자의 터치에의해 Google Map이 이동하는 동안은 마커가 위로 올라가는 것을 볼 수 있다.
위로 올라가 있는 동안에도 위치를 정확하게 맞출 수 있게 마커의 그림자를 표시해 두었는데, 나는 이 그림자를 좀 더 입체적으로 표현해볼까 한다. (그림자로 주는 원근감)

구현해볼 사항을 정리해 보면 다음과 같다.

  1. 구글 지도
  2. 구글 지도 카메라 이동 이벤트
  3. 마커 위로 올라가는 애니메이션
  4. 마커 비활성화 애니메이션
  5. 마커 그림자

개발

먼저 Flutter에서 구글 맵을 사용하기 위해서는 다음 패키지를 불러와야한다.

google_maps_flutter: ^2.0.6

구글 맵 만들기

GoogleMap(),

GoogleMap위젯을 사용하여 간단하게 화면에 지도를 출력할 수 있다.

구글 맵 이벤트

마커를 움직이기 위해서 구글 맵의 이벤트를 달아야한다.
구글 맵 터치 이벤트의 시작과 끝을 알아야한다.

GoogleMap(
  onCameraIdle: () {
  	// 카메라 이동이 멈춘 경우
  },
  onCameraMoveStarted: () {
    // (사용자에 의해)카메라 이동이 시작된 경우
  },
  ...
),

위의 두 함수를 통해서 이벤트를 감지할 수 있다.

그렇다면 마커에게 움직임을 주기 위해서는..
onCameraMoveStarted 상황에 마커의 애니메이션을 시작하고,
onCameraIdle에 마커의 애니메이션을 종료하면 된다.

구글 마커 생성

구글 맵 위에서 마커를 띄우기 위해서는 아래와 같이 마커를 만들어 줘야한다.

canvas나 image를 사용한다.

GoogleMap(
  markers: _markers,
),

하지만, 이 경우에는 본인의 위치를 화면의 정중앙에 위치하도록 하는 상황이기 때문에 마커를 만들지 않아도 된다.

그렇기 때문에 아래의 코드와 같이 Stack으로 쌓은 위젯트리 내부에 마커위젯으로 구현해도 된다.

Stack(
  children: [
    GoogleMapWidget(),
    MarkerWidget(), // == AnimatedMarker()
    ...
  ],
  ...
),

위젯으로 마커를 구현한다고하면 구현 난이도가 훨씬 낮아지게 된다.

마커 애니메이션

먼저 위에서 설명한 구글 맵의 함수를 먼저 채워 넣는다.

bool isMove = false;
///
GoogleMap(
  onCameraIdle: () {
  	// 카메라 이동이 멈춘 경우
    setState(()=> isMove = false);
  },
  onCameraMoveStarted: () {
    // (사용자에 의해)카메라 이동이 시작된 경우
    setState(()=> isMove = true);
  },
  ...
),
...

이후 마커를 구현하는데 움직일 때 투명해지는 모습은 AnimatedOpacity위젯을 사용하고, 위치 변화/그림자 효과는 AnimatedContainer를 사용할 것이다.

사용자의 터치 이벤트를 받게 되면, 마커가 공중으로 뜬 것처럼 보이게 된다.
또한, 마커의 그림자가 커지는 듯한 모습을 구현했다.

그림자의 모양은 Radius.elliptical()를 적용하여 타원형을 만들었다.

class AnimatedMarker extends StatelessWidget {
  final bool isMoving;
  const AnimatedMarker({Key? key, this.isMoving = false}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.center,
      child: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedOpacity(
              opacity: this.isMoving ? 0.5 : 1,
              child: MyMarker(),
            ),
            /// 그림자 및 높이 조절 위젯
            AnimatedContainer(
              duration: const Duration(milliseconds: 150),
              decoration: BoxDecoration(
                borderRadius: const BorderRadius.all(Radius.elliptical(100, 50)),
                gradient: RadialGradient(
                  colors: gradientColors,
                  radius: 1.2,
                ),
              ),
              height: this.isMoving ? 6 : 3,
              width: this.isMoving ? 16 : 8,
              margin: EdgeInsets.only(
                top: this.isMoving ? 16 : 0,
                bottom: this.isMoving ? 16 + 48 : 48,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

❗️ 화면에 구현된 모습에 따라, SafeArea와 Align을 적절히 사용해 실제 화면의 중앙과 마커의 위치를 일치시키도록 하자.

결과

위의 코드를 사용하여 아래 이미지와 같은 결과물을 만들어 낼 수 있다.
이번 경우에는 화면의 중앙에 핀이 있어야한다는 조건 때문에 쉽게 구현할 수 있었다.

해당 인터렉션은 https://kilom.page.link/download (앱)을 다운 받아 확인할 수 있어요.

좀 더 궁금하시다면, 댓글을 남겨주세요!

profile
Flutter 개발자

0개의 댓글