1.  activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/layout01"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3">

        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:src="@drawable/pic1" />

        <ImageView
            android:id="@+id/imageView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:src="@drawable/pic2" />

        <ImageView
            android:id="@+id/imageView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:src="@drawable/pic3" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout02"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3">

        <ImageView
            android:id="@+id/imageView4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:src="@drawable/pic4" />

        <ImageView
            android:id="@+id/imageView5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:src="@drawable/pic5" />

        <ImageView
            android:id="@+id/imageView6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:src="@drawable/pic6" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout03"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3">

        <ImageView
            android:id="@+id/imageView7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:src="@drawable/pic7" />

        <ImageView
            android:id="@+id/imageView8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:src="@drawable/pic8" />

        <ImageView
            android:id="@+id/imageView9"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:src="@drawable/pic9" />

    </LinearLayout>

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="투표 종료" />

</LinearLayout>

 

  • 바깥 리니어레이아웃 안에 리니어레이아웃 3개, 버튼 1개를 생성하고 layout_weight를 3:3:3:1로 함
  • 3개의 레이아웃에 각각 3개의 이미지뷰를 넣음. 각 이미지뷰의 layout_weight는 1:1:1로 함. 필요에 따라 이미지뷰에 적당한 layout_margin을 적용. (예 : 5dp)
  • 이미지뷰 9개의 아이디는 imageView1 ~ imageView9, 버튼의 아이디는 btnResult.


2.  result.xml

서브 액티비티에서 사용할 result.xml을 res - layout 폴더에 생성

바깥은 테이블레이아웃으로 설정하고 stretchColumns 속성을 0으로 함

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical"
    android:stretchColumns="0">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="15dp"  />

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

    <TableRow>

        <TextView
            android:id="@+id/textView1"
            android:layout_gravity="center_vertical"
            android:text="그림1"
            android:textSize="15dp" />

        <RatingBar
            android:id="@+id/ratingBar1"
            style="?android:attr/ratingBarStyleIndicator"
            android:layout_gravity="right" />
    </TableRow>

    <TableRow>

        <TextView
            android:id="@+id/textView2"
            android:layout_gravity="center_vertical"
            android:text="그림2"
            android:textSize="15dp" />

        <RatingBar
            android:id="@+id/ratingBar2"
            style="?android:attr/ratingBarStyleIndicator"
            android:layout_gravity="right" />
    </TableRow>

    <TableRow>

        <TextView
            android:id="@+id/textView3"
            android:layout_gravity="center_vertical"
            android:text="그림3"
            android:textSize="15dp" />

        <RatingBar
            android:id="@+id/ratingBar3"
            style="?android:attr/ratingBarStyleIndicator"
            android:layout_gravity="right" />
    </TableRow>

    <TableRow>

        <TextView
            android:id="@+id/textView4"
            android:layout_gravity="center_vertical"
            android:text="그림4"
            android:textSize="15dp" />

        <RatingBar
            android:id="@+id/ratingBar4"
            style="?android:attr/ratingBarStyleIndicator"
            android:layout_gravity="right" />
    </TableRow>

    <TableRow>

        <TextView
            android:id="@+id/textView5"
            android:layout_gravity="center_vertical"
            android:text="그림5"
            android:textSize="15dp" />

        <RatingBar
            android:id="@+id/ratingBar5"
            style="?android:attr/ratingBarStyleIndicator"
            android:layout_gravity="right" />
    </TableRow>

    <TableRow>

        <TextView
            android:id="@+id/textView6"
            android:layout_gravity="center_vertical"
            android:text="그림6"
            android:textSize="15dp" />

        <RatingBar
            android:id="@+id/ratingBar6"
            style="?android:attr/ratingBarStyleIndicator"
            android:layout_gravity="right" />
    </TableRow>

    <TableRow>

        <TextView
            android:id="@+id/textView7"
            android:layout_gravity="center_vertical"
            android:text="그림7"
            android:textSize="15dp" />

        <RatingBar
            android:id="@+id/ratingBar7"
            style="?android:attr/ratingBarStyleIndicator"
            android:layout_gravity="right" />
    </TableRow>

    <TableRow>

        <TextView
            android:id="@+id/textView8"
            android:layout_gravity="center_vertical"
            android:text="그림8"
            android:textSize="15dp" />

        <RatingBar
            android:id="@+id/ratingBar8"
            style="?android:attr/ratingBarStyleIndicator"
            android:layout_gravity="right" />
    </TableRow>

    <TableRow>

        <TextView
            android:id="@+id/textView9"
            android:layout_gravity="center_vertical"
            android:text="그림9"
            android:textSize="15dp" />

        <RatingBar
            android:id="@+id/ratingBar9"
            style="?android:attr/ratingBarStyleIndicator"
            android:layout_gravity="right" />
    </TableRow>

    <TableRow>

        <Button
            android:id="@+id/btnReturn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_span="2"
            android:text="돌아가기" />
    </TableRow>
</TableLayout>

 

  • 각 <TableRow>에는 텍스트뷰 1개, 레이팅바 1개를 생성.
  • 마지막 <TableRow>에는 '돌아가기'를 생성.
  • 텍스트뷰의 아이디는 textView1 ~ textView9, 레이팅바의 아이디는 ratingBar1 ~ ratingBar9, 버튼의 아이디는 btnReturn.


3.  Kotlin 코드 작성 및 수정

MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_01)

        title = "명화 선호도 투표"

        // 그림을 클릭할 때마다 투표수를 저장할 9개짜리 배열을 선언하고 0으로 초기화
        val voteCounts = IntArray(9)

        // 이미지뷰 위젯을 저장할 9개짜리 배열을 선언
        val images = arrayOfNulls<ImageView>(9)

        // 이미지 버튼 Id 배열
        val imageIds = arrayOf(R.id.imageView1, R.id.imageView2, R.id.imageView3,
            R.id.imageView4, R.id.imageView5, R.id.imageView6,
            R.id.imageView7, R.id.imageView8, R.id.imageView9)

        // 이미지 이름 문자열 배열
        val imageNames = arrayOf("독서하는 소녀", "꽃장식 모자 소녀", "부채를 든 소녀",
            "이레느깡 단 베르양", "잠자는 소녀", "테라스의 두 자매",
            "피아노 레슨", "피아노 앞의 소녀들", "해변에서")

        // 각 이미지뷰에 대한 클릭 이벤트리스너를 생성. 이미지뷰가 9개나 되므로 반복문을 사용
        for (i in imageIds.indices) {
            images[i] = findViewById(imageIds[i]
            images[i]!!.setOnClickListener {
                voteCounts[i]++  // 그림을 클릭하면 해당 그림의 투표수가 증가
                Toast.makeText( // 그림을 클릭할 때마다 그림의 제목과 누적된 투표수를 토스트 메시지로 보여줌
                    applicationContext,
                    "${imageNames[i]} : 총${voteCounts[i]} 표",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }

        // '투표 종료'에 대해 클릭 이벤트 리스너를 생성
        val btnResult = findViewById<Button>(R.id.btn)
        // 인텐트를 생성하고 인텐트에 투표수 배열과 그림 제목 배열을 넣은 후 결과 액티비티를 호출
        btnResult.setOnClickListener {
            val intent = Intent(applicationContext, ResultActivity::class.java)
            intent.putExtra("voteCounts", voteCounts)
            intent.putExtra("imageNames", imageNames)
            startActivity(intent)
        }

    }
}

 

intent.putExtra("voteCounts", voteCounts)
intent.putExtra("imageNames", imageNames)


  정수형 voteCount 배열을 VoteCounts라는 이름으로 넘김. 이것을 액티비티에서는 getIntArrayExtra() 메서드로 받음.
  문자열 배열인 imageNames을 지정한 imageNames은 getStringArrayExtra() 메서드로 받음.

 

ResultActivity.kt
class ResultActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.result)

        // MainActivity에서 보낸 투표 결과 값을 받음
        title = "투표결과"
        
        val intent = intent
        val voteCounts = intent.getIntArrayExtra("voteCounts")
        val imageNames = intent.getStringArrayExtra("imageNames")

        // 가장 많은 투표를 받은 이미지와 제목을 나타내는 변수
        val mainText = findViewById<TextView>(R.id.textView)
        val mainImg = findViewById<ImageView>(R.id.imageView)

        // 9개의 TextView, RatingBar 객체 배열
        val ratingBars = arrayOfNulls<RatingBar>(9)
        val textViews = arrayOfNulls<TextView>(9)

        // 9개의 TextView, RatingBar id 배열
        val textViewIds = arrayOf(R.id.textView1, R.id.textView2, R.id.textView3,
                      R.id.textView4, R.id.textView5, R.id.textView6,
                      R.id.textView7, R.id.textView8, R.id.textView9)

        val ratingBarIds = arrayOf(R.id.ratingBar1, R.id.ratingBar2, R.id.ratingBar3,
                        R.id.ratingBar4, R.id.ratingBar5, R.id.ratingBar6,
                        R.id.ratingBar7, R.id.ratingBar8, R.id.ratingBar9)

        val imageIds = arrayOf(R.drawable.pic1, R.drawable.pic2, R.drawable.pic3,
                       R.drawable.pic4, R.drawable.pic5, R.drawable.pic6,
                       R.drawable.pic7, R.drawable.pic8, R.drawable.pic9)

        var maxVotesIndex = 0
        for (i in 1 until voteCounts!!.size) {
            if (voteCounts[i] > voteCounts[maxVotesIndex]) {
                maxVotesIndex = i
            }

            mainText.text = imageNames!![maxVotesIndex]
            mainImg.setImageResource(imageIds[maxVotesIndex])
        }

        for (i in voteCounts!!.indices) {

            textViews[i] = findViewById(textViewIds[i])
            textViews[i]!!.text = imageNames!![i]

            ratingBars[i] = findViewById(ratingBarIds[i])
            ratingBars[i]!!.rating = voteCounts[i].toFloat()
            ratingBars[i]!!.stepSize = 1f

        }

        // 버튼을 클릭하면 서브 액티비티를 종료. 메인 액티비티로 돌아감.
        val btnReturn = findViewById<Button>(R.id.btnReturn)
        btnReturn.setOnClickListener {
            finish()
        }

    }
}

 

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  안드로이드의 4대 컴포넌트

안드로이드의 4대 컴포넌트는 액티비티 · 서비스 · 브로드캐스트 리시버 · 콘텐트 프로바이더


1)  액티비티 Activity

 화면을 구성하는 가장 기본적인 컴포넌트

 

2)  서비스 service

  눈에 보이는 화면(액티비티)과 상관없이 백그라운드에서 동작하는 컴퍼넌트. 백신 프로그램처럼 눈에 보이지는 않지만 계속 동작
  -  로컬에서 동작하는 서비스는 다음과 같이 세 단계를 거침
      서비스 생성 ▶️ 서비스 시작 ▶️ 서비스 종료

 

3)  브로드캐스트 리시버

  -  안드로이드는 여러 응용 프로그램이나 장치에 메시지를 전달하기 위해 방송 broadcasting 메시지를 사용
  -  안드로이드는 문자 메시지 도착, 배터리 방전, SD 카드 탈부착, 네트워크 환경 변화가 발생하면 전체 응용 프로그램이 알 수 있도록 방송 신호를 보냄 그리고 브로드캐스트 리시버 Broadcast Receiver는 이러한 방송 신호가 발생하면 반응함

 

4)  콘텐트 프로바이더

 콘텐트 프로바이더 Content Provider는 응용 프로그램 사이에 데이터를 공유하기 위한 컴포넌트
  -  안드로이드 응용 프로그램은 데이터에 자신만 접근할 수 있으므로 자신의 데이터를 외부에 공유하려면 콘텐트 프로바이더를 만들어야 함. 콘텐트 프로바이더에서 처리된 데이터는 일반적으로 데이터베이스 또는 파일로 저장

 


2.  액티비티의 개요

액티비티는 안드로이드폰에 나타나는 화면 하나하나를 말함. 액티비티는 사용자에게 보여주는 화면을 만들기 때문에 안드로이드의 4개 컴포넌트 중 가장 핵심적인 요소.

 

  • 안드로이드 프로젝트를 생성할 때 activity_main.xml과 MainActivity.kt 파일이 생성
  • activity_main.xml은 화면을 구성하는 코드로 되어 있지만 activity_main.xml이 아니라 MainActivity.kt가 액티비티에 해당
  • 일반적으로 액티비티 하나당 XML 파일 하나를 만들어서 사용
activity_main.xml 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/btnNewActivity"
        android:text="새 화면 열기" />


</LinearLayout>

 

second.xml 

 

    [res] - [layout] 에 second.xml 생성

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#CDE4F6">

    <Button
        android:id="@+id/btnReturn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="돌아가기" />

</LinearLayout>

 

SecondActivity.kt
class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.second)

        val btnReturn: Button = findViewById(R.id.btnReturn)
        btnReturn.setOnClickListener {
            finish()
        }

    }
}

 

  📍  finish()가 호출되면 현재 액티비티를 종료. 세컨드 액티비티는 메인 액티비티에서 호출할 것이므로 세컨드 액티비티를 종료하면 다시 메인 액티비티가 나타남

 

MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val btnNewActivity = findViewById<Button>(R.id.btnNewActivity)
        btnNewActivity.setOnClickListener {
            val intent = Intent(applicationContext, SecondActivity::class.java)
            startActivity(intent)
        }
    }
}

 

val intent = Intent(applicationContext, SecondActivity::class.java)

 

  -  인텐트를 생성. Intent의 첫 번째 파라미터 applicationContext는 메인 클래스의 컨텍스트를 반환
  -  applicationContext 대신 this@MainActivity 도 가능
  -  두 번째 파라미터로 이 인텐트에 포함될 액티비티 SecondActivity를 넘겨줌. ::class.java를 붙여야 한다는 것을 주의.

 

startActivity(intent)


  -  startActivity() : 새로운 액티비티를 화면에 출력. 파라미터로 인텐트를 받음.

 

AndroidManifest.xml
<activity
    android:name=".MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
         category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:name=".SecondActivity" />

 

  📍  안드로이드는 사용될 액티비티를 AndroidManifest.xml에 반드시 등록. 프로젝트를 생성할 때 메인 액티비티는 자동으로 등록되지만, 추가한 세컨드 액티비티는 별도로 등록해야 함.

 


3.  명시적 인텐트

인텐트 Intent는 안드로이드의 4대 컴포넌트가 서로 데이터를 주고받기 위한 메시지 객체.

명시적 인텐트와 암시적 인텐트로 구분


1)  명시적 인텐트와 데이터 전달

  🐰  명시적 인텐트 explicit intent는 다른 액티비티의 이름을 명확히 지정할 때 사용

val intent = Intent(applicationContext, SecondActivity::class.java)
startActivity(intent)


  ✓  Intent() 생성자의 두 번째 파라미터에서는 액티비티 클래스를 넘길 수 있는데, 위의 코드에서는 그 전에 생성한 SecondActivity로 정확히 지정. 그리고 생성한 인텐트를 넘겨서 세컨드 액티비티를 실행

  ✓  이처럼 명확하게 액티비티의 이름을 지정했기 때문에 명시적 인텐트가 됨. 일반적으로 명시적 인텐트는 사용자가 새로운 액티비티를 직접 생성하고 호출할 때 사용

  🐰  인텐트에 데이터를 담아서 넘기는 것도 가능. 메인 액티비티에서 인텐트에 데이터를 실어서 넘긴 다음 세컨드 액티비티에게 받은 데이터를 처리

 

 

1)  putExtra()를 이용해서 필요한 만큼 데이터를 인텐트에 넣음
2)  startActivity()로 인텐트를 다른 액티비티에 넘김
3)  인텐트를 받은 액티비티에서 getStringExtra(), getIntExtra(), getBooleanExtra() 등의 메서드로 넘어온 데이터에 접근

 

 

 


2)  레이팅바

xml 코드
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <RatingBar
        android:id="@+id/ratingBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <RatingBar
        android:id="@+id/ratingBar2"
        style="?android:attr/ratingBarStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:numStars="10"
        android:stepSize="1" />

    <RatingBar
        android:id="@+id/ratingBar3"
        style="?android:attr/ratingBarStyleIndicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:rating="1.5" />

    <Button
        android:id="@+id/btnIncrease"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="증가시키기" />

    <Button
        android:id="@+id/btnDecrease"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="감소시키기" />

</LinearLayout>

 

style="?android:attr/ratingBarStyleSmall"
style="?android:attr/ratingBarStyleIndicator"

 

  -  레이팅바의 스타일을 지정. 작은 모양과 중간 모양을 표시.

android:stepSize="1"


  -  한 번에 증가될 크기를 별 1개로 지정. 소수점으로 설정할 수 있으며 디폴트는 0.5.

android:numStars="10"


  -  별의 개수를 지정. 디폴트는 5개.

 

android:rating="1.5"


  -  초깃값을 설정. 1.5개가 채워진 상태로 표현.

 

kotlin 코드
val ratingBar1 = findViewById<RatingBar>(R.id.raingBar1)
val ratingBar2 = findViewById<RatingBar>(R.id.raingBar1)
val ratingBar3 = findViewById<RatingBar>(R.id.raingBar1)
val btnIncrease = findViewById<RatingBar>(R.id.btnIncrease)
val btnDecrease = findViewById<RatingBar>(R.id.btnDecrease)

btnDecrease.setOnClickListener {
    ratingBar1.rating = ratingBar1.rating + ratingBar1.stepSize
    ratingBar2.rating = ratingBar2.rating + ratingBar2.stepSize
    ratingBar3.rating = ratingBar3.rating + ratingBar3.stepSize
}    

btnIncrease.setOnClickListener {
    ratingBar1.rating = ratingBar1.rating + ratingBar1.stepSize
    ratingBar2.rating = ratingBar2.rating + ratingBar2.stepSize
    ratingBar3.rating = ratingBar3.rating + ratingBar3.stepSize
}

 

  -  rating으로 레이팅바에 채워진 개수와 stepSize로 레이팅바에 설정된 stepSize 속성의 증가 크기 (디폴트는 0.5)를 더하거나 뺌

 

 

 

 

[ 내용참고 : IT 학원 강의 ]

 


1.  대화상자

대화상자 dialog는 화면에 메시지를 나타낸 후 확인이나 취소 같은 사용자의 선택을 받아들이지는 경우에 사용
토스트보다 좀 더 강력한 메시지를 전할 때 적당


1)   기본 대화상자

대화상자는 사용자에게 중요한 사항을 알려준 후 사용자가 어떤 선택을 하게 하는 것이 주요 목적
그래서 사용자에게 좀 더 확실히 주지시켜야 할 때 혹은 계속 진행할지 여부를 선택하게 할 때 사용

대화상자의 가장 일반적인 사용 형식의 순서


   A.  대화상자 생성
      -  AlertDialog.Builer 클래스로 생성
 

   B.  용도에 따른 설정
      -  setTitle()  :  제목 설정
      -  setMessage()  :  내용 입력
      -  setIcon()  :  아이콘 설정
      -  setPositiveButton()  :  OK 버튼
      -  setNegativeButton()  :  Cancel 버튼
      -  setItems()  :  목록 출력

      - setSingleChoiceItems :  라디오 목록 출력
      - setMultiChoiceItems  :  체크박스 목록 출력

   C.  대화상자 화면 출력
      -  show()

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"

    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    android:gravity="center_horizontal">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="대화상자"/>

</LinearLayout>
class MainActivity : AppCompatActivity() {

    lateinit var button: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button = findViewById(R.id.button)

        button.setOnClickListener {

            val dlg = AlertDialog.Builder(this@MainActivity)
            dlg.setTitle("제목입니다.")
            dlg.setMessage("이곳이 내용입니다.")
            dlg.setIcon(R.mipmap.ic_launcher)
            dlg.setPositiveButton("확인", null)
            dlg.show()
        }
    }
}

 

dlg.setPositiveButton("확인", null)
원형은 dlg.setPositiveButton("문자열", 리스너);


  확인이나 닫기 1개만 사용할 때는 setPositiveButton() 메서드를 사용
  리스너 부분이 null이라 대화상자만 닫힐 뿐 아무 동작이 일어나지 않음

 


 

2)  목록 대화상자

대화상자에 리스트 형태의 목록을 출력하고 그중 하나를 선택할 수 있음.가장 기본적인 목록을 만드는 형식

class MainActivity : AppCompatActivity() {

    lateinit var button: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button = findViewById(R.id.button)

        button.setOnClickListener {
            val versionArray = arrayOf("오레오", "파이", "큐(10)")
            val checkArray = booleanArrayOf(true, false, false)
            val dlg = AlertDialog.Builder(this@MainActivity)
            dlg.setTitle("좋아하는 버전은? ")
            dlg.setIcon(R.mipmap.ic_launcher)
            dlg.setMultiChoiceItems(versionArray, checkArray) {dialog, which, isCheked ->
                button.text = versionArray[which]
            }
            dlg.setPositiveButton("닫기", null)
            dlg.show()
        }
    }
}

 

val versionArray = arrayOf("오레오", "파이", "큐(10)")

 

    ✓  출력할 항목의 문자열 배열 생성

 

dlg.setItems(versionArray) {dialog, which ->
    button.text = versionArray[which]
}


    ✓  문자열에 클릭 이벤트 할당. which는 몇번째 항목인지 알려줌.
    ✓  선택과 동시에 창이 닫힘
    ✓ 선택해도 대화상자가 닫히지 않도록 하려면 setItems 대신 setSingleChoiceItems를 사용하면 되는데, 라디오버튼 같은 형태로 출력.  이 메서드는 setSingleChoiceItems(문자열 배열, 초기 선택 인덱스, 리스너)로 파라미터가 3개.

    ✓  여러 개를 동시에 선택하게 하려면 setMultiChoiceItems() 사용하며, 이는 체크박스 형태로 표시

 

val checkArray = booleanArrayOf(true, false, false)


    ✓  각 항목이 체크되었는지 boolean 배열로 만듦

 

dlg.setMultiChoiceItems(versionArray, checkArray) { dialog, which, isChecked ->


    ✓  setMultiChoiceItems()의 두 번째 파라미터는 첫 번째 파라미터인 문자열 배열과 개수가 같은 boolean 배열이어야 함

 

 

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  메뉴

안드로이드의 메뉴는 옵션 메뉴 option menu와 컨텍스트 메뉴 context menu로구분

  ⚡️  옵션 메뉴를 사용하는 방법에는 메뉴 XML 파일을 생성한 후 Kotlin에서 호출하는 방법과 XML 파일 없이 Kotlin 코드만으로 메뉴를 생성하는 방법이 있음
  ⚡️  메뉴는 항목이 대부분 고정되어 있으므로 메뉴 XML 파일을 이용
        ➡️  메뉴 XML 파일을 이용하면 앱을 다른 나라 언어로 변경할 때 Kotlin코드를 건드리지 않아도 되므로 효율적

1)  XML을 이용한 옵션 메뉴

메뉴 XML 파일을 이용하는 방식은 세 가지만 설정하면 됨

  • 메뉴 코딩 : 메뉴 폴더 생성 및 메뉴 XML 파일 생성, 편집
  • 메뉴 파일 등록 : Kotlin 코딩. onCreateOptionsMenu() 메서드 오버라이딩
  • 메뉴 선택 시 동작할 내용 코딩 : Kotlin 코딩. onOptionsItemSelected() 메서드 오버라이딩
메뉴 XML 파일
<menu>
    <item
        android:id="@+id:항목1아이디"
        android:title="항목1 제목" />
    <item
        android:id="@+id:항목2아이디"
        android:title="항목2 제목" />
</menu>

 

  ✓ 위의 예에서 항목 item 이 2개 ➡️  메뉴에는 '항목1 제목', '항목2 제목'이 출력됨. 또한 메뉴 안에 서브 메뉴도 생성할 수 있음

 

Activity 클래스에서 오버라이딩하는 onCreateOptionsMenu() 메서드

 

    ✓  앱이 실행되면 메뉴의 내용을 XML 파일에서 자동으로 읽어옴. 메서드에 코딩할 내용은 거의 고정화 되어 있음

  override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.메뉴XML아이디, menu)
        return true
    }

 

Activity 클래스에서 오버라이딩하는 onOptionsItemSelected() 메서드 

 

    ✓  메뉴를 선택했을 때 어떤 동작을 할 것인지 담고 있음. 이 메서드에 실제 동작할 내용을 코딩하면 됨
    ✓  메뉴는 항목이 여러 개 나오기 때문에 보통 메서드 내부에서 when 을 사용

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    when(item.itemId) {
        R.id.항목1아이디 -> {
            항목 1을 선택했을 때 실행되는 코드
        }
        R.id.항목2아이디 -> {
            항목 2을 선택했을 때 실행되는 코드
        }
    }    
    return super.onOptionsItemSelected(item)
}

 

 

 

인플레이터 Inflater


정적으로 존재하는 XML 파일을 Kotlin 코드에서 접근해서 실제 객체로 만들어 사용하는 것
메뉴 인플레이터 MenuInflater 객체는 메뉴 XML 파일을 Kotlin 코드에서 가져와 사용

레이아웃 인플레이터 LayoutInflater 객체는 레이아웃 XML 파일을 Kotlin 코드에서 가져와 사용하는 것

 


 

2)  Kotlin 코드만 이용한 옵션 메뉴

onCreateOptionsMenu() 메서드 안에 메뉴 XML 파일에 접근하는 대신에 직접 munu.add() 메서드로 메뉴항목을 추가
onOptionsItemSelected() 메소드의 case문을 코드에서 지정한 항목의 아이디 순번으로 변경하면 됨

class MainActivity : AppCompatActivity() {

    lateinit var baseLayout: LinearLayout
    lateinit var button: Button
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        title = "배경색 바꾸기"

        baseLayout = findViewById(R.id.baseLayout)
        button = findViewById(R.id.button1)

    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        super.onCreateOptionsMenu(menu)

        menu?.add(0, 1, 0, "배경색 (빨강)")
        menu?.add(0, 2, 0, "배경색 (초록)")
        menu?.add(0, 3, 0, "배경색 (파랑)")

        val subMenu = menu?.addSubMenu("버튼 변경 >> ")
        subMenu?.add(0, 4, 0, "버튼 45도 회전")
        subMenu?.add(0, 5, 0, "버튼 2배 확대")
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when(item.itemId) {
            1 -> {
                baseLayout.setBackgroundColor(Color.RED)
            }
            2 -> {
                baseLayout.setBackgroundColor(Color.GREEN)
            }
            3 -> {
                baseLayout.setBackgroundColor(Color.BLUE)
            }
            4 -> {
                button.rotation = 45f
            }
            5 -> {
                button.scaleX = 2f
            }
        }
        return super.onOptionsItemSelected(item)
    }
}

 

    ✓  add() 메서드의 파라미터는 그룹 아이디, 항목아이디, 순번, 제목순으로 지정
          ➡️  두 번째 지정한 아이디가 onOptionsItemSelected()의 when 과 같으면 됨

 

 

 


3)  XML을 이용한 컨텍스트 메뉴

🚀  옵션 메뉴는 키패드의 메뉴 버튼을 클릭할 때 나타나는 것과 달리 컨텍스트 메뉴는 레이아웃 또는 버튼, 에디트텍스트 등의 위젯을 롱클릭했을 때 나타남
🚀  컨텍스트 메뉴에서 메뉴 XML파일을 이용하는 방식은 옵션 메뉴와 비슷. 단, 여러 개의 위젯에 메뉴를 설정할 수 있으므로 onCreate 메서드에서 컨텍스트 메뉴을 나타낼 위젯을 registerForContextMenu()로 등록해야 함. 또, 옵션 메뉴에서 사용한 메서드와 메서드 이름이 약간 다름

  • 메뉴 코딩 : 메뉴 폴더 생성 및 위젯의 메뉴 XML 파일 생성. 편집
  • 메뉴를 사용할 위젯 등록 : Kotlin 코딩. onCreate() 안에 registerForContextMenu()로 등록
  • 메뉴 파일 등록 : Kotlin 코딩. onCreateContextMenu() 메서드 오버라이딩
  • 메뉴 선택시 동작할 내용 코딩 : Kotlin 코딩. onContextItemSelected() 메서드 오버라이딩
@Override 
public void onCreateContextMenu(ContextMenu menu, 
    View v, 
    ContextMenu.ContextMenuInfo menuInfo) { 
    
        super.onCreateContextMenu(menu, v, menuInfo);  
        
        MenuInflater menuInflater = getMenuInflater(); 
         
            if (v == 위젯1) { 
                menuInflater.inflate(R.menu.첫번째메뉴XML파일, menu); 
            } 
            if (v == 위젯2) { 
                menuInflater.inflate(R.menu.두번째메뉴XML파일, menu); 
            }
}

 

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


 

🚀  SD 카드 특정 폴더의 이미지 파일을 보여주는 간단한 이미지 뷰어 앱 만들기

 

1.  화면 디자인 및 편집

 

1) MyPictureView 클래스

커스텀 위젯 (Custom Widget, Custom View)을 직접 만들어서 activity_main.xml에 넣어서 사용.
  ➡️ 커스텀 위젯은 지정된 이미지 파일을 출력하는 역할

 

MyPictureView.kt

 

    ✓  onDraw() 메서드를 오버라이딩

class MyPictureView(context: Context, attrs: AttributeSet?) : View(context, attrs) {

    var imagePath : String? = null

    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        try {
            if (imagePath != null) {
                val bitmap = BitmapFactory.decodeFile(imagePath)
                canvas.scale(2f, 2f, 0f, 0f)
                canvas.drawBitmap(bitmap!!, 0f, 0f, null)
                bitmap.recycle()
            }
        } catch ( e : Exception) {

        }
    }
}

 

var imagePath : String? = null


    ✓  이미지 파일의 경로 및 파일 이름을 저장할 변수

 

if (imagePath != null) {
val bitmap = BitmapFactory.decodeFile(imagePath)
canvas.scale(2f, 2f, 0f, 0f)
canvas.drawBitmap(bitmap!!, 0f, 0f, null)
bitmap.recycle()
}


    ✓ imagePath에 값이 있으면(경로 및 파일이름이 지정되었다면) 화면에 그림 파일을 출력

 


 

2) activity_main.xml

  • 가로 레이아웃에 버튼 2개를 생성
  • 커스텀 위젯인 MyPictureView를 생성
  • 위젯의 이름은 btnPrev, btnNext, myPictureView
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btnPrev"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="이전 그림" />

        <Button
            android:id="@+id/btnNext"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="다음 그림" />

    </LinearLayout>

    <kr.abc.stream_practice.MyPictureView
        android:id="@+id/myPictureView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

 

 

👾  /storage/emulated/0/Pictures 에 이미지 업로드 후 AndroidManifest.xml 에 권한 설정

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:requestLegacyExternalStorage="true"

 


 

2.  kotlin 코드 작성 및 수정

class MainActivity : AppCompatActivity() {
    // 전역변수 선언
    lateinit var btnPrev: Button
    lateinit var btnNext: Button
    lateinit var myPicture: MyPictureView
    
    var curIndex: Int = 1 // 이미지 파일의 인덱스로 사용할 변수
    var imageFiles: Array<File>? = null // SD 카드에서 읽어올 이미지 파일의 배열

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        title = "간단 이미지 뷰어"
        
        // 접근 권한 요청
        ActivityCompat.requestPermissions(
            this,
            arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE),
            Context.MODE_PRIVATE
        )

        btnPrev = findViewById(R.id.btnPrev)
        btnNext = findViewById(R.id.btnNext)
        myPicture = findViewById(R.id.myPictureView)

        imageFiles =
            File(Environment.getExternalStorageDirectory().absolutePath + "/Pictures").listFiles()

        // 파일 목록 출력
        for (i in imageFiles!!.indices) {
            var fileName = if (imageFiles!![i].isDirectory == true)
                "<폴더> " + imageFiles!![i].toString()
            else
                "<파일> " + imageFiles!![i].toString()
            println(fileName)
        }

        // 첫 번째 파일을 커스텀 위젯에 출력
        // 해당 인덱스의 이미지 파일 이름을 myPicture에 전달한다는 뜻
        myPicture.imagePath = imageFiles!![curIndex].toString()

        btnPrev.setOnClickListener {
            if (curIndex <= 1) {
                Toast.makeText(applicationContext, "첫번째 그림입니다", Toast.LENGTH_SHORT).show()
            } else {
                myPicture.imagePath = imageFiles!![--curIndex].toString()
                myPicture.invalidate()
            }
        }

        btnNext.setOnClickListener {
            if (curIndex >= imageFiles!!.size-1) {
                Toast.makeText(applicationContext, "마지막 그림입니다.", Toast.LENGTH_SHORT).show()
            } else {
                myPicture.imagePath = imageFiles!![++curIndex].toString()
                myPicture.invalidate()
            }
        }
    }
}

 

 


첫 번째 이미지에서 이전 버튼을 누르면 마지막 이미지가 뜨거나,
마지막 이미지에서 다음 버튼을 누르면 첫 번째 이미지가 뜨게 하기
class MainActivity : AppCompatActivity() {
    lateinit var btnPrev: Button
    lateinit var btnNext: Button
    lateinit var textIndex: TextView
    lateinit var textTotal: TextView
    lateinit var myPicture: MyPictureView
    var curIndex: Int = 1
    var imageFiles: Array<File>? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        title = "간단 이미지 뷰어"
        ActivityCompat.requestPermissions(
            this,
            arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE),
            Context.MODE_PRIVATE
        )

        btnPrev = findViewById(R.id.btnPrev)
        btnNext = findViewById(R.id.btnNext)

        textIndex = findViewById(R.id.textIndex)
        textTotal = findViewById(R.id.textTotal)

        myPicture = findViewById(R.id.myPictureView)

        imageFiles =
            File(Environment.getExternalStorageDirectory().absolutePath + "/Pictures").listFiles()

        for (i in imageFiles!!.indices) {
            var fileName = if (imageFiles!![i].isDirectory == true)
                "<폴더> " + imageFiles!![i].toString()
            else
                "<파일> " + imageFiles!![i].toString()
            println(fileName)
        }

        myPicture.imagePath = imageFiles!![curIndex].toString()
        textTotal.text = (imageFiles!!.size-1).toString()
        textIndex.text = curIndex.toString()

        btnPrev.setOnClickListener {

            if (curIndex == 1) {
                curIndex = imageFiles!!.size
            } else {
                myPicture.imagePath = imageFiles!![--curIndex].toString()
                textIndex.text = curIndex.toString()
                myPicture.invalidate()
            }

        }

        btnNext.setOnClickListener {

            if (curIndex >= imageFiles!!.size-1) {
               curIndex = 0
            } else {
                myPicture.imagePath = imageFiles!![++curIndex].toString()
                textIndex.text = curIndex.toString()
                myPicture.invalidate()
            }

        }

    }
}

 

 

 

 

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


✏️  일기장 앱을 내장 메모리가 아닌 sd카드의 mydiary 폴더에 저장되도록 작업. 단, SD카드에 mydiary가 없으면 kotlin 코드에서 자동 생성되게 함

 

xml 코드
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <DatePicker
        android:id="@+id/datePicker"
        android:datePickerMode="spinner"
        android:calendarViewShown="false"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:padding="20dp"
        android:background="#E7D9F6"
        android:layout_weight="1"
        android:lines="8"/>

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:enabled="false"
        android:text="BUTTON" />

</LinearLayout>

 

 

MainActivity 코드
class MainActivity : AppCompatActivity() {
    lateinit var datePicker: DatePicker
    lateinit var editText: EditText
    lateinit var btn: Button
    lateinit var fileName: String 

    /* 추가된 코드 */
    lateinit var savePath: String // 저장 경로
    var isFlag = false // 디렉토리 생성을 저장

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        title = "간단 일기장"

        // 접근 권한 요청
        ActivityCompat.requestPermissions(this,
            arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE),
            Context.MODE_PRIVATE)

        datePicker = findViewById(R.id.datePicker)
        editText = findViewById(R.id.editText)
        btn = findViewById(R.id.btn)

        val calendar = Calendar.getInstance()
        val calendarYear = calendar.get(Calendar.YEAR)
        val calendarMonth = calendar.get(Calendar.MONTH)
        val calendarDay = calendar.get(Calendar.DAY_OF_MONTH)
        
        // 외부 경로 지정
        savePath = Environment.getExternalStorageDirectory().absolutePath + "/MyDiary"

        fileName = "${calendarYear}_${calendarMonth + 1}_${calendarDay}.txt"
        val str = readDiary(fileName) // 날짜에 해당하는 일기 파일을 읽기
        editText.setText(str) // 에디트텍스트에 일기 내용을 출력
        btn.isEnabled = true // 버튼 활성화

        datePicker.init(
            calendarYear,
            calendarMonth,
            calendarDay,
            DatePicker.OnDateChangedListener() { datePicker: DatePicker, year: Int, month: Int, day: Int ->
                makeDir() // 새로운 디렉토리 생성 함수 ( 있으면 다시 생성 x )
                
                fileName = "${year}_${month + 1}_${day}.txt"
                val string = readDiary(fileName) // 날짜에 해당하는 파일을 읽기
                editText.setText(string) // 에디트텍스트에 일기 내용을 출력
                Toast.makeText(applicationContext, fileName, Toast.LENGTH_SHORT).show()
                btn.isEnabled = true // 버튼 활성화
            })


        btn.setOnClickListener {
            val outputStream = FileOutputStream("$savePath/$fileName") // 지정 경로 이름으로 저장
            val string = editText.text.toString() // 입력값 저장
            outputStream.write(string.toByteArray())
            outputStream.close()
            Toast.makeText(applicationContext, "${fileName} 이 저장됨", Toast.LENGTH_SHORT).show()
        }
    }


    private fun readDiary(fileName: String): String? {
        var diaryStr: String? = null
        val inputStream: FileInputStream

        try {
            inputStream = FileInputStream("${savePath}/${fileName}") // 지정 파일 불러오기
            val txt = ByteArray(inputStream.available())
            inputStream.read(txt)
            inputStream.close()
            diaryStr = txt.toString(Charsets.UTF_8).trim()
            btn.text = "수정하기"
        } catch (e: IOException) {
            editText.hint = "일기 없음"
            btn.text = "새로 저장"
        }
        return diaryStr;
    }

    private fun makeDir() {
        if (!isFlag) { // 외부에 디렉토리가 생성되어 있지 않으면 생성
            // 저장 경로 생성
            val myDir = File(savePath)
            myDir.mkdir()
            isFlag = true
        }
    }

}

 

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]

+ Recent posts