[kotlin] 기초 및 응용

·2025년 4월 26일

just공부

목록 보기
21/47

3. 안드로이드 APP 개발 기초

안드로이드 APP 프로젝트 구조

주요 구성 요소

파일 또는 폴더명설명
AndroidManifest.xml안드로이드 앱 설정 정보 파일
앱의 구성요소 및 속성 등을 정의 (시작 액티비티 지정) -> OS한테 전해짐
패키지/ MainActivity (.kt)자동으로 추가하는 기본 액티비티의 구현(코틀린) 파일
-> 앱 화면의 기능을 구현
res/layout/activity_main.xml자동으로 추가하는 기본 액티비티의 레이아웃 XML 파일
res/menu 폴더앱에서 사용할 메뉴의 구성 정의
res/ mipmap 폴더앱에서 사용하는 아이콘 이미지 파일을 화면 밀도(density)별로 생성 (새로 추가됨)
res/ drawable 폴더앱에서 사용할 이미지 파일을 저장
res / values 폴더앱에서 사용할 값(문자열, 수치값, 색상값 등)을 XML 파일로 저장
build.gradle (Module: ...)앱을 빌드하기 위한 설정정보 파일
필요한 외부 라이브러리 등의 정보를 기록

Build 시 R.class 생성

  • R.class (R.jar) : Build 시 자동 생성 -> (resource)

Resource의 사용

R.class

  • 화면에 지정한 자원 등은 소스코드에서 참조
  • 빌드 시 [res] 폴더의 요소들은 적절한 Java 클래스의 객체로 자동 변환 및 생성 ([raw]폴더의 이미지 등 일부 제외)
  • 생성한 Java 객체의 ID(정수형 상수)는 R.class에 등록
  • 소스코드(MainActivity 등)에서 R.class 의 ID를 사용하여 res의 항목에 접근

화면 요소 구현의 참고사항

XML 화면 구현의 장점

  • 개발자와 디자이너의 분담 작업 용이
  • 교체 가능하므로 호환성 확보, 국제화 유리
  • 변경된 모듈만 컴파일하므로 개발 속도 개선
  • 레이아웃 재활용 가능
  • 정적인 레이아웃은 XML
  • 동적인 레이아웃은 코드로 구현

4. 안드로이드 인터페이스 기초 - 뷰(View)

안드로이드 화면 구성

  • 앱을 구성하는 하나의 화면은 하나의 Activity가 관리
  • 화면은 다른 View들을 내부에 담는 ViewGroup과
  • 화면요소를 구성하는 Widget으로 구성
  • View는 주로 XML로 선언

위젯(Widget)

화면 입출력 요소를 표현하는 뷰

  • View
    • TextView
      • EditText
      • Button
        • CheckBox
        • RadioButton
        • ToggleButton
    • ImageView
      • ImageButton

뷰그룹 (ViewGroup)

다른 뷰 또는 뷰그룹을 Grouping 및 배치할 때 사용하는 뷰

  • ViewGroup
    • FrameLayout
      • ScrollView, HorizontalScrollView
      • TabHost, TimePicker
      • ViewAnimator
        • ViewFlipper
        • ViewSwictcher
    • RelativeLayout
    • LinearLayout
      • RadioGroup, ZoomControls
      • TableLayout, TableRow
      • TabWidget
    • ConstraintLayout
      • AdapterView
        • AbsListView
          • ListView
          • GridView
        • AbsSpinner
          • Gallery
          • Spinner

View 공통(필수) 속성

[ID] : 특정 뷰를 참조하기 위한 식별정보

  • [activity_main.xml] 열기
  • 형식
    • @[+]id/view_id : + 는 ID를 새로 지을 때만 표시
  • [TextView]의 경우
    • id : textView
    • android:id="@+id/textView"
  • [layout_width] / [layout_height]
    • 뷰그룹 안에서의 가로/세로 너비 지정
    • match_parent : 소속된 뷰그룹 크기에 맞춤
    • wrap_content: 뷰 내부 항목의 크기에 맞춤
    • 상수값 : 값만큼 크기 고정 (dp 또는 dip 단위 사용)
  • background : 뷰의 배경 지정
    • 속성 : visible, invisible, gone
  • padding : 뷰와 내용물 사이의 간격
    • 예 ) 버튼 안의 텍스트
  • visibility : 가시성 결정
  • clickable / longClickable : 클릭/롱클릭 이벤트 반으 ㅇ여부
  • focusable : 키보드 입력 가능 여부

View 배치 시 참고사항

ConstraintLayout 경우

  • 프로젝트 생성 시 activity_main.xml 의 기본 레이아웃으로 지정되어 있음
  • 화면 배치 후 반드시 상/하 중 하나 이상, 좌/우 중 하나 이상을 다른 요소와 연결

이미지 소스의 지정

res/mipmap 폴더

  • 앱 아이콘 지정 폴더
  • 동일한 아이콘을 여러 해상도로 해상도별 폴더에 저장

res/drawable 폴더

  • 앱에서 사용할 이미지를 저장하는 폴더

이미지 파일명 지정 시 주의사항

  • 투명도 등을 조종하기 위해서 가능하면 png 파일 사용
  • 이미지 파일명이 아이디가 되므로 반드시 명명 규칙 준수
    • 영어소문자, 밑줄, 숫자만 가능(한글X, 대문자X)
    • 확장자로 구분하지 않으므로, 파일명을 다르게 사용하여야 함
  • 명명 규칙은 res 폴더의 모든 파일에도 동일 적용

기본 위젯 - Button

  • 사용자 클릭 이벤트 처리

주요 속성

  • text : 버튼에 표시할 문자열
  • onClick : 버튼을 클릭했을 때 동작할 메소드명, 개발자가 임의로 지정(자바 소스코드부분에 작성)
  • onClick 메소드 구현 : fun 메소드명(매개변수 : View) { ... }

기본 위젯 - EditText

  • 사용자의 키보드 입출력 처리

EditText에 입력한 문자열을 코드에서 처리

  • XML 의 EditText(id: editText) 객체를 찾아 지정
    • val editText = findViewById<EditText>(R.id.editText)
  • EditText 객체 속성 [text]를 사용하여 현재 쓰여진 값을 읽어옴 -> getter 쓰지 않음
    • val text : String = editText.text.toString()
    • .toString() 으로 String 변환 -> text는 Editable 객체 반환

토스트(Toast)

  • 화면 하단에 일정시간 동안 나타나는 안내용 출력창
    • 간단한 상태 정보 표시
    • 디버깅 시 값 확인용으로도 사용 가능
Toast myToast = Toast.makeText(this, "Hello", Toast.LENGTH_SHORT);
myToast.show();

Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show();
// this : Context 객체 (Activity 상속)
//"Hello" : 출력 메시지
// Toast.LENGTH_SHORT : 출력 시간 (or LENGTH_LONG)
// show() 를 실행시켜야 화면에 표시됨

5. 레이아웃 (Layout)

Layout

View들을 내부에 배치하는 컨테이너

  • View를 내부에 배치하여 다양한 화면 구성
  • 일반적으로 화면 상에 직접 보이지 않음
  • ViewGroup도 하나의 View 취급
  • 주요 Layout
    • LinearLayout/RelativeLayout/FrameLayout/GridLayout
    • 추가 레이아웃 : ConstraintLayout

레이아웃 추가 및 변경

  • MainActivity에서 추가한 layout으로 변경
  • setContentView(R.layout.activity_linear)

LinearLayout 1

  • 가장 간단한 레이아웃으로 가로 또는 세로의 순서대로 항목을 배치

주요 속성

  • orientation : vertical, horizontal
    • 내부 View의 배치 방향 결정
  • gravity : 내부 View(또는 값) 의 수직/수평 배치 결정
    • 내가 담고 있는 것을 정렬하겠다는 것 (-> 자신이 담는 입장)
  • layout_gravity : 레이아웃에 View 자신의 수직/수평 방향 배치 결정
    • layout이 붙은 속성은 본인이 layout 어딘가에 담길 때, 이렇게 되면 좋겠다는 것임.
    • 자신이 담겨지는 입장
<Button
	android:id="@+id/button1"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_gravity="center_horizontal"
	android:gravity="top"
	android:text="Button" />
  • baselineAligned : 레이아웃에 배치한 View의 아래 부분 맞춤 활성화 여부
  • layout_weight : 레이아웃 공간을 어느 정도 비중으로 차지하느냐를 결정
    • 0일 경우 : 본래 크기
    • 1 이상 : 다른 뷰와의 비율에 따라 레이아웃에 배치
  • layout_margin : 레이아웃과 뷰 사이의 간격
  • padding : 뷰와 내부 내용물 사이의 간격

RelativeLayout

  • 뷰와 뷰를 담고 있는 레이아웃(부모 뷰), 그리고 다른 뷰와의 상대적인 위치 관계로 배치 => ID 필수
<Button
	android:id="@+id/btn1"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_alignLeft="@+id/btn2"
	android:layout_alignParentTop="true"
	android:layout_marginLeft="78dp"
	android:layout_marginTop="50dp"
	android:text="ButtonA" />
<Button
	android:id="@+id/btn2"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_alignParentLeft="true"
	android:layout_below="@+id/btn1"
	android:layout_marginLeft="97dp"
	android:layout_marginTop="43dp"
	android:text="ButtonB" />

FrameLayout

  • 레이아웃의 좌측 상단에 모든 뷰들을 겹쳐서 배치
  • 앱 실행 중 addView/removeView 메소드를 사용하여 뷰들을 추가 및 삭제
  • 뷰의 visibility 속성을 사용하여 한 화면에서 여러 화면을 번갈아 보여주고 싶을 때 사용
    • 나머지 속성들을 감춰놓고, 보여줄 수 있음
    • 탭 구현할 때, 프레임 레이아웃을 응용 가능
    • 버튼 눌렀을 때 하단 화면이 바뀔 때 등등

레이아웃의 중첩과 사용

레이아웃의 중첩

  • 레이아웃 안에는 뷰 배치 뿐만 아니라 다른 레이아웃을 중첩하여 배치 가능 -> 레이아웃 == 뷰
    • Design 화면의 Component Tree 에서 관리 용이

레이아웃의 사용

XML 활용

  • 레이아웃 및 View를 XML로 지정
  • 지정한 XML 파일을 setContentView(...)에 전달하여 Activity에 배치

직접 구현

  • 코드로 화면 객체를 직접 구현
class MainActivity : AppCompatActivity(){
	override fun onCreate(savedInstanceState: Bundle?){
		super.onCreate(savedInstanceState)

		val layout = LinearLayout(this).apply{ // 초기화 객체 
			orientation = LinearLayout.VERTICAL
			gravity = Gravity.CENTER
		}
		val btn1= Button(this)
		val btn2 = Button(this)
		layout.addView(btn1) // add : linearlayout에 버튼 만들어줌, 오버로딩한 것
		layout.addView(btn2)

		setContentView(layout)
	}
}

실행 중 레이아웃의 속성 변경

  • 앱 실행 중 View 속성 변경
class MainActivity: AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?){
		super.onCreate(savedInstanceState)
		steContentView(R.layout.activity_linear)
	}
	fun onClick(view: View) {
		val layout = findViewById<LinearLayout>(R.id.linearLayout)
		layout.orientation = LinearLayout.HORIZONTAL
	}
}
  • setContentView(...) : 액티비티 관리 화면에 View 배치, XML 파일명을 전달받을 경우 Inflation 수행
    • Inflation : XML을 토대로 실제 객체를 생성하는 작업
    • Inflation 이후 생성한 객체를 지정한 ID로 찾는 함수
      • -> findViewById(R.id.ID)

View 객체 사용1 - ViewBinding

1. findViewByID<TYPE>(ID) 사용

  • View의 ID를 사용하여 View 객체 확인
  • 찾고자 하는 객체가 없을 경우 Null 반환하므로 주의
fun onClick(view: View){
	val textView = findViewByID<TextView>(R.id.textView) // 지역변수이므로 재사용 불가, 
	// 멤버변수 선언시 불필요한 멤버 증가
	textView.text= "Hello"
}

2. ViewBinding의 적용

  • XML 선언 View 객체를 손쉽게 사용하도록 추가
  • XML 의 ID 명과 객체명이 대응
  • 사전 준비 필요
    1. build.gradle(Module:app) 수정
    2. 수정 후 Sync Now 수행
	android{
	...
	}
	viewBinding{ // 추가 !!!
		enable = true 
	}

3. ViewBinding 을 활용한 XML View의 사용

  • Binding 객체 -> XML View를 표현
class MainActivity : AppCompatActivity() {
	lateinit var binding = ActivityMainBinding // Binding 객체 선언: 타입 자동 생성
	// activity_main.xml -> ActivityMainBinding
	override fun onCreate(savedInstanceState : Bundle?){
		super.onCreate(savedInstanceState)
	
		binding = ActivityMainBinding.inflate(layoutInflater) // Binding 객체 생성: XML의 View와 Binding 객체 연결
		val root = binding.root // 최상위 레이아웃 객체
		setContentView(root) // 액티비티 화면에 보여줄 객체 지정
	}
	fun onClick(view: View){
		binding.textView.text = "Hello!" // binding: Binding객체, textView: XML View의 ID, text: View의 속성
	}
}

6. View의 상속 (View Inheritance)

View의 상속

개요

  • 기존의 View를 상속 받아 새로운 View(Custom View)를 구현하고자 할 경우

상속 구현 방법

  • 내부/외부 클래스로 클래스 추가
  • 상속 생성자 모두(4개) 재정의
  • 필요 함수 재정의
  • Layout XML 코드로 추가 -> 추가 후 Design에서 조정

View 상속 Custom View 구현

클래스 상속 및 재정의

  • 외부 클래스 또는 MainActivity 내부 클래스로 구현
  • 클래스 추가 시 @JvmOverloads 추가
import android.view.View // View import 수행
// 수행 후 alt+enter 로 @JvmOverloads 추가

class MyCustomView @JvmOverloads constructor(
	context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

	override fun onDraw(canvas: Canvas){ // onDraw() : 뷰 모양을 그리는 함수
		super.onDraw(canvas)
		canvas.drawColor(Color.LTGRAY) // canvas : 모양을 그리는 공간
		val paint = Paint() // paint : 그리기 도구
		paint.color = Color.GREEN
		canvas.drawCircle(200.toFloat(), 200.toFloat(), 100.toFloat(), paint)
	}
}
  • @JvmOverloads : 디폴트 값을 사용하여 여러 버전의 생성자(또는 함수)를 자동으로 생성

레이아웃 XML에 추가

  • 코드에서 직접 추가
<?xml version="1.0" encoding="utf-8"?>
<androidx.contraintlayout.widget.ConstraintLayout xml ... 
	...
	<TextView .../>
	<ddwu.com.mobile.customviewtest.MyCustomView  // 풀패키지명으로 클래스 지정
		android:id="@+id/myView"
		android:layout_width="100dp"
		android:layout_height="100dp" /> // 너비 높이 필수 지정, 편의를 위해 임의 크기 지정
</androidx.constraintlayout.width.ContraintLayout>
  • Build 후에는 Palette의 Project 탭에서 확인 가능

Custom View 그리기

Canvas 클래스

  • View가 그려지는 영역 담당 클래스
  • Canvas의 멤버함수를 사용하여 화면에 그리기 수행
  • View의 멤버 onDraw()의 매개변수로 전달됨
  • 기본 도형 출력 가능
//점그리기
canvas.drawPoint(x: Float, y:Float, paint: Paint)
// 선 그리기
canvas.drawLine(startXLFloat, startY:Float, stopX: Float, stopY:Float, paint:Paint)
//원그리기
canvas.drawCircle(s:Float, y:Float, radius:Float, paint:Float)
// 사각형 그리기
canvas.drawRect(left:Float, top: Float, right: Float, bottom: Float, paint: Float)
//텍스트 그리기
canvas.drawText(text:String, x: Float, y: Float, paint: Float)

Paint

  • Canvas에 그림을 그리는 도구 표현 클래스
  • 그리기 도구에 대한 속성 지정
val paint = Paint() // Paint 객체 생성
// val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.isAntiAlias = true // Anti-Alias 지정
paint.setARGB(255, 255, 255, 0) // 색상 및 투명도 지정
// paint.color = Color.GREEN
paint.strokeWidth = 5.0f // 그리기 선의 두께 지정

Custom View 그리기 적용

Custom View의 그리기 변경 절차

  • View 클래스 상속 후 생성자, onDraw() 재정의
  • onDraw() 에 새로운 그리기 지정
    • Canvas에 어떠한 형식으로 그릴지 Paint 객체로 지정
    • 매개변수로 전달 받은 canvas의 함수를 사용하여 그리기 수행
    • CustomView 객체를 생성
    • 화면을 다시 그릴 필요가 있을 때 그리기 방법 변경 후
      -> CustomView.invalidate() 호출
    • 화면을 무효화 한 후 onDraw()가 호출되어 화면이 다시 그려짐

View를 새로운 모양으로 다시 그리고자 할 때

  • 클래스 수정 - ex) 크기 변경 -> 반지름 멤버변수 추가
class MyCustomView @JvmOverloads constructor ( ..){
	var radius = 100.0f // 그리기 관련 정보 멤버변수 추가
	override fun onDraw(canvas: Canvas){
		...
		canvas.drawCircle(200.toFloat, 200.toFloat, radius, paint)
	}
}
  • 원의 크기를 변경하여 다시 그리기
class MainActivity : AppCompatActivity(){
	...
	override fun onCreate(savedInstanceState: Bundle?){
		super.onCreate(savedInstanceState)
		binding = ActivityMainBinding.inflate(layoutInflater)
		setContentView(binding.root)
	
		binding.myView.radius = 200.0f // 멤버변수 값 변경
		binding.myView.invalidate() // invalidate() 호출을 통해 onDraw( ) 간접 호출
	}
}

7. 안드로이드 이벤트(Event) 처리

Event-Driven Programming

개요

  • 프로그램의 실행 흐름이 사용자 동작과 센서 입출력 등 외부 이벤트에 따라 결정되는 프로그래밍 방식
  • GUI(Graphic User Interface) 프로그램

이벤트 처리(구현) 방식 유형

상속 후 기본 이벤트 처리 함수 재정의

  • 콜백(Callback) 함수 형태
  • View를 상속할 경우 이벤트 처리 함수를 재정의하여 기능 구현

리스너 인터페이스 구현 (기본)

  • [이벤트 처리 리스너] 객체를 구현하여 이벤트 처리가 필요한 View에 등록
  • Listener는 이벤트 핸들러 함수를 보유

위젯 이벤트 처리

  • 클릭, 롱클릭과 같이 자주 사용하는 기본 이벤트
  • XML의 View 속성에 처리 함수 명을 등록한 후 구현

1. 이벤트 처리 함수 재정의

개요

  • 뷰가 기본으로 갖고 있는 이벤트 처리 함수를 재정의

View의 대표적인 이벤트 처리 함수

  • override fun onTouchEvent(event: MotionEvent?) : Boolean
  • override fun onKeyDown(keyCode: Int, event: keyEvent?) : Boolean
  • override fun onKeyUp(keyCode: Int, event: keyEvent?) : Boolean
    • onKeyDown/onKeyUp : 키보드 누를 때 / 키보드에서 뗄 때

고려사항

  • 뷰를 상속하여 커스텀 뷰를 구현할 때만 가능
  • 모든 이벤트 종류에 대한 함수가 정의되어 있지는 않음

MyCustomView의 onTouchEvent() 재정의

class MyCustomView @JvmOverloads constructor(
	context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr){
	var posX = 200.0f
	var posY = 200.0f
	var radius = 100.0f
	var color = Color.GREEN

	override fun onDraw(canvas: Canvas){
		super.onDraw(canvas)
		canvas.drawColor(Color.LTGRAY)
		val paint = Paint()
		paint.color = color
		canvas.drawCircle(posX, posY, radius, paint)
	}
	override fun onTouchEvent(event: MotionEvent?) : Boolean { // 상속받은 onTouchEvent() 재정의
		Toast.makeText(context, "Touch!", Toast.LENGTH_SHORT).show()
		Log.i("MyCustomView", "x: ${event?.x} y: ${event?.y}")
		return true // 이벤트 처리 완료했을 경우 true, 다른 처리가 필요할 경우 false
	}
}
  • MycustomView 내부를 터치할 경우 해당 위치에 원이 나타나게 변경
    -> activity_main.xml의 focusableInTouchable 활성화시켜야 함
    -> 그림 다시 그릴 때 invalidate() 해주기

2. 리스너 인터페이스 구현

기본 UI 이벤트 처리 방법

  • 뷰에 필요한 UI 이벤트 처리 리스너를 구현하여 뷰에 연결

UI 이벤트 처리 리스너 인터페이스

  • -> 객체 생성 X
  • 기본적으로 이벤트 처리하는 애
  • 각각의 이벤트 별로 준비되어 있음
  • 하나의 인터페이스 당 하나의 구현 함수가 존재
    -> 코틀린의 SAM(Single Abstract Method) 적용 가능
  • View.OnClickListener : fun onClick(..)
  • View.OnKeyListener : fun onKey(..)
  • View.OnLongClickListner : fun onLongClick(..)

적용 절차

  • 구현하고자 하는 UI 이벤트 처리 리스너 인터페이스를 구현 -> 인터페이스 구현 클래스 작성
  • 리스너 객체 생성
  • 생성한 객체를 이벤트 처리가 필요한 뷰에 등록

리스너 인터페이스 명명 규칙 이용

  • 리스너 인터페이스 : OnClickListener
  • 인터페이스 구현 함수 : onClick
  • 뷰의 리스너 등록 함수 : View.setOnClickListener

리스너 인터페이스 구현 유형

  • A : 별도의 리스너 구현 클래스 작성
  • B : 액티비티에서 직접 리스너 구현
    • 커스텀 뷰일 경우 해당 뷰가 리스너를 구현하는 것도 가능
  • C : 익명 객체(object)로 구현
  • D : SAM 적용 : 인터페이스 선언 없이 함수만 적용
    • 사용이 약속되어진 객체가 함수가 하나만 갖고 있을 경우 객체를 생략하고 함수의 본체만 적용

A. 별도의 리스너 인터페이스 구현 클래스 작성

class MainActivity : AppCompatActivity(){
	lateinit var binding = ActivityMainBinding

	override fun onCreate(savedInstanceState: Bundle?){
		super.onCreate(savedInstanceState)
		...
		binding = AcitvityMainBinding.inflate(layoutInflater)
		setContentView(binding.root)
		...
		val myClick = MyClick(this)
		binding.button.setOnClickListner(myClick) // 객체 생성 후 이벤트 처리가 필요한 View에 연결
	}
	class MyClick(val context: Context) : View.OnClickListener { // 클릭에 해당하는 OnClickListener 인터페이스
		override fun onClick(view : View?){
			Toast.makeText(context, "리스너 인터페이스 클래스 구현)", Toast.LENGTH_SHORT).show()
		}
	}
}

B. 액티비티에서 직접 리스너 인터페이스 구현

class MainActivity : AppCompatActivity(), View.OnClickListener{ // 리스너 인터페이스 상속
	lateinit var binding : ActivityMainBinding
	
	override fun onCreate(savedInstanceState: Bundle?){
		super.onCreate(savedInsatanceState)
		binding = ActivityMainBinding.inflate(layoutInflater)
		setContentView(binding.root)
		...
		binding.button.setOnClickListener(this) // 현재 액티비티가 리스너 인터페이스 구현 객체이므로 this 사용
	}
	override fun onClick(view: View?){ // 상속한 리스너 인터페이스의 멤버함수를 액티비티에서 구현
		Toast.makeText(this, "액티비티가 리스너 인터페이스 구현", Toast.LENGTH_SHORT).show()
	}
}

C. 익명 객체로 리스너 인터페이스 구현

class MainActivity : AppCompatActivity() {
	lateinit var binding : ActivityMainBinding

	override fun onCreate(savedInstanceState: Bundle?){
		super.onCreate(savedInstanceState)
		binding = ActivityMainBinding.inflate(layoutInflater)
		setContentView(binding.root)
		...
		binding.button.setOnClickListener(myClick) // 객체 연결
	}
	val myClick = object: View.OnClickListener { // 리스너 인터페이스에서 직접 객체 생성
		override fun onClick(view: View?){ // object : 상속 클래스 구현 없이 기존 클래스 또는 인터페이스를 구현한 객체 생성
			Toast.makeText(this@MainActivity, "익명 객체로 리스너 인터페이스 구현", Toast.LENGTH_SHORT).show()
		} // this@MainActivity: this키워드가 MainActivity 객체가 아닌 
		// object에 의해 생성된 객체를 지시하므로 MainAcitivity로 명확히 지정
	}
}

D. SAM(Single Abstract Method) 적용

  • 인터페이스의 함수가 하나일 경우에만 적용 가능 -> 리스너 인터페이스에 적용 가능
class MainActivity : AppCompatActivity(){
	lateinit var binding : AcitivityMainBinding

	override fun onCreate(savedInstanceState: Bundle?){
		super.onCreate(savedInstanceState)
		binding = ActivityMainBinding.inflate(layoutInflater)
		setContentView(binding.root)
		...
		binding.button.setOnClickListener{ // (  ) 가 생략된 형태임.
			Toast.makeText(this, "SAM 적용 형태로 구현", Toast.LEGNTH_SHORT).show()
		} // View.setOnClickListener에는 OnClickListener의 단일 함수를 전달하게 약속되어 있으므로 객체 및 함수명 생략 가능
	}
}
  • .setOnClickListener 앞에 ( ...) 가 생략된 형태인데 생략 가능한 이유는
  • View.setOnClickListener에는 OnClickListener의 단일 함수를 전달하는 것이 당연하게 약속된 것,
  • 즉 이미 지정되어 있기 때문에 생략이 가능한 것임.

3. 위젯 이벤트 처리

뷰의 XML 속성으로 이벤트 처리 지정

  • 이벤트를 처리하는 뷰(위젯)의 XML [onClick] 속성에 이벤트를 처리할 함수명 등록

이벤트 처리의 우선 순위

하나의 이벤트에 대하여 여러 이벤트 리스너를 작성할 경우의 우선 순위

  • 구현한 이벤트 리스너가 있을 경우 우선 수행
  • 뷰 자체의 콜백 함수를 구현하였을 경우 수행(커스텀 뷰 사용 시)
  • 액티비티에 콜백 함수를 구현하였을 경우 수행

이벤트 리스너의 반환값 설정

  • 반환값이 있는 이벤트 리스너의 경우
    • true 반환 : 이벤트 처리 종료로 간주 -> 추가적인 이벤트 처리 진행을 생략
    • false 반환 : 이벤트 처리를 다음 순위의 리스너에게 전달하여 이벤트 처리 진행

Logcat의 사용

개요

  • 각종 로그, 디버그 정보 출력 및 확인

사용 방법

  • Log.? (TAG, 출력문자열) // ? : d, e, w, i, v 등
    • TAG : 보통 클래스 이름을 TAG로 많이 적음
import android.util.log // Log 클래스 패키지
class MainActivity : AppCompatAcitvity() {
	val TAG = "MainActivity" // Log의 TAG 문자열 보편적으로 클래스명 기재
	...
	override fun onCreate(savedInstanceState: Bundle?){
		...
		binding.button.setOnClickListener{
			Toast.makeText(this, "SAM 적용 ", Toast.LENGTH_SHORT).show()
			Log.i(TAG, "SAM 적용 형태로 구현") // 로그를 사용하여 필요 정보 출력
		}
	}
}

8. 대화상자(Dialog)의 사용

대화상자

개요

  • 사용자에게 정보를 전달하거나 선택을 입력 받는 용도
  • Toast와 달리 정보 전달의 경우에도 확인 필요 -> 확인버튼

Dialog 클래스

  • 안드로이드의 기본 대화상자 클래스
  • 대화상자의 모든 기능을 제공하나 세세한 제어 필요 -> 사용 지양

AlertDialog 클래스

  • Dialog 하위 클래스로 간단한 설정을 통해 대화상자 생성
  • 내부 클래스인 [AlertDialog.Builder] 클래스를 활용하여 대화상자 객체 생성

DatePickerDialog 또는 TimePickerDialog 클래스

DialogFragment 클래스

  • 다이얼로그 관리를 위해 사용하는 Fragment
  • 생명주기 통합/재사용/화면이동 관리 등이 필요할 경우 사용

AlertDialog의 사용

override fun onCreate(savedInstanceState: Bundle?){
	...
	binding.button.setOnClickListener{
		val builder: AlertDialog.Builder = AlertDialogBuilder(this).apply{ // 객체.apply{...} : 코틀린 범위지정함수, 
														// 객체 생성후  필요 set 수행
			setTitle("대화상자 제목") // 대화상자 제목
			setMessage("대화상자 메시지") // 대화상자 본문 메시지
			setIcon(R.mipmap.ic_launcher) // 제목 옆 아이콘
			setPositiveButton("확인", posClick) // 확인 버튼 추가
			setNeutralButton("대기", null) // 대기 버튼 추가
			setNegativeButton("취소", null) // 취소버튼 추가
			setCancelable(false) // 대화상자 외부/back 버튼을 클릭해도 대화상자 유지
		}
		val dialog: Dialog = builder.create() // 대화상자 생성
		dialog.show() .. 대화상자 표시
		// builder.show()  -> 위에 2줄을 이렇게 한 번에 표시 가능
	}
}
  • 이벤트 처리 매개변수에 null 전달 시 -> 종료 또는 이벤트 처리 리스너에서 dismiss() 호출
  • this.setTitle() : 이런식으로 앞에 this가 생략된 형태
  • set~ : builder 객체의 유형
  • 버튼을 누를 때마다 새로 생성되는 dialog임

AlertDialog 버튼 이벤트 처리

이벤트 리스너 구현 (SAM)

// 1번
val posClick = DialogInterface.OnClickListener{ // 사용하는 리스너 인터페이스 표시
	dialogInterface: DialogInterface?, whichButton: Int ->  // Lambda 함수 형식 사용 시
		Toast.makeText(this@MainActivity, "확인!", Toast.LENGTH_SHORT).show()
}
// 2번
binding.button.setOnClickListener{
	val builder: AlertDialog.Builder = AlertDialog.Builder(this).apply{
		...
		setPositiveButton("확인", posClick)
		setNeutralButton("대기", null)
		setNegativeButton("취소", { di, _ ->
			Toast.makeText(this@MainActivity, "종료!", Toast.LENGTH_SHORT).show() // 이벤트 핸들러 직접 추가
			di.cancel()
		})
		setCancelable(false)
	}
}

인터페이스의 매개변수를 사용하지 않을 경우 축약

val posClick = DialogInterface.OnClickListener{
	_, _ ->
	Toast.makeText(this@MainActivity, "확인", Toast.LENGTH_SHORT).show()
}

대화상자 처리 시 유의점

  • 대화상자를 표시하면 대화상자가 닫히기 까지 프로그램은 실행 대기 -> 대화상자를 닫은 이후에 동작
  • 파일 열기 등의 일반적인 대화상자 방식
    -> 대화상자가 뜨면 프로그램이 대기함(동작하지 않음)

Modaless 대화상자

  • 대화상자가 떠도 프로그램은 계속 실행 -> 굳이 닫지 않아도 동작 가능
  • 찾기(Ctrl+F) 대화상자와 같은 대화상자 방식

Android의 대화상자는 실행 흐름을 막지 않으므로 사용시 실행 흐름을 확인 후 사용 필요

-> modal도 modaless도 아님

  • finish() 를 제대로 작동시키려면 , 버튼을 함수 안으로 꼭 넣어주어야 함

목록 대화상자

대화 상자의 message 대신 목록 출력

출력 대상

  • 문자열 배열 또는 XML로 지정한 string-array
  • string-array 추가
    • res -> values 우클릭 -> [New] -> [Values Resources File] 선택
    • 파일명 : arrays.xml로 지정

목록 대화상자 구현

  • setMessage() 삭제 -> setItems(리소스ID, 이벤트핸들러) 사용
  • 목록 이벤트 핸들러 : DialogInterface.OnClickListener로 동일 -> 두 번째 매개변수가 어떤 항목을 클릭했는지 표현(index)
binding.btnList.setOnClickListener{
	val builder: AlertDialog.Builder = AlertDialog.Builder(this).apply{
		setTitle("목록 대화상자") // 대화상자 제목
		setIcon(R.mipmap.ic_launcher) // 제목  옆 아이콘
		setItems(R.arrays.foods){ _, index ->
			val foods = resources.getStringArray(R.array.foods) // res에 작성한 리소스를 ID를 사용하여 가져옴
			Log.i(TAG, "Selected Item: ${foods[index]}")
		}
		setPositiveButton("닫기", null)
	}
	builder.show()
}

선택 목록 대화상자

대화상자에 라디오 버튼 목록 표시

  • setSingleChoiceItems(리소스ID, 선택IDX, 이벤트핸들러)
  • 항목 선택 인덱스를 별도의 변수로 보관하여 선택정보 유지
var selectedIndex = 0 // 선택 항목 인덱스
binding.btnRadio.setOnClickListener {
	val builder: AlertDialog.Builder = AlertDialog.Builder(this).apply{
		setTitle("선택 대화상자")
		setIcon(R.mipmap.ic_launcher)
		setSingleChoiceItems(R.array.foods, selectedIndex) {
			_, index -> selectedInex = index // 현재 선택 항목 인덱스를 변수에 보관
		}
		setPositiveButton("선택 완료") { _, _ -> 
			val foods = resources.getStringArray(R.array,foods)
			Log.i(TAG, "Selected Item: ${foods[selectedInex]}") // 현재 선택한 항목의 index를 사용하여 선택항목 확인
		}
	}
	builder.show()
}

대화상자에 체크박스 표시

  • setMultiChoiceItems(리소스ID, BOOL배열, 이벤트핸들러) 사용
  • Boolean 배열에 각 항목의 체크 상태 기록
val selectedItems = booleanArrayOf(false, false, false) // 체크항목 상태 기록
binding.btnCheck.setOnClickListener{
	val builder: DialogAlert.Builder = DialogAlert.Builder(this).apply{
		setTitle("다중선택 대화상자")
		setIcon(R.mipmap.ic_launcher)
		setMultiChoiceItems(R.arrays.foods, selectedItems){  // 체크상태 기록
			_ : DialogInterface?, index: Int, isChecked: Boolean -> 
			selectedItems[index] = isChecked // isChecked(true/false) : 현재 항목의 체크 상태
		}
		setPositiveButton("선택 완료"){ _, _ -> 
			val foods = resources.getStringArray(R.arrays.foods)
			for((index, isChecked) in selectedItems.withIndex())
				Log.i(TAG,"Items: ${foods[index]} : ${isChecked}")
		}
	}
	builder.show()
}
  • selectedItems.withIndex() : for문에서 배열의 인덱스와 값 쌍을 차례대로 추출

커스텀 대화상자

대화상자에 작성한 레이아웃 표시

  • 표시할 레이아웃 작성 : [res] -> [layout]우클릭 -> [New] -> [Layout Resource File]
  • dialog_custom.xml 추가 후 편집
  • setView(레이아웃) 사용
val dialogBinding = DialogCustomBinding.inflate(layoutInflater) // 레이아웃 inflation : 대화상자용 레이아웃

binding.btnCustom.setOnClickListener{
	val builder: DialogAlert.Builder = DialogAlert.Builder(this).apply{
		setTitle("대화상자")
		setIcon(R.mipmap.ic_launcher)
		setView(dialogBinding.root) // 레이아웃 사용
		setPositiveButton("입력"){ _, _ ->
			val id = dialogBinding.etId.text.toString() // 레이아웃에 배치한 뷰 활용
			val pwd = dialogBinding.etPwd.text.toString()
			Log.i(TAG, "User : ${id} / ${pwd} ")
		}
	}
	builder.show()
}

DialogFragment 사용

개요

  • 대화상자 컨테이너
    • Androidx.fragment.app.DialogFragment
  • 다이얼로그의 생성/수정/삭제 등 수명주기와 관련한 이벤트를 관리
  • 대화상자의 크기를 바꾸어 표시하는 등 유연성 제공

사용 방법

  • DialogFragment 상속 클래스 구현 후 onCreateDialog()를 재정의 -> 다이얼로그 생성 코드 작성
  • 다이얼로그를 표시하고자 할 때 DialogFragment 객체 생성 후 show() 호출
profile
Whatever I want | Interested in DFIR, Security, Infra, Cloud

0개의 댓글