2019년 12월 31일 화요일

Complex UI/Animations on Android 정리

https://proandroiddev.com/complex-ui-animation-on-android-8f7a46f4aec4

1. 스크롤 할 때 툴바에 애니메이션 효과 주기

AppBarLayout의 behavior에 내가 사용하려는 애니메이션 효과를 가지는 커스텀 CoordinatorLayout behavior을 지정해준다.

아래와 같은 클래스를 만들고 onStartNestedScroll(스크롤이 시작할 때 호출되는 함수)과 onNestedScroll(스크롤중일 때 호출되는 함수)을 구현한다.

class ToolbarBehavior : CoordinatorLayout behavior<AppBaLayoutr>() { ... }

- onStartNestedScroll

위 아래로의 스크롤(vertical)일 때 true를 리턴한다.

- onNestedScroll

dyConsumed의 값이 때라 scroll up인지 down인지 파악한다. dyConsumed가 양수이면 scroll down이고, dyConsumed가 음수이면 scroll up임을 의미한다.

height를 변경하고 나면 requestLayout을 호출해 주어야 한다.
위치를 바꿀 때 translationX와 translationY를 사용한다.
scale을 적용할 때 scaleX와 scaleY를 사용한다.

추가로 ToolbarBehavior의 효과를 적용할 때 관련 뷰들을 참조하고 있어야 한다. 따라서, onStartNestedScroll이나 onNestedScroll이 호출되기 전에 이러한 관련 값(Views, height, 최소 height등)들을 미리 설정해 놓고 있어야 한다.

2. RecyclerView의 item의 height 변경할 때 애니메이션 효과 주기

아이템의 평상시 height와 커졌을 때의 height를 계산해놓고 있다가 사용자가 아이템을 클릭할 때 이에 맞추어 애니메이션 효과를 주는 것이 필요하다.

- Adapter의 onViewAttachedToWindow에서 height를 계산한다.

onViewAttachedToWindow는 Adapter에서 만든 뷰가 RecyclerView에 연결될 때 호출되는 함수이다.

- 평상시 height와 커졌을 때의 height를 계산하는 방법은 다음과 같다.

doOnLayout에서 평상시 뷰의 height를 계사한다. -> expanded view를 visible로 설정한다. -> doOnNextLayout에서 expanded view의 height를 계산한다. -> expanded view를 hide한다.

위와 같이 하면 사용자에게 뷰가 커졌다 작아졌다 하는 것을 보여주지 않고 각각의 height를 알 수 있게 된다.(이유는? : 아마도 onDraw가 불리기 전에 관련 layout이 실행되는 동안 height를 알아내서 사용자에게는 안보이게 되는 것이 아닐까 추측. 정확하게는 모르겠네요.) 여기서 한가지 더 주의할 것은 expanded view의 hide를 호출하는 부분을 expandedView.post { ... } 안(또는 doOnPreDraw에서)에서 해주어야 한다는 점이다.(expandedView.visible = false는 layout이 일어나도록 하지 않는다.)(post나 doOnPreDraw를 사용하면 onDraw가 불리기 전에 layout이 다시 일어나게 되는 걸까요? 이부분도 잘 모르겠군요.)

아이템을 클릭하면 이전에 커진 뷰가 있다면 원래 크기로 돌려야 하고 새로 클릭된 뷰를 크기를 키워야 한다. RecyclerView의 findViewHolderForAdapterPosition를 통해 이전에 커진 뷰를 찾을 수 있다.

ValueAnimator를 사용해서 점진적으로 커지는 애니메이션 효과를 줄 수 있다.

3. ViewPager2의 위치가 변경될 때 관련 탭의 위치도 같이 변경하기

ViewPager의 onPageScrolled에서 position과 positionOffset을 받아서 탭의 위치를 변경한다.(이 문서에서는 RecyclerView의 scrollBy를 사용해서 탭의 위치를 변경했는데 RecyclerView의 layout manager가 LinearLayoutManager라면 LinearLayoutManager의 scrollToPositionWithOffset를 사용하는 방법도 있다. 이게 더 간단한 방법인듯 하다.)

4. Filter sheet animation

네 단계로 나누어진 애니메이션 효과를 준다. 각각의 애니메이션이 동작하는 순서를 위해 AnimationSet을 사용한다.

a. Fab 버튼의 path animation
b. RecyclerView의 아이템들을 shrink하는 scale down animation
c. Fab 버튼이 Filter sheet로 변경되는 animation
d. 최종 뷰들 보여주기

각각을 자세히 살펴보자.

a. Fab 버튼의 path animation

현재 위치에서 화면의 중간으로 이동한다.
애니메이션을 위해 ValueAnimator를 사용하는데 이때의 progress를 ArcMetric를 사용해서 변경된 형태로 뷰의 x와 y에 적용한다.

b. RecyclerView의 아이템들을 shrink하는 scale down animation

ValueAnimator를 써서 현재 화면에 보이는 아이템들을 scale down 한다.

c. Fab 버튼이 Filter sheet로 변경되는 animation

Fab 버튼의 크기를 최종 뷰의 크기로 변경한다. 이때 0 ~ 0.8 까지는 원의 형태로 커지다가 0.8 ~ 1 로는 사각형으로 커지게 한다.

d. 최종 뷰들 보여주기

개별 뷰들을 화면에 보여준다.

5. Filtering animation

여섯 단계로 나누어진 애니메이션이다.

a. Fab Collapse Animation
b. Bottom Bar Animation
c. Tabs and ViewPager fade out animation
d. Close Icon rotate animation to simulate loading

rotation값을 변경한다.

e. Fab Arc Path animation
f. RecyclerView items scale down animation

Crafting Interpreters - 8. Statements and State 를 읽고

http://craftinginterpreters.com/statements-and-state.html

Statement는 값을 내는 것이 아닌 다른 무언가(side effect - output을 내거나 state를 변경하거나 하는 것)를 하는 것이다.

변수 선언을 하면 어딘가에 저장이 되어 있어야 나중에 이 변수의 값을 사용할 수 있습니다. 이를 위해 Environment라고 하는 것이 필요하게 됩니다.
Environment는 HashMap으로 (변수 이름, 값)을 내부에 저장하고 있습니다. 현재로서는 값을 저장할때는 define, 값을 꺼내올때는 get, 값을 재할당할때는 assign정도의 함수만 가지고 있으면 되겠습니다.

변수가 선언되고 사용되는 위치에 따라 scope가 지정됩니다. 이것은 block({})으로 지정합니다.  scope에는 lexical scope과 dynamic scope이 있는데 우리는 lexical scope를 사용합니다.
가장 상위를 global scope라고 하고 이후의 코드에서 별도의 scope를 가지고 싶으면 block을 사용합니다. 이러한 경우 block 내부의 변수가 외부의 변수를 가릴 수도 있고 값을 변경할 수도 있습니다. 이에 대한 처리를 위해 Environment에 parent Environment를 추가합니다. 어떤 값을 찾을 때 자기 자신이 가지고 있지 않으면 바로 에러를 발생시키지 않고 parent Environment에게 위임하는 거지요. parent에게서 변수가 찾아지면 그 값을 사용하면 되고 여전히 찾아지지 않으면 그의 parent에게 위임합니다. 이렇게 global environment까지 찾아 보게 합니다.

2019년 12월 26일 목요일

Crafting Interpreters - 7. Evaluating Expressions 를 읽고

http://craftinginterpreters.com/evaluating-expressions.html

앞장에서 파서를 만들어 봤다. 이번 장에서는 expression의 값을 구해보자.

Lox는 dynamically typed language이므로 런타임에 타입을 체크한다. 그러면 타입은 어떻게 알 수 있을까? JVM에서 Lox를 구현하고 있으므로 JVM의 도움으로 쉽게 체크할 수 있다.(자바는 instanceOf로 코틀린은 is로 타입을 확인한다.)

또한 Lox에서 사용하는 primitive type들은 자바에서의 타입과 쉽게 매치할 수 있다.

Lox typeJava representation
nilnull
BooleanBoolean
numberDouble
stringString

나중에 함수, 클래스, 인스턴스 등을 추가하면 좀 더 복잡해질 것이다.

런타임에 에러가 발생하면 어떻게 할까?(자바라면 ClassCastException을 던지고 stack trace를 보게 될 것이다.) 에러가 발생하면 에러의 위치(line number)와 에러 메시지를 화면에 보여주자.

2019년 12월 13일 금요일

Crafting Interpreters - 6. Parsing Expressions 를 읽고

http://craftinginterpreters.com/parsing-expressions.html

앞장에서 문법을 어떻게 표현할 것인지를 살펴보고 간단한 문법을 Context-free grammar로 표시해 봤습니다.

이러한 문법 표현에서 모호함(ambiguity)이 있을 수 있습니다. 예를 들면, 1 + 2 * 3 의 경우 결과가 어떻게 나와야 할까요? 앞에서 부터 계산을 하면 결과는 9가 될 것이고, 곱하기를 먼저 한다면(우리가 수학에서 배웠듯이) 결과가 7이 될 겁니다.

이러한 모호함을 우선 순위(precedence)와 결합 법칙(associativity)을 정해서 해결할 수 있습니다.(또는 먼저 계산되어야 하는 값에 괄호를 사용할 수도 있을 겁니다.)

토큰을 분석(parsing)하는 방법에는 여러가지 방법이 있는데 여기서는 Recursive descent parsing을 살펴볼 겁니다. 파서를 만드는 가장 간단한 방법이지만 그렇다고 만만하게 볼 파서는 아닙니다. 실제로 GCC나 V8(JavaScript)이나 Roslyn(C#)등에서 사용되고 있거든요.

토큰을 분석하다 에러를 만나면 어떻게 하는 것이 좋을까요? 처음으로 만나는 에러에서 멈추고 그 부분을 표시해주는게 좋을까요? 이게 가장 간단한 방법이기도 하지요. 하지만, 사용자한테는 매번 에러가 나타날 때마다 바로바로 표시해 주는 것보다는 전체에서 발생할 수 있는 에러를 한번에 표시해주는게 좋을겁니다. 이걸 error recovery라고 부릅니다.

에러가 발생해도 계속 진행하면서 이후의 에러도 확인하려면 어떻게 해야 할까요? 에러가 발생한 지점부터 특정 지점까지는 무시했다가 그 다음부터 토큰 분석을 다시 시작해야 할겁니다. 안그러면 엄청나게 쓸데없는 에러가 표시되겠지요. 에러가 발생하면 파서는 바로 panic mode로 들어가고 다시 정상적으로 파싱을 시작할 수 있는 지점을 만나면 panic mode를 벗어나게 됩니다. 이러한 과정을 synchronizaion이라고 합니다.

2019년 12월 7일 토요일

Crafting Interpreters - 5. Representing Code 를 읽고

http://craftinginterpreters.com/representing-code.html

앞장에서 살펴본 내용은 단순한 토큰의 나열이었죠.

예를 들어 1 + 2 * 3 - 4 가 있다고 하면 1, +, 2, *, 3, -, 4 이렇게 개별 토큰의 나열을 만드는 것을 해봤습니다. 근데 이것만으로는 아무 의미가 없습니다. 계산을 한다고 할 때 앞에서 부터 계산을 하는데 중간에 곱하기가 있으면 곱하기를 먼저 한다던지 하는 어떤 법칙이 필요하죠. 이걸 표현할 필요가 있습니다. => 문법

문법을 Context-Free Grammar로 표현할 수 있고 이 문법을 통해 어떤 형태가 옳은지를 표현할 수 있습니다. 이에 맞지 않으면 틀린 표현이 되는 것이겠지요.

아래와 같이 문법에 맞는 rule들을 표시하는데 각각의 rule은 head와 body로 표시합니다.

A -> a
B -> b
(A는 a로 변환할 수 있고, B는 b로 변환할 수 있다는 의미입니다.)

컴퓨터 과학에서는 이것을 아래와 같이 Backus-Naur form의 형태로 많이 사용합니다.

expression  literal
           | unary
           | binary
           | grouping ;

literal     NUMBER | STRING | "true" | "false" | "nil" ;
grouping    "(" expression ")" ;
unary       ( "-" | "!" ) expression ;
binary      expression operator expression ;
operator    "==" | "!=" | "<" | "<=" | ">" | ">=" 
| "+" | "-" | "*" | "/" ;

오른쪽에 있는 값들 중 "true"나 "==" 등은 더이상 쪼개 질 수 없기 때문에 terminal이라 부르고, expression이나 operator등은 다른 rule로 더 쪼개질 수 있기 때문에 nonterminal이라 부릅니다.

이 문법에 대한 data structure는 tree로 표현할 수 있습니다. 이것을 syntax tree라고 합니다.
(이처럼 보통 트리를 많이 사용합니다. 하지만, 다르게 표현할 수도 있는데요. 예를 들면, 바이트코드로 표현하는 방법이 있습니다.)

트리를 순회하면서 작업을 하려면 어떻게 하는게 좋을까요? 이와 관련하여 Expression problem이라는 것이 있습니다.
(이와 관련한 내용은 다음의 링크를 참고: http://www.haruair.com/blog/3338)

syntax tree를 보기 좋게 표현할 수 있으면 좋겠죠?
트리를 텍스트로 바꾸어 보기 좋게 표현해 주는 것을 pretty printer라고 부릅니다.
(이와 관련한 내용은 다음의 링크를 참고: https://jooyunghan.gitbooks.io/a-prettier-printer-kr/content/chapter1.html#sec1)



Crafting Interpreters - 4. Scanning 을 읽고

http://craftinginterpreters.com/scanning.html

인터프리터의 간단한 동작 단계는 scanning -> parsing -> evaluating code 로 말 할 수 있습니다. 4장에서는 이중 scanning에 대해 설명합니다.

다음과 같은 코드가 있다고 하면,

var language = "lox";

이 코드는 다음의 5개의 토큰으로 분할됩니다.

var, language, =, "lox", ;

이러한 개별 토큰들의 타입을 TokenType으로 분류합니다. 분류된 토큰들은 이후에 parsing 단계에서 사용되어야 하는데 그 때 필요한 정보들을 저장하고 있어야 하겠죠.

그래서,  Token class를 만듭니다. 이 class는 다음의 정보들을 저장하고 있습니다.

type: 토큰의 타입(TokenType)
lexeme: 토큰 string
literal: 토큰의 값
line: 토큰이 있는 소스의 라인값으로 에러 표시에 사용합니다.

lexeme과 literal의 차이에 대한 예

"lox"의 경우 lexeme은 "lox"이고, literal은 "를 제외한 lox 이다.
1234 의 경우 lexeme은 1234이고, literal은 double값인 1234.0이다.

토큰을 나눌 때 주의사항

orchid의 경우 or과 chid로 분리할 것인가 orchid로 분리할 것인가: 긴 토큰으로 분리하는 형태로 한다. - maximal munch

Building asynchronous views in SwiftUI 정리

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