오버스크롤 효과

Android 12 이상을 실행하는 기기에서 오버스크롤 이벤트의 시각적 동작이 변경됩니다.

Android 11 이하에서 오버스크롤 이벤트는 시각적 요소에 발광 효과가 나도록 합니다. Android 12 이상에서는 시각적 요소가 드래그 이벤트에 늘어났다가 다시 돌아오고 플링 이벤트에 플링되었다가 다시 돌아옵니다.

새 오버스크롤 동작은 드래그 애니메이션과 플링 애니메이션에 영향을 미칩니다.

이 동작은 EdgeEffect를 사용하는 모든 앱과 다음 클래스 내에 있는 모든 콘텐츠에 적용됩니다.

시각적 효과는 세로 스크롤과 가로 스크롤에서 모두 작동합니다. 기본적으로 오버스크롤을 선택 해제하지 않은 모든 앱에 적용되므로 사용자에게 더 일관된 UI 환경을 제공합니다.

권장사항

새 오버스크롤 환경이 앱에서 잘 작동하도록 하려면 다음 권장사항을 따르세요.

스트레치 EdgeEffect 사용

EdgeEffect는 스트레치 오버스크롤 효과를 구현하는 API를 두 개 추가합니다.

float getDistance()
float onPullDistance(float deltaDistance, float displacement)

스트레치 오버스크롤을 사용하여 최상의 사용자 환경을 제공하려면 다음을 실행하세요.

  • 사용자가 해제 애니메이션 중에 콘텐츠를 해제하고 터치하면 터치를 '캐치'로 등록합니다. 사용자가 애니메이션을 중지하고 스트레치 조작을 다시 시작합니다.
  • 사용자가 스트레치의 반대 방향으로 손가락을 이동하면 완전히 사라질 때까지 스트레치를 해제하고 스크롤을 시작합니다.
  • 사용자가 스트레치 중에 플링하면 EdgeEffect를 플링하여 스트레치 효과를 높입니다.

애니메이션 캐치

사용자가 활성 스트레치 애니메이션을 캐치하면 EdgeEffect.isFinished()false를 반환합니다. 이는 스트레치를 터치 모션으로 조작해야 함을 나타냅니다. 대부분의 컨테이너에서는 다음 코드 스니펫과 같이 onInterceptTouchEvent()에서 감지됩니다.

Kotlin

override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
  ...
  when (action and MotionEvent.ACTION_MASK) {
    MotionEvent.ACTION_DOWN ->
      ...
      isBeingDragged = !edgeEffectBottom.isFinished() ||
          !edgeEffectTop.isFinished()
      ...
  }
  return isBeingDragged
}

자바

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
  ...
  switch (action & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN:
      ...
      mIsBeingDragged = !mEdgeEffectBottom.isFinished()
          || !mEdgeEffectTop.isFinished();
      ...

위 예에서 onInterceptTouchEvent()mIsBeingDraggedtrue일 때 true를 반환하므로 하위 요소에 사용할 기회가 생기기 전에 이벤트를 사용하기에 충분합니다.

오버스크롤 효과 해제

스크롤 전에 스트레치 효과를 해제하여 스트레치가 스크롤 콘텐츠에 적용되지 않도록 하는 것이 중요합니다. 다음 코드 샘플은 이를 보여줍니다.

Kotlin

override fun onTouchEvent(ev: MotionEvent): Boolean {
  val activePointerIndex = ev.actionIndex

  when (ev.getActionMasked()) {
    MotionEvent.ACTION_MOVE ->
      val x = ev.getX(activePointerIndex)
      val y = ev.getY(activePointerIndex)
      var deltaY = y - mLastMotionY
      val pullDistance = deltaY / height
      val displacement = x / width

      if (deltaY < 0f && mEdgeEffectTop.distance > 0f) {
        deltaY -= height * mEdgeEffectTop
            .onPullDistance(pullDistance, displacement);
      }
      if (deltaY > 0f && mEdgeEffectBottom.distance > 0f) {
        deltaY += height * mEdgeEffectBottom
            .onPullDistance(-pullDistance, 1 - displacement);
      }

      ...
  }

자바

@Override
public boolean onTouchEvent(MotionEvent ev) {

  final int actionMasked = ev.getActionMasked();

  switch (actionMasked) {
    case MotionEvent.ACTION_MOVE:
      final float x = ev.getX(activePointerIndex);
      final float y = ev.getY(activePointerIndex);
      float deltaY = y - mLastMotionY;
      float pullDistance = deltaY / getHeight();
      float displacement = x / getWidth();

      if (deltaY < 0 && mEdgeEffectTop.getDistance() > 0) {
        deltaY -= getHeight() * mEdgeEffectTop
            .onPullDistance(pullDistance, displacement);
      }
      if (deltaY > 0 && mEdgeEffectBottom.getDistance() > 0) {
        deltaY += getHeight() * mEdgeEffectBottom
            .onPullDistance(-pullDistance, 1 - displacement);
      }
            ...

드래그할 때 터치 이벤트를 중첩된 스크롤에 전달하거나 스크롤을 드래그하기 전에 EdgeEffect의 pull 거리를 사용해야 합니다. 앞선 코드 샘플에서 getDistance()는 가장자리 효과가 표시되고 모션으로 해제될 수 있을 때 양수 값을 반환합니다. 터치 이벤트에서 스트레치를 해제하면 먼저 EdgeEffect에서 사용하므로 중첩된 스크롤과 같은 다른 효과가 표시되기 전에 완전히 해제됩니다. getDistance()를 사용하여 현재 효과를 해제하는 데 필요한 pull 거리를 알 수 있습니다.

onPullDistance()는 전달된 델타의 사용량을 반환한다는 점에서 onPull()과 다릅니다. 이전에 onPull()은 발광 효과의 총 거리에 음수 값을 허용했습니다. Android 12 이상에서 getDistance()가 0일 때 onPull()이나 onPullDistance()에 음수 deltaDistance 값이 전달되면 스트레치에 변화가 없습니다.

선택 해제

XML 레이아웃 파일에서 또는 프로그래매틱 방식으로 오버스크롤을 선택 해제할 수 있습니다. 다음 XML 코드는 레이아웃 파일에 설정된 android:overScrollMode를 보여줍니다.

<!-- Via markup -->
<ScrollView
  ...
  android:overScrollMode="never"
  ...
>

다음 코드 스니펫과 같이 프로그래매틱 방식으로 선택 해제하세요.

Kotlin

<!-- Programmatically-->
...
recyclerview.overScrollMode = View.OVER_SCROLL_NEVER
...

자바

<!-- Programmatically-->
...
recyclerview.setOverScrollMode(View.OVER_SCROLL_NEVER);
...

의견 보내기

보내주시는 의견은 프로그램 개선에 큰 힘이 됩니다. 문제를 발견하거나 기능 개선을 위한 아이디어가 있다면 Google에 알려주세요. 새 문제를 제출하기 전에 기존 문제를 살펴보시기 바랍니다. 별표 버튼을 클릭하여 기존 문제에 투표할 수 있습니다.

새로운 문제 제출하기

자세한 내용은 Issue Tracker 문서를 참고하세요.