경험의 기록

▶Do it! 안드로이드 앱 프로그래밍(정재곤 지음) 을 참고하여 제작하였습니다.

 

※ 개발 방법을 일일히 기록한 강의 형식의 글이 아닙니다. 개발하면서 대략적으로 중요하고, 새로 얻게된 지식 부분만 기록한 글입니다. 자세한 내용은 깃허브 파일 참조.

github.com/HanYeop/Diary

 

HanYeop/Diary

안드로이드 스튜디오 일기장 만들기. Contribute to HanYeop/Diary development by creating an account on GitHub.

github.com


개발 목표 (구현 기능)

  • 일기 리스트 조회, 작성, 수정, 삭제
  • 위치, 날씨 연동
  • 사진, 앨범 연동
  • 데이터베이스 연동
  • 전체적인 앱 디자인(아이콘, 스플래시 화면 등)

 

 

메인 화면 만들기

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

    </FrameLayout>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:itemBackground="@color/colorPrimary"
        app:itemIconTint="@drawable/item_color"
        app:itemTextColor="@drawable/item_color"
        app:menu="@menu/menu_bottom"/>
</androidx.constraintlayout.widget.ConstraintLayout>

메인으로 사용할 화면.

프래그먼트를 표시할 프레임레이아웃 배치,

밑 부분에 바텀네비게이션을 만들고

메뉴로 사용할 레이아웃 만들기

 

menu_bottom.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/tab1"
        app:showAsAction="ifRoom"
        android:enabled="true"
        android:icon="@drawable/list_48"
        android:title="목록"/>

    <item
        android:id="@+id/tab2"
        app:showAsAction="ifRoom"
        android:enabled="true"
        android:icon="@drawable/write_48"
        android:title="작성"/>

    <item
        android:id="@+id/tab3"
        app:showAsAction="ifRoom"
        android:enabled="true"
        android:icon="@drawable/graph_48"
        android:title="통계"/>
</menu>

 

 

리스트 화면 만들기

 

repositories {
    maven { url 'https://jitpack.io' }
} // 라이브러리 주소
    dependencies{
    implementation 'lib.kingja.switchbutton:switchbutton:1.1.8'
    // 라디오 버튼
    implementation 'com.github.channguyen:rsv:1.0.1'
    // 시크바 연속x, 구간 선택
    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0-alpha'
    // 그래프
    }

리스트 화면 구성을 위해 build.gradle(Module:app)에 추가.

 

fragment_1.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:id="@+id/fragment1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginBottom="60dp"
    android:orientation="vertical"
    tools:context=".Fragment1">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <lib.kingja.switchbutton.SwitchMultiButton
            android:id="@+id/switchButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:padding="8dp"
            app:strokeRadius="5dp"
            app:strokeWidth="1dp"
            app:selectedTab="0"
            app:selectedColor="#eb7b00"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:switchTabs="@array/switch_tabs"
            app:textSize="14sp" />

        <Button
            android:id="@+id/todayWriteButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="오늘 작성"
            android:textColor="@android:color/white"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:background="@drawable/select_button"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="4dp"/>
</LinearLayout>

앞에서 추가한 라디오 버튼으로 버튼 만들고,

리싸이클러뷰를 활용하여 일기를 표시할 것이기 때문에 리싸이클러뷰 사용

note_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:layout_marginLeft="4dp"
    android:layout_marginRight="4dp"
    android:layout_marginBottom="4dp"
    app:cardBackgroundColor="#FFFFFFFF"
    app:cardCornerRadius="10dp"
    app:cardElevation="5dp">

    <LinearLayout
        android:id="@+id/layout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:visibility="visible">

        <ImageView
            android:id="@+id/moodImageView"
            android:layout_width="54dp"
            android:layout_height="54dp"
            android:layout_gravity="center_vertical"
            android:padding="5dp"
            app:srcCompat="@drawable/smile5_48" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="1"
            android:orientation="vertical">

            <TextView
                android:id="@+id/contentsTextView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="내용"
                android:textSize="24sp" />

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp">

                <ImageView
                    android:id="@+id/weatherImageView"
                    android:layout_width="32dp"
                    android:layout_height="32dp"
                    android:layout_alignParentLeft="true"
                    android:src="@drawable/weather_icon_1" />

                <ImageView
                    android:id="@+id/pictureExistsImageView"
                    android:layout_width="32dp"
                    android:layout_height="32dp"
                    android:layout_marginLeft="10dp"
                    android:layout_toRightOf="@+id/weatherImageView"
                    android:src="@drawable/picture_128" />

                <TextView
                    android:id="@+id/locationTextView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerHorizontal="true"
                    android:layout_centerVertical="true"
                    android:text="전주시 덕진구"
                    android:textSize="16sp" />

                <TextView
                    android:id="@+id/dateTextView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:text="01월 01일"
                    android:textColor="#FF0000FF"
                    android:textSize="16sp" />

            </RelativeLayout>

        </LinearLayout>

    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:visibility="gone">

        <ImageView
            android:id="@+id/pictureImageView"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_gravity="center_vertical"
            android:layout_margin="10dp"
            android:padding="5dp"
            android:src="@drawable/noimagefound" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_marginBottom="10dp"
            android:layout_weight="1"
            android:orientation="vertical">

            <TextView
                android:id="@+id/contentsTextView2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="내용"
                android:textSize="24sp" />

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp">

                <ImageView
                    android:id="@+id/moodImageView2"
                    android:layout_width="32dp"
                    android:layout_height="32dp"
                    android:layout_alignParentLeft="true"
                    android:padding="5dp"
                    app:srcCompat="@drawable/smile5_48" />

                <ImageView
                    android:id="@+id/weatherImageView2"
                    android:layout_width="32dp"
                    android:layout_height="32dp"
                    android:layout_marginLeft="10dp"
                    android:layout_toRightOf="@+id/moodImageView2"
                    android:src="@drawable/weather_icon_1" />

                <TextView
                    android:id="@+id/locationTextView2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:text="전주시 덕진구"
                    android:textSize="16sp" />

                <TextView
                    android:id="@+id/dateTextView2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:text="01월 01일"
                    android:textColor="#FF0000FF"
                    android:textSize="16sp" />

            </RelativeLayout>

        </LinearLayout>

    </LinearLayout>

</androidx.cardview.widget.CardView>

리싸이클러뷰에 넣을 아이템을 추가.

내용 중심인 경우와 사진 중심인 경우로 나눔

 

Note.class

package org.techtown.diary

class Note(var _id : Int?, var weather : String?, var address : String?, var locationX : String?, var locationY : String?,
            var contents : String?, var mood : String?, var picture : String?, var createDataStr : String?) {
}

아이템의 데이터를 담아둘 클래스 정의

 

NoteAdapter.class

package org.techtown.diary

import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.note_item.view.*

class NoteAdapter(private val items: ArrayList<Note>) :
    RecyclerView.Adapter<NoteAdapter.ViewHolder>(), OnNoteItemClickListener{

    lateinit var listener : OnNoteItemClickListener

    private var layoutType = 0
    // 내용 중심인지 사진 중심인지 판단하기 위한 변수

    override fun getItemCount() = items.size

    fun getItem(position: Int): Note? {
        return items[position]
    } // 아이템 반환

    fun addItem(item: Note?) {
        if (item != null) {
            items.add(item)
        }
    } // 아이템 추가

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflatedView = LayoutInflater.from(parent.context)
            .inflate(R.layout.note_item,parent,false)
        return ViewHolder(inflatedView,this,layoutType)
    } // 새로 만들어준 뷰홀더 생성

    override fun onBindViewHolder(holder: NoteAdapter.ViewHolder, position: Int) {
        val item = items[position]
        holder.bind(item)
        holder.setLayoutType(layoutType)
    } // 데이터 뷰홀더에 바인딩

    fun setOnItemClickListener(listener: OnNoteItemClickListener){
        this.listener = listener
    }
    override fun onItemClick(holder: ViewHolder, view: View, position: Int) {
        listener?.onItemClick(holder,view,position)
    }

    fun switchLayout(position: Int){
        layoutType = position
    } // 내용,사진 레이아웃 변경 함수

    class ViewHolder(itemView : View, listener: OnNoteItemClickListener, layoutType : Int) : RecyclerView.ViewHolder(itemView){

        init {
            itemView.setOnClickListener(View.OnClickListener {
                var position = adapterPosition
                listener?.onItemClick(this,itemView,position)
                setLayoutType(layoutType)
            })
        }

        fun bind(item : Note){
            var mood = item.mood
            var moodIndex = Integer.parseInt(mood)
            setMoodImage(moodIndex) // 기분 설정

            var picturePath = item.picture
            if(picturePath != null && !picturePath.equals("")){

                itemView.pictureExistsImageView.visibility = View.VISIBLE
                itemView.pictureImageView.visibility = View.VISIBLE
                itemView.pictureImageView.setImageURI(Uri.parse("file://$picturePath"))
            }
            else{
                itemView.pictureExistsImageView.visibility = View.GONE
                itemView.pictureImageView.visibility = View.GONE
                itemView.pictureImageView.setImageResource(R.drawable.noimagefound)
            }
            // 사진 설정
            
            var weather = item.weather
            var weatherIndex = Integer.parseInt(weather)
            setWeatherImage(weatherIndex) // 날씨 설정

            itemView.contentsTextView.text = item.contents
            itemView.contentsTextView2.text = item.contents
            // 텍스트 설정
            
            itemView.locationTextView.text = item.address
            itemView.locationTextView2.text = item.address
            // 주소 설정
            
            itemView.dateTextView.text = item.createDataStr
            itemView.dateTextView2.text = item.createDataStr
            // 날짜 설정
        }

        fun setMoodImage( moodIndex : Int){
            when(moodIndex){
                0 -> {
                    itemView.moodImageView.setImageResource(R.drawable.smile1_48)
                    itemView.moodImageView2.setImageResource(R.drawable.smile1_48)
                }
                1 -> {
                    itemView.moodImageView.setImageResource(R.drawable.smile2_48)
                    itemView.moodImageView2.setImageResource(R.drawable.smile2_48)
                }
                2 -> {
                    itemView.moodImageView.setImageResource(R.drawable.smile3_48)
                    itemView.moodImageView2.setImageResource(R.drawable.smile3_48)
                }
                3 -> {
                    itemView.moodImageView.setImageResource(R.drawable.smile4_48)
                    itemView.moodImageView2.setImageResource(R.drawable.smile4_48)
                }
                4 -> {
                    itemView.moodImageView.setImageResource(R.drawable.smile5_48)
                    itemView.moodImageView2.setImageResource(R.drawable.smile5_48)
                }
                else -> {
                    itemView.moodImageView.setImageResource(R.drawable.smile3_48)
                    itemView.moodImageView2.setImageResource(R.drawable.smile3_48)
                }
            }
        } // moodIndex에 따른 기분 이미지 출력

        fun setWeatherImage(weatherIndex : Int){
            when(weatherIndex){
                0 -> {
                    itemView.weatherImageView.setImageResource(R.drawable.weather_icon_1)
                    itemView.weatherImageView2.setImageResource(R.drawable.weather_icon_1)
                }
                1 -> {
                    itemView.weatherImageView.setImageResource(R.drawable.weather_icon_2)
                    itemView.weatherImageView2.setImageResource(R.drawable.weather_icon_2)
                }
                2 -> {
                    itemView.weatherImageView.setImageResource(R.drawable.weather_icon_3)
                    itemView.weatherImageView2.setImageResource(R.drawable.weather_icon_3)
                }
                3 -> {
                    itemView.weatherImageView.setImageResource(R.drawable.weather_icon_4)
                    itemView.weatherImageView2.setImageResource(R.drawable.weather_icon_4)
                }
                4 -> {
                    itemView.weatherImageView.setImageResource(R.drawable.weather_icon_5)
                    itemView.weatherImageView2.setImageResource(R.drawable.weather_icon_5)
                }
                5 -> {
                    itemView.weatherImageView.setImageResource(R.drawable.weather_icon_6)
                    itemView.weatherImageView2.setImageResource(R.drawable.weather_icon_6)
                }
                6 -> {
                    itemView.weatherImageView.setImageResource(R.drawable.weather_icon_7)
                    itemView.weatherImageView2.setImageResource(R.drawable.weather_icon_7)
                }
                else -> {
                    itemView.weatherImageView.setImageResource(R.drawable.weather_icon_1)
                    itemView.weatherImageView2.setImageResource(R.drawable.weather_icon_1)
                }
            }
        } // weatherIndex에 따른 날씨 이미지 출력

        fun setLayoutType(layoutType: Int){
            if( layoutType == 0){
                itemView.layout1.visibility = View.VISIBLE
                itemView.layout2.visibility = View.GONE
            }
            else if( layoutType == 1){
                itemView.layout1.visibility = View.GONE
                itemView.layout2.visibility = View.VISIBLE
            }
        } // 레이아웃 타입에 따라 내용,사진 레이아웃 하나는 비활성화, 하나는 활성화
    }


}

리싸이클러뷰를 위한 NoteAdapter 클래스 정의

import kotlinx.android.synthetic.main.note_item.view.* 선언해줌으로써 findviewbyid 사용하지 않고 직접 지정

 

OnNoteItemClickListener

package org.techtown.diary

import android.view.View

interface OnNoteItemClickListener {
    fun onItemClick(holder: NoteAdapter.ViewHolder,view : View, position : Int)

}

아이템클릭에 다른 함수 오버로딩을 위해 인터페이스 정의

 

Fragment1

package org.techtown.diary


import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_1.view.*



class Fragment1 : Fragment() {

    private var _context: Context? = null
    lateinit var recyclerView : RecyclerView
    lateinit var adapter: NoteAdapter
    private var listener: OnTabItemSelectedListener? = null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        this._context = context

        if(context is OnTabItemSelectedListener){
            listener = context
        } // is == 자바의 instanceof (자료형의 일치)
    } // 프래그먼트가 액티비티에 올라갈 때 호출

    override fun onDetach() {
        super.onDetach()

        if (_context != null){
            _context = null
            listener = null
        }
    } // 액티비티에서 내려갈 때

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        var rootView = inflater.inflate(R.layout.fragment_1, container, false) as ViewGroup
        // as 형변환

        initUI(rootView)

        return rootView
    }

    private fun initUI (rootView : ViewGroup){
        rootView.todayWriteButton.setOnClickListener{
            listener?.onTabSelected(1)
        } // 클릭시 작성 레이아웃으로
        
        rootView.switchButton.setOnSwitchListener { position, tabText ->
            adapter.switchLayout(position)
            adapter.notifyDataSetChanged()
        } // 내용,사진 위주의 레이아웃으로 바꿔주고 갱신

        adapter = NoteAdapter(arrayListOf<Note>())

        recyclerView = rootView.recyclerView
        val layoutManager = LinearLayoutManager(context)
        recyclerView.layoutManager = layoutManager


        adapter.addItem(
            Note(
                0,
                "0",
                "전주시 덕진구",
                "123",
                "",
                "1. 테스트중!",
                "0",
                "capture1.jpg",
                "3월 14일"
            )
        )
        adapter.addItem(
            Note(
                1,
                "1",
                "군산시 나운동",
                "",
                "",
                "2. 안녕하세요",
                "1",
                "capture1.jpg",
                "1월 1일"
            )
        )
        adapter.addItem(
            Note(
                2,
                "2",
                "전북대학교",
                "",
                "",
                "3. ABC123",
                "2",
                null,
                "8월 5일"
            )
        ) // 테스트용 임의 아이템 3개
        recyclerView.adapter = adapter
        // 리싸이클러뷰에 어댑터 연결
        
        adapter.setOnItemClickListener(object : OnNoteItemClickListener {
            override fun onItemClick(holder: NoteAdapter.ViewHolder, view: View, position: Int) {
                val item = adapter.getItem(position)

                Toast.makeText(context, "아이템 선택됨 : " + item?.contents, Toast.LENGTH_SHORT).show()
            }
        }) // 아이템 선택 됐을때 호출할 함수
    }
}

목록 프래그먼트의 리싸이클러뷰에 NoteAdapter 연결

 

 

일기 리스트 화면 완성

 

반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading