드로잉과 이벤트 처리를 담당하는 UI 구성요소의 기본 클래스로, View 클래스를 직접 상속하여 커스텀 뷰를 만들거나, View 클래스의 하위 클래스 중 하나를 상속하여 특정 기능을 하는 위젯(컴포넌트)를 만들 수 있다.
액티비티가 포커스를 받게되는 시점 : onCreate()
Android에게 View 계층의 루트 노드를 전달하는 방법 : setContentView()
루트 노드 : 트리에서 부모가 없는 최상위 노드, 트리의 시작점
리프 노드 : 루트 노드를 제외한 차수가 1인 정점을 뜻한다. 쉽게 말해 자식이 없는 노드. 단말 노드라 부르기도 한다.
View 클래스를 상속받아 여러 개의 자식 View나 다른 ViewGroup을 포함하고 배치하는 컨테이너 역할을 하는 클래스로, 자식 View들의 위치와 크기를 결정합니다.
measure(int, int) : 뷰가 얼마나 커야 하는지 알아보기 위해 호출되며, 뷰의 실제 측정 작업은 measure 메소드에 의해 호출되는 onMeasure(int, int)에서 수행된다.
하향식 순회 : 각 재귀 호출에서 먼저 노드를 방문하여 일부 값(너비와 높이에 대한 제약 조건 정보)을 생성하고, 함수를 재귀 호출할 때 이 값을 하위 노드에게 전달하는 것을 의미한다.
치수 사양 : 뷰의 크기와 위치를 결정하는데 필요한 정보로, 너비와 높이, 그리고 뷰의 위치를 결정하는 데 사용되는 여러 속성들로 구성된다.
- Ex) 너비 = layout_width, 높이 : layout_height, 그외 : layout_margin, layout_padding 등
LayoutParams 클래스 : 자식 뷰가 너비와 높이 양면에서 얼마나 크게 보이길 원하는지를 부모 뷰에게 설명하는 클래스
- 정확한 숫자값 : dp, sp 등
- MATCH_PARENT : 부모 뷰 크기에 꽉 맞출 때
- WRAP_CONTENT : 자신의 내용물 크기에 맞출 때
MeasureSpec 클래스 : 자식 뷰가 어떤 크기로 그려질 것인지 결정하는 데 사용되는 클래스
- UNSPECIFIED : 부모 뷰가 자식 뷰에게 어떤 크기 제약도 주지 않는다.
- 즉, 자식 뷰는 원하는 크기대로 지정할 수 있다.
- EXACTLY : 부모가 자식 뷰에게 정확한 크기를 결정한다.
- 즉, 자식 View의 사이즈와 관계없이 주어진 경계 내에서 사이즈가 결정된다.
- AT_MOST : 자식 뷰는 지정된 크기까지 원하는 만큼 커질 수 있다.
// 1
public View(Context context) { .. }
fun createDynamicView(context: Context) {
val layout = LinearLayout(context)
val newView = View(context)
newView.setBackgroundColor(Color.RED)
newView.layoutParams = LinearLayout.LayoutParams(200, 200)
layout.addView(newView)
}
// 2
public View(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
// xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/myView"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#FF0000" />
</LinearLayout>
// kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.sample_layout)
val myView = findViewById<View>(R.id.myView)
...
}
// 3
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
// kotlin
class CustomView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, R.style.CustomViewDefaultStyle)
}
// styles.xml
<resources>
<style name="CustomViewDefaultStyle" parent="android:Widget">
<item name="android:background">#FF0000</item>
<item name="android:layout_width">100dp</item>
<item name="android:layout_height">100dp</item>
</style>
</resources>
// themes.xml
<resources>
<style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="customViewStyle">@style/CustomViewDefaultStyle</item>
</style>
</resources>
// xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.myapp.CustomView
android:id="@+id/myCustomView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
// 4
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { .. }
// xml
<resources>
<style name="CustomViewStyle" parent="android:Widget">
<item name="android:background">#FF0000</item>
</style>
</resources>
// kotlin
class CustomView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, R.style.CustomViewStyle)
}