Android - 계산기

Code_Alpacat·2021년 11월 25일

안드로이드 기초

목록 보기
18/18

계산기의 간단한 기능을 구현할 것이다. 여러 계산이 아닌 1차원적인 계산만을 할 것이라, 후위연산자를 이용한 계산기 구현은 나중에 따로 만들어보도록하겠다.

기본기능사용

  • Onclick ->setOnClickListner의 역할을 해준다. 동작구문으로 코틀린으로 연결을 해줘야 사용이 가능하고, 이벤트를 처리해준다.
    레이아웃을 짜면서 무슨 동작이 들어가는 함수를 짜야할지 미리 구조를 짜도록 활용이 가능하다.
  • stateListAnimator-> 애니메이션 모션을 설정하는 기능이다. 기본적으로 버튼의 눌리는 음영이 있지만 @null로 모션을 삭제할 수 있다.
  • enabled - 해당 기능이 동작하지 않도록 설정하거나 동작하도록 설정한다.
  • clickable - 클릭이 가능한지 아닌지 여부를 결정한다.
  • scrollView - 스크롤뷰로 위아래로 스크롤하는 뷰를 생성한다. 히스토리 버튼을 누르면, ConstraintLayout안에 지정해둔 invisible한 layout안의 버튼들과 scrollview가 드러나도록 설정할 때 사용했다.

TableLayout

  • shrinkColumns -> 너비에 맞게 행의 객체들의 크기를 조절해준다.
  • tableRow를 지정해 가로의 격자공간을 지정해 버튼을 배치했다.

LayoutInFlater

LayoutInflater는 xml에 정의된 뷰 리소스를 메모리에서 사용할 수 있는 View객체로 반환하는 역할을 한다.
따라서, 뷰 리소스가 정의되어 있는 Xml을 객체화(Objectify) 실제 앱에 사용하고 싶을 때 이 과정을 담당하는 것이 LayoutInflater이다.

Kotlin

  • spannableStringBuilder - 택스트뷰의 일부 색상이나 크기 등을 변경하는 기능. 선언 후에는, 해당변수 ssb.setSpan(what, start변수, end변수, flag)로 표현하며, start부터 end까지 what에 해당하는 조건으로 변경시켜준다.
  • 확장함수 - 클래스에서 제공하는 기능들인 isEmpty와 같은 함수처럼 따로 선언해서 원래 존재하던 메소드처럼 사용하는 함수.

Room

Room은 데이터베이스를 다루는 sqlite와 같은 기능을 하지만 내부의 DB를 이용해 다른 쓰레드를 활용하는 방법이다. 순서는 다음과 같다.

  • DB를 다룰 새로운 패키지를 생성하고, db의 테이블의 역할을 하게될 클래스를 생성한다.@Entity즉 테이블의 개체에 어노테이션을 붙여 data class 내부에는 유일한 값인 PrimaryKey(ex 홍길동-> 이름이므로 유일함)과 테이블에 저장할 속성들을 지정할 수 있다.
@Entity
data class History(
    @PrimaryKey val uid: Int?,
    @ColumnInfo(name = "expression") val expression: String?,
    @ColumnInfo(name = "result") val result: String?
)
  • build.gradle(module) -> plugin 내부에 id 'kotlin-kapt' 선언 -> dependencies 아래에 아래와 같이 room을 사용할 수 있도록 가져온다. 버젼 정보는 안드로이드 room을 검색해 최신 버전을 추가하는 방법이 나온다. 간단히 room을 적고 import할 수도 있다.

    implementation "androidx.room:room-runtime:2.2.6"
    kapt "androidx.room:room-compiler:2.2.6"

  • 다음은 DAO(Data Access Object)다. 우리가 데이터에 접근할 수 있는 메서드를 정의해놓은 인터페이스다.

    @INSERT 삽입
    @DELETE 삭제
    @UPDATE 수정

등의 기능이 있고, 검색하거나 다른 세부기능을 원한다면 sql의 문법에 맞게 어노테이션을 붙여 Query를 아래와 같이 선언해야한다.
아래에 작성된 코드를 보면 history에서 모든 데이터를 가져오라는 쿼리를 getAll()의 형태로 History Entity의 List를 불러오는 것을 알 수 있다.

@Dao
//history를 지우고 join하는 것을 관리.
interface HistoryDao {
    //모든 history를 검색하는 sql 쿼리문.
    @Query("SELECT * FROM history")
    fun getAll(): List<History>

    //insert하는 메소드
    @Insert
    fun insertHistory(history: History)

    @Query("DELETE FROM history")
    fun deleteAll()

/*
    @Delete
    fun delete(history: History)
    //result 조건을 걸고 찾는다. LIMIT 1을 걸면 하나만 반환이 가능하다.
    @Query("SELECT * FROM history WHERE result LIKE :result")
    fun findByResult(result: String)

 */
}
  • 마지막으로 추상클래스를 통해 데이터를 관리하는 데이터베이스를 만들어줘야한다. 새로 만든 패키지에 클래스를 하나 더 만들어주면된다. 데이터베이스의 인자로는 entity로 생성한 클래스의 이름과 버전이 있다.
    이때, 버전은 앱의 데이터베이스 구조가 업데이트 등으로 바뀌는 경우에 버전을 달리해줘 오류를 방지하기 위한 용도다.
@Database(entities = [History::class], version = 1)
abstract class AppDataBase : RoomDatabase() {

    abstract fun historyDao(): HistoryDao

}

이제 mainactivity에서 생성한 db를 사용하는 방법이다.

  • 변수에 db를 선언해줘야한다. 참고로 AppDataBase는 내가 생성한 데이터베이스 추상클래스의 이름이다.
lateinit var db: AppDataBase
  • 그리고 생명주기인 OnCreate에서 databaseBuilder를 사용해 인자는 (context, 데이터베이스 클래스, DB가 저장될 이름) 이다.
db = Room.databaseBuilder(
            applicationContext,
            AppDataBase::class.java,
            "historyDB"
        ).build()
  • 이 DB의 작업은 다른 쓰레드에서 진행되어야하므로 쓰레드의 runnable 구현체를 이용해야한다. 만약, 메인쓰레드에서 진행하고싶다면 runonUIThread를 사용해야한다.
    위에서 말했듯이 아래에서는 db라는 변수를 선언해 AppDatabase라는 데이터베이스에서 가져왔고, sql의 쿼리를 사용하기위해 Dao를 가져온 모습이다. 그 안에서는, History(primarykey, columninfo, columninfo2)의 형태로 uid와 지정한 테이블의 속성값 2개를 인자로 받았다.
Thread(Runnable {
            db.historyDao().insertHistory(History(null, expressionText, resultText))
        }).start()
  • 마지막으로, 이 계산기의 계산기록은 모두 앱의 메인 UI에서 진행되므로 runonUIThread를 사용해줄 차례다. 여기서, 위에서 언급했던 layoutinflater을 사용했다. 참고로, expression과 result는 처음 Entity의 속성에 붙인 이름이다.
Thread(Runnable {
            db.historyDao().getAll().reversed().forEach {
                //LinearLayout안의 view가 없으므로 layoutinflater을 사용한다.
                //새로운 쓰레드가 아닌 메인쓰레드로 전환해서 작업해야함.
                runOnUiThread {
                    //root =historyLinearLayout이지만 나중에 add로 붙일것임.
                    val historyView = LayoutInflater.from(this).inflate(R.layout.history_row, null, false)
                   
                   
                   historyView.findViewById<TextView>(R.id.expressionTextView).text = it.expression
                    historyView.findViewById<TextView>(R.id.resultTextView).text = " = ${it.result}"

                    historyLinearLayout.addView(historyView)

                }

            }
        }).start()

이 room이라는 기능은 처음 접하는데 너무 헷갈리고 어려워 여러번을 다시 조사하고 코드리뷰를 해봤다. 뭐든지 이해하면 그다음에 사용할 때 수월할테니 헛투루 지나치지 않기를 당부한다.

profile
In the future, I'm never gonna regret, cuz I've been trying my best for every single moment.

0개의 댓글