본문 바로가기

Android(Kotlin) Study A부터 Z까지 등반

Android UI이론

안드로이드 앱을 처음 실행했을 때 보는 화면을 View라고 한다. 사용자는 View라는 컴포넌트를 통해서 앱과 상호작용을 한다. 우리는 이것을 User Interface라고 한다.

User Interface는 View, Viewgroup의 결합으로 만들어지며 View에 대해서 상호작용을 합니다.

이러한 View도 생명주기가 있다

 

  • Constructors는 컴포넌트를 생성할 때 해당 클래스로 따라가보면 Contructors들이 선언되어 있는 것을 볼 수 있다.
  • 화면에 나타나는 데에는 3가지로 분류된다.
    • 1. measure
    • 2. layout
    • 3. draw

화면이 그려지는 순서를 말로 간단하게 요약하자면 다음과 같다.

  1. 먼저 View가 그려질 때 measure() 함수에서 View의 크기를 결정하기 위해 호출된다. wrap_content나 match_parent같은 크기 설정에서 measure() 함수가 사용된다.
  2. 후에 layout() 함수에서 content를 배치하는 함수를 호출하여 위치를 설정한다.
  3. 그리고 나서 마지막으로 draw() 함수를 통해 실제로 그리게 된다.
  4. 위의 사이클을 참조해 생각해보면, 이미 위치가 정해져 있고, 크기가 변하지 않는다면 다시 measure() 함수나 layout() 함수를 호출할 필요가 없다. 이때는 invalidate()함수를 통해 dispatchToDraw() 함수로 이동하여 draw() 함수 부분만 실행할 수 있도록 한다.
  5. 하지만 크기가 바뀌거나 할 경우에는 requestLayout() 함수를 통해 처음으로 돌아가 1번의 과정부터 다시 수행하게 된다.

함수별 설명

onAttachedToWindow()

  • View가 Window에 연결되면 호출이 된다. View가 활성화될 수 있고, 드로잉할 것을 알고있는 단계로, 리소스 할당을 시작하거나 리스너를 설정할 수 있다.

measure()

  • measure 함수는 View의 크기를 결정해 달라고 요청할 때 사용된다. 인자의 widthMeasureSpec과 heightMeasureSpec은 MeasureSpec이라는 값으로 크기를 결정할 때 사용할 제약 조건이다.이는 Parent View가 Child View의 크기를 결정할 때의 제약조건을 의미한다.
  • MeasureSpec에는 3가지 속성으로 이루어져 있는데,1. UNSPECIFIED는 부모 View가 자식 View에 제한을 두지 않아 자식 View는 원하는 크기가 가능하다.2. EXACTLY는 부모 View가 자식 View의 크기를 결정한다. 주어진 경계 내에서 사이즈가 결정된다.3. AT_MOST는 자식 View는 지정된 크기까지 원하는 만큼 커질 수 있다.

onMesure()

  • 후에 onMeasure() 함수에서 widthMeasureSpec과 heightMeasureSpec을 기반으로 View의 크기를 결정한다.
  • onMeasure()에서는 값을 반환하지 않는다. 대신에 View의 크기가 결정되면 setMeasuredDimension()을 호출해준다. setMeasuredDimension에서는 계산된 View의 width와 height를 View의 크기로 설정한다.

layout()

  • layout은 View에서 Content의 위치를 결정해달라고 요청한다. 인자로 left, top, right, bottom 순서대로 받는다.

onLayout()

  • onLayout() 함수에서 위치가 바뀌었는지, left, top, right, bottom을 인자로 받는다.

draw()

  • 후에 draw() 함수를 통해 View에 그리기를 요청한다.

onDraw()

  • View가 가지고 있는 Content를 그린다.

dispatchDraw()

  • child View를 그린다.

requestLayout()

  • requestLayout() 함수가 호출되는 경우 위의 코드를 보아 예측해보자면, measure() 할 때로 돌아가기 위해 측정 했던 데이터들을 모두 지우는 듯하다.

invalidate()

  • invalidate()에서는 invalidateInternal() 메소드를 통해 onLayout() 이후부터 다시 실행하게 된다.

순회(Traversals)

View 계층 구조는 부모 노드 (ViewGroup)에서 분기가있는 리프 노드 (Child Views)의 트리 구조와 같기 때문에 순회 단계라고 한다. 따라서 각 메소드는 부모에서 시작하여 마지막 노드까지 순회하여 제약 조건을 정의한다.

따라서 ViewGroup의 depth가 많아질수록 내부에 있는 View에 대해서 접근하는 시간은 더 오래 걸릴 것이고 findViewById또한 많은 시간이 걸릴 것이다. 그래서 ConstrantLayout을 잘 사용하는 것을 추천한다.

참고

https://www.charlezz.com/?p=29013

https://velog.io/@seokzoo/Android-View의-생명주기

Static UserInterFace란 Versus Dynamic User Interface

정확한 차이는 분명하게 나온 것이 없다. 아마 둘의 차이로 동적 생성 vs 정적 생성으로 둘 수 있을 것이다.

우리는 안드로이드 UI를 구성할 때 대표적으로 다음과 같이 2가지 방법으로 UI를 구성할 수 있습니다.

Layout Inflater란

  • xml을 View개체로 인스턴스화한다. 직접적으로 접근하지 않고 getter로 접근한다.
  • context에 연결되어 있는 인스턴스에 대해서 관리해준다.
  • 새로 생성하려면 ViewFactory를 복제해야 하기 때문에 Factory라는 클래스를 사용한다.
  • Thread not Safety (why?) 복제를 하기 때문인듯? Single Thread에서만 엑세스 해야한다. (why?)

1. xml에 선언, 2. activity에서 동적으로 생성

  • xml 선언은 알고 있을테니 건너뛰자. 정적인 컴포넌트를 넘거나 동적 컴포넌트의 뼈대만 선언하는 데에 쓰인다. 장점은 UI와 액티비티 코드를 분리할 수 있다는 것입니다. 또한 런타임시에도 수정이 가능합니다.
  • 기본적으로 xml파일은 런타임시 View 리소스로 컴파일된다. onCreate에서 반드시 레이아웃 inflate를 선언해줘야합니다.

2. 동적으로 생성 아주 간단한 예제

https://dreamaz.tistory.com/319런타임시점에 레이아웃 요소를 인스턴스화합니다 . 앱은 프로그래밍 방식으로 View 및 ViewGroup 개체를 만들고 속성을 조작할 수 있습니다.

  • 해당 텍스트뷰의 위치를 Activity상에서 바꿔줄 것이다. 위 아래를 바꾸는 과정

```xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
 
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView1"
        android:layout_centerVertical="true"
        android:textSize="30sp"
        android:background="#ff0000"
        />
 
    <TextView
        android:id="@+id/tv2"
        android:layout_below="@id/tv"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:text="TextView2"
        android:textSize="30sp"
        android:background="#00ff00"
        />
 
</RelativeLayout>
```

Java 코드긴 하지만 이해할만할것이다. above를 통해서 위치가 바뀌는 것이다.

public class MainActivity extends AppCompatActivity {
 
    TextView tv;
    TextView tv2;
 
    RelativeLayout.LayoutParams layoutParams;
    float dp;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        tv = findViewById(R.id.tv);
        tv2 = findViewById(R.id.tv2);
 
 
        dp =  getResources().getDisplayMetrics().density; //pixel to dp로 변환하기 위함
        layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, (int)(50*dp));
        layoutParams.addRule(RelativeLayout.ABOVE,R.id.tv);
        tv2.setLayoutParams(layoutParams);
    }
}

따라서 Static User Interface도 동적으로 생성이 가능하다. 차이는 뭘까?

  • 자료를 찾아봐도 잘 못 찾겠어서 주관적인 의견을 담자면 동적 UI, 정적 UI의 차이는 사용목적에 따른 분류라고 생각한다.
  • 예를 들면 LinearLayout만을 사용해서 내부 요소를 굳이 늘리거나 줄일 필요가 없다. 왜냐하면 더 좋은 라이브러리인 RecyclerView까 있기 때문이다. Static User Interface는 오직 동적 생성을 위한 기본 요소로 사용되기 때문이라고 생각한다.