val animation: ObjectAnimator = ObjectAnimator.ofFloat(this, "rotation", degree)
    animation.apply {
    duration = getRollDuration()
    interpolator = DecelerateInterpolator()
    addListener(object: Animator.AnimatorListener {
        override fun onAnimationStart(p0: Animator?) {
        }

        override fun onAnimationEnd(p0: Animator?) {
            var degree = rotation%360
            var sweep = 360 / PAINTS.size
            var position = PAINTS.size-1 - (degree/sweep).toInt();
            listener?.onRouletteStop(position)
        }

        override fun onAnimationCancel(p0: Animator?) {
        }

        override fun onAnimationRepeat(p0: Animator?) {
        }
    })
}
animation.start()

코드는 네 줄인데 이틀이나 잡아먹었다.

돌아간만큼 - 된다고 생각하고 해당하는 각도의 위치를 계산하였더니 된다.

 


 

다 써놓고 하는 말이지만 이거를 포스팅으로 써도 될지 확신이 들지 않았다.

하루 삽질하고 오늘 퇴근하면서 주차장에서 번쩍 생각이 들어서 해봤더니.. 어라 되네??

그래서 내가 이해하고 만든 게 아닌...? 근데 결과는 잘 나오는...? 그런 코드라..

그냥 '어 이러면 될 거 같은데?' 했더니 되버린.. 그런 코드다 ㅜㅜ

(하루아침에 이랬나 코딩&디버깅의 대부분이 '엥 혹시?ㅋㅋ'로 때려맞추는 나란 여자..)

 

 

 

어제 만든 액티비티에 돌리기 버튼과 원판을 추가하였다.

원판은 View를 상속받은 Custom View를 만들었고

돌리기 버튼을 누르면 원판이 돌아가도록 하였다.

 

원판의 영역 갯수는 일단 고정적으로 3개만 주었고

돌릴 때의 시간이나 돌리는 각도는 랜덤으로 주었다.

 

1. 원판 만들기

RouletteView.kt

class RouletteView : View {
    val COLORS = arrayOf(0xFFDDDDDD, 0xFFEEEEEE, 0xFFCCCCCC)	//룰렛 구역에 들어갈 색상
    val PAINTS = arrayOf(Paint(), Paint(), Paint())	//paint 객체를 미리 만들어두었다
    var rectF: RectF? = null	//원호를 그릴 때 쓰이는 RectF 객체를 미리 만들어두었다

    //... 생성자
    
    init {
        for (i in PAINTS.indices) {
            PAINTS[i].color = COLORS[i].toInt()	//각 페인트는 구역 색상대로 칠해지도록 설정
        }
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        canvas?.let {
            if (rectF == null) {
                rectF = RectF(0f, 0f, width.toFloat(), height.toFloat())	//캔버스를 꽉 채우는 rectF 정의
            }
            for (i in PAINTS.indices) {
                val sweepAngle = 360f / PAINTS.size	//한번에 회전시킬 각도 = 한바퀴/갯수
                val start = -90f + (i * sweepAngle)	//12시 각도에서 시작하기 위해 -90에 돌릴 각도를 추가하였다
                it.drawArc(rectF!!, start, sweepAngle, true, PAINTS[i])	//캔버스에 원호를 그림
            }
        }
    }

 

별 것 없다 (....)

 

 

2. 원판 돌리기

다음으로 뷰를 돌려줄 roll 함수를 추가해주었다.

public fun roll() {
    roll((Math.random() * 2000+900).toFloat())
}

public fun roll(degree: Float) {
    rotation = 0f
        
    val animation: ObjectAnimator = ObjectAnimator.ofFloat(this, "rotation", degree)
    animation.duration = getRollDuration()
    animation.interpolator = DecelerateInterpolator()
    animation.start()
}

 

마찬가지로 별 것 없다 (.....)

 

 

이제 마지막으로 액티비티의 버튼을 눌렀을 때 처리를 해 준다.

 

MainActivity.kt

val rouletteView = findViewById<RouletteView>(R.id.roulette)	//나는 레이아웃 뷰에서 룰렛 뷰를 추가해 뒀음.
findViewById<View>(R.id.btn_start).setOnClickListener {
    rouletteView.roll()	//룰렛을 돌린다
}

 

요렇게 하면 버튼을 누르면 원판이 돌아간다.

 

빙글빙글..

 

 

3. 원판 멈출 때 이벤트 처리

원판을 돌리는 것까진 했고, 원판이 멈췄을 때 어떤 액션을 취해주기 위해

OnRouletteListener 인터페이스를 만들고 추가해주었다.

 

OnRouletteListener.kt

interface OnRouletteListener {
    fun onRouletteStop(position: Int) : Unit
}

 

RouletteView에 전역변수로 OnRouletteListener 객체를 선언하고

roll에 animationListener를 달아주고, 끝날 때 listener를 실행하도록 하였다.

 

RoulletView.kt의 roll(degree: Float) 메소드 내부

animation.addListener(object: Animator.AnimatorListener {
    override fun onAnimationStart(p0: Animator?) {
        TODO("Not yet implemented")
    }

    override fun onAnimationEnd(p0: Animator?) {
        Log.i("TEST", "degree : $rotationX / $rotationY / $rotation");
        listener?.onRouletteStop(0)
    }

    override fun onAnimationCancel(p0: Animator?) {
        TODO("Not yet implemented")
    }

    override fun onAnimationRepeat(p0: Animator?) {
        TODO("Not yet implemented")
    }
})

 

그리고 MainActivity에서 listener를 정의해주었다.

 

MainActivity.kt

rouletteView.listener = object: OnRouletteListener {
    override fun onRouletteStop(position: Int) {
        Toast.makeText(this@MainActivity, "roullet stop", Toast.LENGTH_SHORT).show()
    }
}

 

코틀린 공부를 할 겸 서버가 필요없는 단순한 앱을 만들어보기로 했다.

네이버에 검색하면 나오는 원판돌리기를 만들 것이다 ^▽^

오늘은 일단 시작으로 시안을 만들고 앱바를 구현하는 것까지 하였다.

 

앱바 공식문서 : 

https://developer.android.com/guide/fragments/appbar

 

AppBar 사용  |  Android 개발자  |  Android Developers

AppBar 사용 상단 앱 바는 현재 화면의 정보와 작업을 표시하기 위해 앱 창 상단을 따라 일관된 위치를 제공합니다. 그림 1. 상단 앱 바의 예 프래그먼트를 사용할 때 앱 바는 호스트 활동 또는 프

developer.android.com

 

1. 앱바 메뉴 설정하기

일단 앱바에 띄울 메뉴를 설정하기 위해 menu.xml 파일을 만들었다.

(menu.xml 파일은 res 디렉토리 내에 menu라는 디렉토리를 만들고 그 안에 생성하면 된다)

 

menu.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/action_save"
        android:icon="@drawable/icn_save"
        android:title="@string/action_save"
        app:showAsAction="always" />

    <item android:id="@+id/action_load"
        android:icon="@drawable/icn_load"
        android:title="@string/action_load"
        app:showAsAction="always" />

    <item android:id="@+id/action_share"
        android:icon="@drawable/icn_share"
        android:title="@string/action_share"
        app:showAsAction="always" />
</menu>

icon과 title은 이름만 봐도 뭔지 알 것이고

나는 아이콘이 항상 보이게 하고 싶어서 showAsAction을 always로 주었다.

 

 

2. Activity에 표시하기 + 클릭할 때 액션 주기

menu.xml을 작성한 다음 MainActivity.kt로 와서 

onCreateOptionsMenu 메소드를 오버라이딩해준다.

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu, menu)
        return true
    }

마지막을 return true로 해 줘야 메뉴바가 보인다고 한다.

 

두 번째로 메뉴를 선택했을 때의 액션을 설정하기 위해 

onOptionsItemSelected를 오버라이딩한다

override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when(item.itemId) {
            R.id.action_save, R.id.action_load, R.id.action_share -> {
                Toast.makeText(this, item.title, Toast.LENGTH_SHORT).show()
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }

item을 받아서 item의 title을 토스트로 출력하도록 하였다.

 

마지막으로 앱 바에서 Title 텍스트를 숨겨주기 위해서

onCreate의 setContentView 아래에 아래와 같이 추가한다.

supportActionBar?.setDisplayShowTitleEnabled(false)

만약 로고를 넣고 싶다면 아래처럼 설정한다. (같은 애들 쭈루룩 하는 거라 스코프 함수 사용햇음)

supportActionBar?.apply {
    setLogo(R.mipmap.ic_launcher_round)	//로고 이미지 지정
    setDisplayUseLogoEnabled(true)	//얘가 true로 해야 로고 보임
    setDisplayShowHomeEnabled(true)	//얘를 true로 해야 로고 보임22222
}

 

3. 다국어 지원

위의 menu.xml를 보면 title을 string 리소스로 사용하는 것을 볼 수 있다.

다국어 지원을 위해 그렇게 처리하였다. (다른나라 사람들이 과연 다운로드 받을지는 일단 논외로..)

다국어 지원을 위해서는 values 폴더를 따로 만들어주어야한다.

 

values-ko-rKR의 strings.xml에는 한글로 된 문자열을 써 두고

values의 string.xml에는 영어로 된 문자열을 써 두면

기기에 설정된 언어에 따라서 자동으로

언어가 한국어일때는 'values-ko-rKR'에서 리소스를 불러오고

다른 언어일 때는 values에서 리소스를 불러오게 된다.

 

와! 날먹!

 


오늘은 피그마로 시안 만드는 게 오래 걸려서 코드는 많이 만지지는 못했다.

차근차근 만들어서 내 이름 들어간 앱 하나라도 만들어봐야지 ㅜ.ㅜ 

 

코틀린 문제.pdf
0.08MB

 

나중에 있을 스터디를 위해서 문제를 만들어 보았습니다.

 

답지는 아직 만들지 못했음..

 

혹시라도 보시는 분 중에 틀린 부분 발견하시면 있으면 말씀해주세요.

 


 

※ 네이버 부스트코스 - 코틀린 프로그래밍 기본1,2 편을 참고로 하여 만들었습니다.

강의 : 

https://www.boostcourse.org/mo132

https://www.boostcourse.org/mo234

 

코틀린 프로그래밍 기본 1

부스트코스 무료 강의

www.boostcourse.org

 

함수를 변수에 할당하는 다양한 방법

fun main() {
  val calcSum : Int = sum(3, 5)   //(1)
  val calcSubtract : Int = calculate(::subtract, 10, 5)   //(2)
  val calcMultiply : Int = calculate(fun (a:Int, b:Int) = a * b, 3, 5)    //(3)
  val calcDivide : Int = calculate({ a, b -> a / b }, 10, 5)  //(4)

  println("sum : $calcSum" +
      "\nsubtract : $calcSubtract" +
      "\nmultiply : $calcMultiply" +
      "\ndivide : $calcDivide"
  );
}

fun sum (a: Int, b: Int) = a + b
fun subtract (a: Int, b: Int) = a - b

fun calculate (calc: (Int, Int) -> Int, a: Int, b: Int) : Int{
	return calc(a, b)
}

(2) 미리 만들어놓은 일반 함수를 인자값으로 넘길 때는 :: 을 사용한다.

(3) 익명 함수로 넘길 수도 있다

(4) 람다식으로 넘길 수도 있다

+ Recent posts