Kotlin_간단한 게시판 실습

소정·2023년 3월 22일
0

Kotlin

목록 보기
8/27
  • 코틀린은 멤버변수(프로퍼티)를 초기화 하지 않으면 Error인걸 명시하자

[1] IntrolActivity

🧨 주요 내용 : Intent 사용법

activity_introl.xml

메인 액티비티로 가는 버튼과 앱 종료 버튼 생성

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    android:padding="16dp"
    tools:context=".IntrolActivity">

    <Button
        android:id="@+id/btn"
        android:text="go to main"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"/>

    <Button
        android:id="@+id/btn_exit"
        android:text="Exit"
        android:layout_alignParentRight="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>

IntrolActivity.kt

  • nullable 변수 만들기 ? 키워드 사용 (사용할 땐 ?.)
  • var의 늦은 초기화
  • object : 익명 객체 쓰는 키워드 (코딩쓰는 영역인 중괄호만 쓴다) 람다를 더 축약
  • 샘(SAM : Single Abstract Method) 변환
  • Intent 객체 생성


    바깥 거 부를 때 라벨표시로 , ::class.java(:: 클래스 부르는 키워드) -> 아직 Intent가 java로 인식 돼서 .java가 붙어 있음


package com.bsj0420.ex02kotlinrecyclerview

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.view.View.OnClickListener
import android.widget.Button

class IntrolActivity : AppCompatActivity() {

    //코틀린은 멤버변수(프로퍼티)를 초기화 하지 않으면 Error
    var btn:Button? = null // nullable 변수
    
    //늦은 초기화
    lateinit var btnExit:Button

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

        //버튼 객체 참조
        btn = findViewById(R.id.btn)
        // nullable변수는 null일수도 있기 떄문에 안전하게 멤버에 접근하는 연산자 사용해야함
        btn?.setOnClickListener(object : OnClickListener{ //object : 익명 객체 쓰는 키워드
            override fun onClick(p0: View?) {
                val intent:Intent = Intent(this@IntrolActivity,MainActivity::class.java)
                //바깥 거 부를 때 라벨표시로 , ::class.java(:: 클래스 부르는 키워드) -> 아직 Intent가 java로 인식 돼서 .java가 붙어 있음
                startActivity(intent)
            }
        })
        
        //btnExit 참조
        btnExit = findViewById(R.id.btn_exit)
        //1. 리스너 설정을 익명클래스 말고 간결하게 람다식으로
        btnExit.setOnClickListener({v-> finish()}) //코틀린에선 중괄호 안에 v->
        
        //2. 파라미터가 1개라면 생략가능[ 자동 it 키워드로 파라미터명 지정됨 ]
        btnExit.setOnClickListener({ 
            finish()
        })

        //3. 람다를 더 축약 -> 샘(SAM : Single Abstract Method) 변환 -> 코딩쓰는 영역인 중괄호만 쓴다
        btnExit.setOnClickListener{
            finish()
        }

    }
}



[2] MainActivity

🧨 주요 내용 : 리사이클러 뷰 사용

main_activity.xml

  • 대량의 데이터를 보여줄 리사이클러뷰만 설계
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

recycler_item.xml

  • 리사이클러 뷰에 들어갈 view 모양
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="160dp"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardCornerRadius="8dp"
    android:elevation="8dp"
    android:layout_margin="12dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/iv"
            android:src="@drawable/moana01"
            android:scaleType="centerCrop"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        <TextView
            android:id="@+id/tv_name"
            android:layout_margin="16dp"
            android:layout_alignParentRight="true"
            android:textStyle="bold"
            android:textSize="40sp"
            android:textColor="@color/white"
            android:text="name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <TextView
            android:id="@+id/tv_msg"
            android:layout_margin="16dp"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:textStyle="bold"
            android:textSize="18sp"
            android:textColor="@color/white"
            android:text="message"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </RelativeLayout>


</androidx.cardview.widget.CardView>

item.kt

  • 주생성자 사용
  • 주생성자에 var 키워드 있으면 매개변수면서 멤버변수가 됨
  • 클래스 영역에 아무것도 쓸게 없으면 {} 없어도 됨

💡 data 클래스

  • 데이터 저장 목적인 애들은 앞에 data 키워드를 붙인다
  • 일반 클래스와 다르게 자동으로 equls() 할 때 객체 주소를 비교하지 않고 멤버값을 비교해주도록 설계된 클래스
  • 단, 주 생성자의 멤버만 비교한다

package com.bsj0420.ex02kotlinrecyclerview

data class Item constructor(var name:String, var msg:String, var imgId:Int)

adapter.kt

  • 상속 키워드 : & 상속 받을 때 () 생성자 빼먹지 않도록 조심!
  • 생성자에 Context와 MutableList 받아옴

    생성자 키워드 construct 생략 가능

  • viewholder 이너 클래스 만들 때 inner 키워드!

    ViewHolder를 상속 받을 때 constructor()로 View를 받는다
    (레이아웃 안에 있는 뷰들을 들고 오는 거니까 View를 생성자로 받음)
    itemView는 itemlayout의 최상위 카드뷰를 부른 것

  • onCreateViewHolder : 레이아웃 그리는 곳 (LayoutInflater)
  • onBindViewHolder

    코틀린은 리스의 get() set() 싫어함 따라서 get 대신에 배열처럼 [] 씀 (인덱스 번호 를 권장)

  • getItemCount() : 함수 리턴 값 - 할당 연산자로 단순화
package com.bsj0420.ex02kotlinrecyclerview

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bumptech.glide.Glide

// 상속 키워드 : & 상속 받을 때 () 생성자 빼먹지 않도록 조심!
// 생성자에 Context와 MutableList 받아옴
class MyAdapter(var context:Context, var items:MutableList<Item>) : Adapter<MyAdapter.VH>() {

    //이너클래스는 inner 키워드!
    //ViewHolder 만들 때 constructor()로 View를 받는다
    // itemView는 itemlayout의 최상위 카드뷰를 부른 것
    inner class VH(itemView:View) : ViewHolder(itemView) {
        val tvName:TextView by lazy { itemView.findViewById(R.id.tv_name) }
        val tvMsg:TextView by lazy { itemView.findViewById(R.id.tv_msg) }
        val iv:ImageView by lazy { itemView.findViewById(R.id.iv) }
    }

    //아이템뷰 레이아웃 불러오기
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        var itemView:View = LayoutInflater.from(context).inflate(R.layout.recycler_item,parent,false)
        return VH(itemView)
    }

    override fun onBindViewHolder(holder: VH, position: Int) {
        //var item:Item = items.get(position)
        //코틀린은 리스의 get() set() 대신에 배열처럼 [] - 인덱스 번호 를 권장
        var item:Item = items[position]
        holder.tvName.setText(item.name)
        //코틀린은 get/ set권장하지 않음
        holder.tvMsg.text = item.msg

        Glide.with(context).load(item.imgId).into(holder.iv)
    }

    // 함수 리턴 값 - 할당 연산자로 단순화
    override fun getItemCount(): Int = items.size

}

옵션 메뉴에 들어갈 메뉴 아이템 지정

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/aaa" android:title="aaa" />
    <item android:id="@+id/bbb" android:title="bbb"/>
    <item android:id="@+id/ccc" android:title="ccc"/>
</menu>

MainActivity.kt

  • val 의 늦은 초기화 : by lazy
  • MutableList 객체 생성 방법

    new 로 객체 생성하지 않고 mutableListOf() 를 쓴다

  • 리사이클러뷰에 아답터 설정

    아답터 참조 객체 안만들어도 된다 - 바로 넣어줌
    코드로 LinearLayoutManager 설장하는 방법

  • onResume() 에서 데이터들을 갱신하는 작업

    recycler.adapter?.notifyDataSetChanged()
    리사이클러 아답터한테 notify

  • onCreateOptionsMenu : 옵션 메뉴 만드는 콜백 메소드
  • onOptionsItemSelected : 옵션 메뉴 아이템 선택하면 자동으로 발동하는 콜백 메소드
package com.bsj0420.ex02kotlinrecyclerview

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    //뷰 참조변수는 참조값이 변경되지 않기에 var보다는 val로 만들어보기
    //늦은 초기화
    val recycler:RecyclerView by lazy {
        findViewById(R.id.recycler)
    }

    //대량의 데이터들
    var items:MutableList<Item> = mutableListOf() //mutableListOf() 객체 만들어 주는 애

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

        //대량의 데이터 추가 (테스트 목적)
        items.add(Item("sam","hihi",R.drawable.moana01))
        items.add(Item("Lisa","ohohoho yeah",R.drawable.moana02))
        items.add(Item("jeni","moana",R.drawable.moana03))
        items.add(Item("jisoo","blackpink",R.drawable.moana04))
        items.add(Item("rose","in your area",R.drawable.moana05))

        //리사이클러뷰에 아답터 설정
        //아답터 참조 객체 안만들어도 됨 - 바로 넣어줌
        recycler.adapter = MyAdapter(this,items)
        
        //notify 할 땐 - recycler.adapter한테 노티파이


        //리사이클러뷰에 레이아웃매니저 설정 - 레이아웃에 하면 되는데 연습하기 위해 여기서 설정함
        recycler.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

    }

    override fun onResume() {
        super.onResume()

        //보통 이곳에서 데이터들을 갱신하는 작업들이 이루어짐
        // adapter은 nullable 변수임 따라서 ? 연산자 붙이기
        recycler.adapter?.notifyDataSetChanged()

        //recycler.adapter!!.notifyDataSetChanged() //무조건 널이 아니다 연산자
    }

    //옵션 메뉴 만드는 콜백 메소드
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        //자바처럼 menuInflater를 get하는 작업필요없이 액티비티에 이미 menuInflater 객체 존재
        menuInflater.inflate(R.menu.option, menu)

        return super.onCreateOptionsMenu(menu)
    }

    //옵션 메뉴 아이템 선택하면 자동으로 발동하는 콜백 메소드
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        
        when(item.itemId) {
            R.id.aaa -> Toast.makeText(this, "aaa 클릭", Toast.LENGTH_SHORT).show()
            R.id.bbb -> {
                Toast.makeText(this, "bbb 클릭", Toast.LENGTH_SHORT).show()
            }
            R.id.ccc -> Toast.makeText(this, "ccc 클릭", Toast.LENGTH_SHORT).show()
        }

        return super.onOptionsItemSelected(item)
    }

}



[3] DetailActivity

activity_detail.xml

adjustViewBounds : 이미지 뷰 쓸때 warp 쓰면 필수

<?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"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".ItemDetailActivity">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"/>


    <TextView
        android:id="@+id/tv"
        android:padding="8dp"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:textSize="20sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>


</LinearLayout>

DetailActivity.kt

  • 넘어온 인텐트 객체가 가져온 Extra 데이터 받기
  • 제목줄에 이름 표시
  • 텍스트 뷰에 대입
  • 전환효과를 줄 뷰에게 adapter에서 지정한 이름 붙여주기
package com.bsj0420.ex02kotlinrecyclerview

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.Glide

class ItemDetailActivity : AppCompatActivity() {

    val iv:ImageView by lazy { findViewById(R.id.iv) }
    //참조변수 자료형을 자동추론시키려면 .. find 할 때 제네릭으로 자료형 알려줘야함
    val tv by lazy { findViewById<TextView>(R.id.tv) }

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

        //나한테 넘어온 인텐트 객체가 가져온 Extra 데이터 받기
        var name:String? = intent.getStringExtra("name")
        var msg:String = intent.getStringExtra("msg") as String //null을 String으로 바꿔도 됨
        var imgId:Int = intent.getIntExtra("imgId",R.drawable.penguins)

        //제목줄에 이름 표시
        supportActionBar!!.title = name

        //메세지는 텍스트 뷰에 표시
        tv.text = msg

        //이미지 보여주기
        Glide.with(this).load(imgId).into(iv)
        
        
        ////////////////////////////////
        //전환효과를 줄 뷰에게 별칭 지정하기
        iv.transitionName = "iii"
        

    }
}

Adapter

🧨 주요 내용 : 화면 전환 효과

ActivityOptionsCompat

options 을 줄 떄 번들(꾸러미)로 만들어서 줘야함 옵션이 한두개가 아니니까
ActivityOptionsCompat.makeSceneTransitionAnimation(context as MainActivity, Pair(holder.iv, "iii")
1. MainActivity의 능력을 넘겨받은 context를 MainActivity로 형변환 해서 넣어줌
2. Pair() 객체 - 리사이클러뷰의 선택된 item에게 별명 붙이고 전환될 뷰에도 같은 별명을 붙여 한 쌍으로 만든다

package com.bsj0420.ex02kotlinrecyclerview

import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.app.ActivityOptionsCompat
import androidx.core.util.Pair
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bumptech.glide.Glide

// 상속 키워드 : & 상속 받을 때 () 생성자 빼먹지 않도록 조심!
// 생성자에 Context와 MutableList 받아옴
class MyAdapter(var context:Context, var items:MutableList<Item>) : Adapter<MyAdapter.VH>() {

    //이너클래스는 inner 키워드!
    //ViewHolder 만들 때 constructor()로 View를 받는다
    // itemView는 itemlayout의 최상위 카드뷰를 부른 것
    inner class VH(itemView:View) : ViewHolder(itemView) {
        val tvName:TextView by lazy { itemView.findViewById(R.id.tv_name) }
        val tvMsg:TextView by lazy { itemView.findViewById(R.id.tv_msg) }
        val iv:ImageView by lazy { itemView.findViewById(R.id.iv) }
    }

    //아이템뷰 레이아웃 불러오기
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        var itemView:View = LayoutInflater.from(context).inflate(R.layout.recycler_item,parent,false)
        return VH(itemView)
    }

    override fun onBindViewHolder(holder: VH, position: Int) {
        //var item:Item = items.get(position)
        //코틀린은 리스의 get() set() 대신에 배열처럼 [] - 인덱스 번호 를 권장
        var item:Item = items[position]
        holder.tvName.setText(item.name)
        //코틀린은 get/ set권장하지 않음
        holder.tvMsg.text = item.msg

        Glide.with(context).load(item.imgId).into(holder.iv)
        
        //아이템 뷰를 클릭했을 때 화면 이동
        // itemView : 아이템 뷰가 원래 부터 가지고 있는 멤버
        holder.itemView.setOnClickListener {
            val intent = Intent(context, ItemDetailActivity::class.java)
            intent.putExtra("name", item.name)
            intent.putExtra("msg", item.msg)
            intent.putExtra("imgId", item.imgId)

            ///////////////////장면 전환 효과 만들기/////////////////////////////
            //액티비티 전환시에 뷰들에 효과 주기 . ActivityOptions
            // options 을 줄 떄 번들(꾸러미)로 만들어서 줘야함 옵션이 한두개가 아니니까
            val options:ActivityOptionsCompat
                = ActivityOptionsCompat.makeSceneTransitionAnimation(context as MainActivity,
                Pair(holder.iv, "iii")
            )
            // context 사실 메인액티비티를 전달받은 것임 : 형변환 연산자 as로 메인액티비티임을 알려준다
            //Pair() 객체 - 리사이클러뷰의 선택된 item에게 별명 붙이고 전환될 뷰에도 같은 별명을 붙여 한 쌍으로 만든다
            /////////////////////////////////////////////////////////////////////

            context.startActivity(intent , options.toBundle())
        }
        
    }

    // 함수 리턴 값 - 할당 연산자로 단순화
    override fun getItemCount(): Int = items.size

}



profile
보조기억장치

0개의 댓글