2016년 12월 13일 화요일

SlidingUpPanelLayout 소스 분석

지금은 없어졌지만 Umano에서 만든 앱에 적용된 sliding up panel의 구현 소스 분석

https://github.com/umano/AndroidSlidingUpPanel

패널을 위로 스와이프하는 경우 메인 영역 어둡게 처리하기

  • ViewGroup의 child를 그릴때 drawChild가 호출되므로 여기에서 처리하도록 한다.
  • slideable view가 있고 main view인 경우에 main view를 어둡게 처리한다.
  • 그림을 그리는 영역을 canvas.getClipBounds로 얻어온다.
    • 이 영역의 bottom을 slideable view의 top과 비교하여 작은 값으로 바꿔준다. -> slideable view는 어둡게 처리할 필요 없다.
  • 지정된 fade color로부터 alpha를 얻어온 후 slide offset의 비율에 따라 어두운 정도를 계산한다.
  • 계산된 alpha로 다시 color값을 만든 후 이 color를 canvas.drawRect에 지정한다.

Shadow를 slideable view 위에 표시하기

  • draw를 override하여 구현한다.
    • draw는 drawChild에 의해 호출된다.
  • View 소스를 보면 drawing step에 대해 아래와 같은 설명이 있다.
    • 1 Draw the background
    • 2 If necessary, save the canvas' layers to prepare for fading
    • 3 Draw view's content
    • 4 Draw children
    • 5 If necessary, draw the fading edges and restore layers
    • 6 Draw decorations (scrollbars for instance)
  • shadow drawable을 xml로 만든다: gradient
  • draw(Canvas c)를 override 하여 여기서 shadow drawable을 지정한 위치에 그린다.
  • slideable view의 top으로부터 shadow drawable을 놓을 위치(drawable의 top과 bottom)를 파악한다.

onSaveInstanceState

  • slide state 정보를 저장하여 onRestoreInstanceState 호출 시 다시 저장할 수 있도록 한다.

class LayoutParams extends ViewGroup.MarginLayoutParams

  • weight를 지정하여 percentage로 height를 정할 수 있도록 한다.
  • ViewGroup의 generateLayoutParams이 호출될 때 이 LayoutParams를 생성하여 리턴하여 이 layout이 사용되도록 한다.
  • checkLayoutParams가 false를 리턴하는 경우 generateLayoutParams이 호출된다.
  • onLayout에서 margin이 크기 계산에 사용될 수 있도록하기 위해 사용된다.

class DragHelperCallback extends ViewDragHelper.Callback

  • tryCaptureView
    • dragging을 허용하는 slideable view를 리턴하도록 한다.
  • getViewVerticalDragRange
    • slide range를 리턴한다.
  • onViewDragStateChanged
    • drag state가 바뀌면 slide offset을 계산한 후 이 값에 따른 state를 저장한다.
  • onViewPositionChanged
    • slideable view의 위치에 따라 main view의 크기를 조절한다.
      • 보통은 현재 상태 그대로 유지.
      • collapsed 상태이고 overlay가 아닐 때 height를 match_parent로 변경한다.
  • onViewReleased
    • dragging하다가 손을 뗀 경우 호출된다.
    • slideable view를 최종으로 위치시킬 top 위치를 계산한 후 settleCapturedViewAt를 사용하여 적용한다.
  • clampViewPositionVertical
    • collapsed와 expanded 사이로 해서 top의 위치를 리턴한다. 이 값으로 slideable view의 위치를 이동한다.

mFirstLayout

  • layout을 다시 정하는 경우 사용된다.
  • true로 설정
    • anchor point를 새로 설정할 때
    • onAttachedToWindow와 onDetachedFromWindow가 호출될 때
    • onSizeChanged가 호출될 때 height가 변경되었을 때
  • false로 설정
    • onLayout이 끝난 경우
  • onLayout
    • slide offset 새로 계산
    • slideable view의 경우 새로 계산된 slide offset 값에 따라 새로 위치가 계산되어야 함

setWillNotDraw(false)

  • 직접 draw를 그린다는 것을 의미한다. onDraw가 호출되도록 한다.

onMeasure

  • child로 두개만 가지는지를 확인한다. 첫번째가 main view이고 두번째가 slideable view이다.
  • main view와 slideable view의 width와 height를 계산한다.
  • main view
    • overlay가 아닌 경우 height에서 panel의 height를 뺀다.
  • slideable view
    • slide range를 지정한다: height - panel height

onLayout

  • slide state에 따라 slide offset(0 ~ 1)을 계산한다.
  • slide offset에 따라 slideable view의 top을 계산한 후 이를 적용한다.
  • parallax가 지정된 경우 main view의 위치를 ViewCompat.setTranslationY함수를 사용해서 slide offset의 비율만큼 옮겨준다.

computeScroll

  • 스크롤시 호출되는 함수
  • 보통 특별한 작업이 필요한 것이 아니면 여기서 사용되는 것과 같이 쓰면 된다: continueSettling와 ViewCompat.postInvalidateOnAnimation의 조합

requestLayout과 invalidate의 사용

  • layout부터 다시 그려야 하는 경우는 requestLayout을 호출하고 view의 내용만 다시 그려야 하는 경우는 invalidate를 호출해준다.

터치 이벤트 처리

  • 뷰의 터치 이벤트 관련 함수에서 ViewDragHelper의 관련 함수를 호출해준다.
  • dispatchTouchEvent
    • dragging중일때는 바로 onTouchEvent를 호출한다.
  • onInterceptTouchEvent
    • mDragHelper.shouldInterceptTouchEvent(ev)를 호출해준다.
    • true를 리턴하면 자기 자신의 onTouchEvent를 호출하고 false를 리턴하면 자식의 onTouchEvent가 호출된다.
    • 터치 포인트가 slideable view 영역 안이 아니면 mDragHelper.cancel()를 호출하고 false를 리턴한다.
  • onTouchEvent
    • mDragHelper.processTouchEvent(ev)를 호출해준다.

터치 영역이 특정 View에서 이루어진 것인지 확인하기

  • 확인을 원하는 View의 getLocationOnScreen함수를 호출하여 스크린 기준 기준점의 위치를 알아낸다. -> viewLocation
  • 자기 자신의 getLocationOnScreen함수를 호출하여 스크린 기준 기준점의 위치를 알아낸다. 여기에 터치 포인트를 더하여 스크린의 어디에서 터치가 일어났는지 알아낸다. -> screenX, screenY
  • 앞에서 찾은 값의 비교로 터치 포인트가 View안에서 일어난 것인지 확인 가능

댓글 없음:

댓글 쓰기

Building asynchronous views in SwiftUI 정리

Handling loading states within SwiftUI views self loading views View model 사용하기 Combine을 사용한 AnyPublisher Making SwiftUI views refreshable r...