Runtime Permission (Kotlin 버전)

|

기존에 Java 버전의 Runtime Permission을 포스팅 했었지만 이번에는 Kotlin 버전으로 포스팅합니다.

private const val PERMISSION_REQUEST_CODE = 1231

class MainActivity : AppCompatActivity() {

    private val permissions = arrayOf(
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

        if (!checkPermissions(permissions)) {
            requestPermissions(permissions, PERMISSION_REQUEST_CODE)
        }
    }

    private fun checkPermissions(permissions: Array<String>): Boolean {
        for (permission in permissions) {
            if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                return false
            }
        }
        return true
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        when (requestCode) {
            PERMISSION_REQUEST_CODE -> {
                if ((grantResults.isNotEmpty()) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {

                } else {
                    Toast.makeText(applicationContext, "Permission is not granted.", Toast.LENGTH_SHORT).show()
                }
            }

        }
    }
}

build.gradle 에서 변수 사용하기

|

gradle에서 변수를 사용하는 방법입니다.

예를 들어 특정 라이브러리의 버전을 변수로 지정할 수 있습니다.

build.gradle (Project)

buildscript {
    ext.kotlin_version = '1.3.41'
    ext.snowlib_version = 'latest.integration'

    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}


build.gradle (Module)

그리고 아래 예제와 같은 방법으로 위에서 선언한 snowlib_version이라는 변수를 사용할 수 있습니다.

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation project(':common_library')
    implementation 'com.google.code.gson:gson:2.8.5'

    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:design:28.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    implementation group: 'snowdeer.message.utils', name: 'snowdeer-message-util', version: "$snowlib_version", changing: true
    implementation group: 'snowdeer.actiontool.library', name: 'snowdeer-actiontool-library', version: "$snowlib_version", changing: true

    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

경로를 따라 그리는 Path Animation

|

먼저 View를 상속받는 캔버스(Canvas)를 가진 클래스를 하나 구현합니다. 그리고 setPercentage 메소드를 만들어줍니다. 클래스 외부에서 애니메이션을 동작시킬 때 필요한 progress 관련 메소드입니다.

class PathView(context: Context, attrs: AttributeSet) : View(context, attrs) {

    companion object {
        private const val CORNER_ROUND = 40F
    }

    private val wayPointList = ArrayList<PointF>()

    private val paint = Paint()

    private var progress = 0F
    private var pathLength = 0F

    init {
        paint.apply {
            color = context.getColor(R.color.path_color)
            style = Paint.Style.STROKE
            isAntiAlias = true
            strokeWidth = 8.0F
            strokeCap = Paint.Cap.ROUND
            strokeJoin = Paint.Join.ROUND
        }

        initDummyData()
    }

    private fun initDummyData() {
        wayPointList.add(PointF(190F, 1715F))
        wayPointList.add(PointF(270F, 1715F))
        wayPointList.add(PointF(270F, 650F))
        wayPointList.add(PointF(460F, 650F))
        wayPointList.add(PointF(460F, 500F))
    }

    fun setPath(pointList: ArrayList<Point>) {
        wayPointList.clear()

        for (p in pointList) {
            val x = MapCoordinateConverter.getCanvasXFromWorldX(p.x).toFloat()
            val y = MapCoordinateConverter.getCanvasXFromWorldX(p.y).toFloat()

            wayPointList.add(PointF(x, y))
        }

        invalidate()
    }

    fun setPercentage(percentage: Float) {
        if (percentage < 0.0f || percentage > 1.0f) {
            throw IllegalArgumentException("setPercentage not between 0.0f and 1.0f")
        }

        progress = percentage
        invalidate()
    }

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

        drawPath(canvas)
    }

    private fun drawPath(canvas: Canvas?) {
        val p = createPath()

        val measure = PathMeasure(p, false)
        pathLength = measure.length

        val total = pathLength - pathLength * progress
        val pathEffect = DashPathEffect(floatArrayOf(pathLength, pathLength), total)

        val cornerPathEffect = CornerPathEffect(CORNER_ROUND)
        paint.pathEffect = ComposePathEffect(cornerPathEffect, pathEffect)

        canvas?.drawPath(p, paint)
    }

    private fun createPath(): Path {
        val p = Path()

        if(wayPointList.size > 0) {
            p.moveTo(wayPointList[0].x, wayPointList[0].y)
            for (pf in wayPointList) {
                p.lineTo(pf.x, pf.y)
            }
        }

        return p
    }

}

위에서 만든 클래스를 외부에서 사용할 때는 다음과 같이 작성하면 됩니다.

class MapView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) {

    private val pathView = PathView(context, attrs)

    init {

        val layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT)

        addView(pathView, layoutParams)
    }

    fun start() {
        playPathAnimation(pathView)
    }

    fun setPath(pointList: ArrayList<Point>) {
        pathView.setPath(pointList)
    }

    private fun playPathAnimation(target: View) {
        val anim = ObjectAnimator.ofFloat(target, "percentage", 0.0f, 1.0f)

        anim.duration = 3000
        anim.interpolator = LinearInterpolator()
        anim.start()
    }
}

TextView의 Width를 Programatically하게 얻기

|

TextView에 Text가 렌더링되기 전에 미리 width를 얻는 코드입니다. Text의 width는 TextView에서 출력되는 폰트의 종류와 크기 등에 영향을 받기 때문에, TextView의 현재 Paint() 정보를 가져와서 계산을 해줍니다.

textView.setText(texxt);
int width = (int)textView.getPaint().measureText(text);

다양한 Animation 샘플(Scene Transition)

|
package com.snowdeer.animation.sample.fragment

import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.transition.*
import com.snowdeer.animation.sample.R
import kotlinx.android.synthetic.main.fragment_scene_change.view.*
import kotlinx.android.synthetic.main.scene1.view.*

class SceneChangeFragment :Fragment() {

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

        val view = inflater.inflate(R.layout.fragment_scene_change, container, false)

        val scene1 = Scene(view.scene_root!!, view.container)
        val scene2 = Scene.getSceneForLayout(view.scene_root, R.layout.scene2, activity!!)
        val scene3 = Scene.getSceneForLayout(view.scene_root, R.layout.scene3, activity!!)


        view.scene_1_button.setOnClickListener {
            TransitionManager.go(scene1)
        }

        view.scene_2_button.setOnClickListener {
            val set = TransitionSet()
            val slide = Slide(Gravity.LEFT)
            slide.addTarget(R.id.image2)
            set.addTransition(slide)
            set.addTransition(ChangeBounds())
            set.ordering = TransitionSet.ORDERING_TOGETHER
            set.duration = 350
            TransitionManager.go(scene2, set)
        }

        view.scene_3_button.setOnClickListener {
            TransitionManager.go(scene3)
        }

        return view
    }
}