Talk is cheap, let’s code

The previous article introduced the basics of Kotlin, but knowledge alone is not enough. The most important thing is to be able to use Kotlin in real life, which is worth learning. Here’s a simple Android application using pure Kotlin to show you how to use Kotlin in a real project.

Follow Kotlin’s path

At the end of the previous article, we showed how to create a Kotlin-based Android app, but it was too simple because the code was changed to Kotlin and the layout was still XML, which didn’t use all of Kotlin. In order to facilitate the development of Android applications and take advantage of the great advantages of Kotlin, JetBrains has also released a supporting library for Android development, Anko. Its biggest advantage is to create THE UI in the way of DSL. Let’s introduce Anko.

Anko

What is Anko and why is it used

The purpose of the Anko library is to improve the efficiency of Android development using the advantages of the Kotlin language. There are four main sections: Anko Commons, Anko Layouts, Anko SQLite, and Anko Coroutines. You can also see the official wiki for all four sections. In fact, the biggest change is the layout, regular Android projects, we generally use XML to write layout XML, actually no big problem, with the help of various development tools and open source libraries, efficiency is not low, but the biggest problem with XML is verbose, otherwise it would not be replaced by JSON. Of course, we could use XML for the layout and Kotlin for the code as shown in KotlinHello, and there’s nothing wrong with that, but the great thing about Kotlin is brevity, so using Anko you can do the same thing very succinctly with less code, and with less code, you’re more efficient.

How do I use Anko

Rewrite KotlinHello to show you how to use Anko in your project. Open the KotlinHello project from the previous article and add it to dependencies in the build.gradle section of your app:


1
Copy the code
implementation "org.jetbrains.anko:anko:$anko_version"
Copy the code

Define the anko_version variable at the top:


1
Copy the code
Ext anko_version = '0.10.5'Copy the code

Re-sync gradle and when it’s done, it’s ready to use.

Edit HelloActivity. Kt, inside onCreate, delete everything except super.oncreate, and add:


12 3 4 5 6 7 8 9 10 11 12Copy the code
    val colorTable = listOf("#ff0000", "#00ff00", "#0000ff", "#ffff00", "#00ffff", "#ff00ff")

    verticalLayout {
        val name = editText()
        button("Say Hello") {
            onClick {
                val randomIndex = (Math.random() * colorTable.size).toInt()
                setBackgroundColor(Color.parseColor(colorTable[randomIndex]))
                toast("Hello, ${name.text}! with color ${colorTable[randomIndex]}")
            }
        }
    }
Copy the code

When it works, it looks like this:

The page is a little ugly, so I’ll show you how to add layout attributes:


One, two, three, four, five, sixCopy the code
        padding = dip(20)
        val name = editText()
        name.lparams(width = matchParent) {
            topMargin = dip(20)
            bottomMargin = dip(30)
        }
Copy the code

This is what it ended up being:

Post the full code: app/build.gradle:


12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36Copy the code
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-Android-Extensions' ext.anko_version = '0.10.5' Android {compileSdkVersion 27 defaultConfig {applicationId "Net. Toughcoder. Kotlinhello" minSdkVersion 21 targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" implementation 'com. Android. Support: appcompat - v7:27.1.1' implementation 'com. Android. Support. The constraint, the constraint - layout: 1.1.0' Implementation "org. Jetbrains. Anko: anko: $anko_version" 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'}Copy the code

HelloActivity.kt:


12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23Copy the code
class HelloActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val colorTable = listOf("#ff0000", "#00ff00", "#0000ff", "#ffff00", "#00ffff", "#ff00ff")

        verticalLayout {
            padding = dip(20)
            val name = editText()
            name.lparams(width = matchParent) {
                topMargin = dip(20)
                bottomMargin = dip(30)
            }
            button("Say Hello") {
                onClick {
                    val randomIndex = (Math.random() * colorTable.size).toInt()
                    setBackgroundColor(Color.parseColor(colorTable[randomIndex]))
                    toast("Hello, ${name.text}! with color ${colorTable[randomIndex]}")
                }
            }
        }
    }
}
Copy the code

Actual combat, lu a program ape old history

A KotlinHello is still too toy, let’s do a slightly more complicated small project to practice. Considering that the biggest change Kotlin brings is to use Anko to write the layout, so let’s make a slightly more complicated layout. Therefore, we can do a program monkey Lao Calendar, its function is relatively simple, mainly is the layout, and does not involve the network. So suitable for beginners practice.

Need to understand

Before you do anything, understand the requirements. We want to masturbate is this version of the programmer old almanac. The principle is very simple, pre-define some events, tools, drinks, location, etc., and then calculate a random index using the current date, take a batch from the pre-defined, and then display it. In fact, for the logical part of the code, we just copy, don’t care too much. The key is how the layout is implemented with Anko.

To fit a lu

  1. Create a new package: Calendar

  1. Create a new Empty Activity in the Calendar: CalendarActivity


One, two, three, four, five, six, sevenCopy the code
class CalendarActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        title = "Programmer Calendar"
    }
}
Copy the code
  1. Skip to CalendarActivity when you click Button in KotlinHello

1
2
3
4
Copy the code
onClick {
    // other codes
    startActivity<CalendarActivity>()
}
Copy the code
  1. Start the layout of the overall layout is divided into five pieces: head date, appropriate head, appropriate details, bad head, bad details, bottom direction and index. One of these, the header date, can be solved with a TextView. Good and bad, they’re the same thing, you can reuse them, and the good is a list, and the bottom is a list, but because the number and each item is fixed, you can do it with three views.

Conclusion:

  1. The root layout should be a ScrollView, because if you have a lot of content, or if the screen is too small, there might be some out-of-screen space, so the root layout should be able to slide.
  2. The middle good/bad, and good or bad specific events, to use a LinearLayout to wrap the two, because the good/bad height is determined by the specific event, and to fill the background color, so the package on a layer of LinearLayout is inevitable.
  3. So, from top to bottom, a LinearLayout will do

Running effect

Final operation result:

The final code

CalendarActivity, responsible for layout and presentation


12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97Copy the code
class CalendarActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {super.oncreate (savedInstanceState) title = "ProgrammerCalendar() scrollView {verticalLayout {super.oncreate (savedInstanceState) title =" ProgrammerCalendar() scrollView {verticalLayout { val dateString = buildSpanned { append(calendar.genTodayString(), Bold) } val dateLabel = textView(dateString) { id = View.generateViewId() textColor = Color.BLACK textSize = Sp (6.4f).tofloat () singleLine = true textAlignment = view.text_alignment_Center}.lparams(width = matchParent, height = wrapContent) { topMargin = dip(10) bottomMargin = dip(10) } val (goodList, BadList) = calendar.gentodayLuck () generateLuck(" ", color.parsecolor ("#ffee44"), color.parsecolor ("# DDDDDD "), GenerateLuck (" ", color.parsecolor ("#ff4444"), color.parsecolor ("# aaaAAA "), // Direction val directionDetail = buildSpanned {append(" Seat orientation: ", Bold) AppEnd (" face ") append(calendar.gendirection (), foregroundColor(color.green)) append(" Write program, least buggy." } val direction = extraLabel(directionDetail) // Drink val drinkDetail = buildSpanned {append(" today's Drink: ", Bold) append(calendar.gendrinks ())} val drink = extraLabel(drinkDetail) val girlDetail = buildSpanned {append(" ", Bold) append(calendar.genGirlsIndex()) } val girl = extraLabel(girlDetail) } } } private fun @AnkoViewDslMarker _LinearLayout.extraLabel(detail: CharSequence): TextView { return textView(detail) { textSize = sp(5).toFloat() horizontalPadding = dip(10) verticalPadding = dip(6) } }  private fun @AnkoViewDslMarker _LinearLayout.generateLuck(type: String, typeColor: Int, detailColor: Int, eventList: List<Map<String, String>>) { linearLayout { orientation = LinearLayout.HORIZONTAL val good = textView(type) { id = View.generateViewId() textColor = Color.WHITE textSize = sp(14).toFloat() textAlignment = View.TEXT_ALIGNMENT_CENTER gravity = Gravity.CENTER backgroundColor = typeColor }.lparams(width = dip(100), height = matchParent) val goodDetail = verticalLayout { id = View.generateViewId() eventList.forEach { val caption = textView(it[ProgrammerCalendar.NAME]) { textColor = Color.BLACK textSize = sp(6).toFloat() } val detail = textView(it[ProgrammerCalendar.DESCRIPTION]) { textColor = Color.GRAY textSize = sp(5).toFloat() horizontalPadding = dip(2) } } horizontalPadding = dip(15) backgroundColor = detailColor verticalPadding = dip(10) }.lparams(width = matchParent, height = wrapContent) } } }Copy the code

ProgrammerCalendar, here is the business logic


12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 109 110 111 112 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 166 167 168 169 170 171 172 175 176 177 178 179 180 181 182 183 184Copy the code
class ProgrammerCalendar {
    companion object EventKey {
        const val NAME = "name"
        const val GOOD = "good"
        const val BAD = "bad"
        const val WEEKEND = "weekend"
        const val DATE = "date"
        const val TYPE = "type"
        const val DESCRIPTION = "description"
    }

    private val weeks = listOf( "日", "一", "二", "三", "四", "五", "六")
    private val directions = listOf("北方", "东北方", "东方", "东南方", "南方", "西南方", "西方", "西北方")
    private val activities = listOf(
            mapOf(NAME to "写单元测试", GOOD to "写单元测试将减少出错", BAD to "写单元测试会降低你的开发效率"),
            mapOf(NAME to "洗澡", GOOD to "你几天没洗澡了?", BAD to "会把设计方面的灵感洗掉", WEEKEND to "true"),
            mapOf(NAME to "锻炼一下身体", GOOD to "", BAD to "能量没消耗多少,吃得却更多", WEEKEND to "true"),
            mapOf(NAME to "抽烟", GOOD to "抽烟有利于提神,增加思维敏捷", BAD to "除非你活够了,死得早点没关系", WEEKEND to "true"),
            mapOf(NAME to "白天上线", GOOD to "今天白天上线是安全的", BAD to "可能导致灾难性后果"),
            mapOf(NAME to "重构", GOOD to "代码质量得到提高", BAD to "你很有可能会陷入泥潭"),
            mapOf(NAME to "使用%t", GOOD to "你看起来更有品位", BAD to "别人会觉得你在装逼"),
            mapOf(NAME to "跳槽", GOOD to "该放手时就放手", BAD to "鉴于当前的经济形势,你的下一份工作未必比现在强"),
            mapOf(NAME to "招人", GOOD to "你面前这位有成为牛人的潜质", BAD to "这人会写程序吗?"),
            mapOf(NAME to "面试", GOOD to "面试官今天心情Xiao很好", BAD to "面试官不爽,会拿你出气"),
            mapOf(NAME to "提交辞职申请", GOOD to "公司找到了一个比你更能干更便宜的家伙,巴不得你赶快滚蛋", BAD to "鉴于当前的经济形势,你的下一份工作未必比现在强"),
            mapOf(NAME to "申请加薪", GOOD to "老板今天心情很好", BAD to "公司正在考虑裁员"),
            mapOf(NAME to "晚上加班", GOOD to "晚上是程序员精神最好的时候", BAD to "", WEEKEND to "true"),
            mapOf(NAME to "在妹子面前吹牛", GOOD to "改善你矮穷挫的形象", BAD to "会被识破", WEEKEND to "true"),
            mapOf(NAME to "撸管", GOOD to "避免缓冲区溢出", BAD to "强撸灰飞烟灭", WEEKEND to "true"),
            mapOf(NAME to "浏览成人网站", GOOD to "重拾对生活的信心", BAD to "你会心神不宁", WEEKEND to "true"),
            mapOf(NAME to "命名变量\"%v\"", GOOD to "", BAD to ""),
            mapOf(NAME to "写超过%l行的方法", GOOD to "你的代码组织的很好,长一点没关系", BAD to "你的代码将混乱不堪,你自己都看不懂"),
            mapOf(NAME to "提交代码", GOOD to "遇到冲突的几率是最低的", BAD to "你遇到的一大堆冲突会让你觉得自己是不是时间穿越了"),
            mapOf(NAME to "代码复审", GOOD to "发现重要问题的几率大大增加", BAD to "你什么问题都发现不了,白白浪费时间"),
            mapOf(NAME to "开会", GOOD to "写代码之余放松一下打个盹,有益健康", BAD to "小心被扣屎盆子背黑锅"),
            mapOf(NAME to "打DOTA", GOOD to "你将有如神助", BAD to "你会被虐的很惨", WEEKEND to "true"),
            mapOf(NAME to "晚上上线", GOOD to "晚上是程序员精神最好的时候", BAD to "你白天已经筋疲力尽了"),
            mapOf(NAME to "修复BUG", GOOD to "你今天对BUG的嗅觉大大提高", BAD to "新产生的BUG将比修复的更多"),
            mapOf(NAME to "设计评审", GOOD to "设计评审会议将变成头脑风暴", BAD to "人人筋疲力尽,评审就这么过了"),
            mapOf(NAME to "需求评审", GOOD to "", BAD to ""),
            mapOf(NAME to "上微博", GOOD to "今天发生的事不能错过", BAD to "今天的微博充满负能量", WEEKEND to "true"),
            mapOf(NAME to "上AB站", GOOD to "还需要理由吗?", BAD to "满屏兄贵亮瞎你的眼", WEEKEND to "true"),
            mapOf(NAME to "玩FlappyBird", GOOD to "今天破纪录的几率很高", BAD to "除非你想玩到把手机砸了", WEEKEND to "true")
    )


    private val specials = listOf(
            mapOf(DATE to "20140214", TYPE to "BAD", NAME to "待在男(女)友身边", DESCRIPTION to "脱团火葬场,入团保平安。")
    )

    private val tools = listOf("Eclipse写程序", "MSOffice写文档", "记事本写程序", "Windows8", "Linux", "MacOS", "IE", "Android设备", "iOS设备")

    private val varNames = listOf("jieguo", "huodong", "pay", "expire", "zhangdan", "every", "free", "i1", "a", "virtual", "ad", "spider", "mima", "pass", "ui")

    private val drinks = listOf("水", "茶", "红茶", "绿茶", "咖啡", "奶茶", "可乐", "鲜奶", "豆奶", "果汁", "果味汽水", "苏打水", "运动饮料", "酸奶", "酒")

    private val today = GregorianCalendar()
    private val iday = today.get(Calendar.YEAR) * 10000 + (today.get(Calendar.MONTH) + 1) * 100 + today.get(Calendar.DAY_OF_MONTH)

    private fun random(seed: Int, index: Int): Int {
        var n = seed % 11117
        for (i in 0 until 100+index) {
            n *= n
            n %= 11117   // 11117 是个质数
        }
        return n
    }

    private fun isSomeday(): Boolean {
        return today.get(Calendar.MONTH) == 5 && today.get(Calendar.DAY_OF_MONTH) == 4
    }

    private fun star(num: Int): String {
        var result = ""
        var i = 0
        while (i < num) {
            result += "★"
            i++
        }
        while(i < 5) {
            result += "☆"
            i++
        }
        return result
    }

    private fun isWeekend(): Boolean {
        return today.get(Calendar.DAY_OF_WEEK) == 0 || today.get(Calendar.DAY_OF_WEEK) == 6
    }

    // 从 activities 中随机挑选 size 个
    private fun pickRandomActivity(activities: List<Map<String, String>>, size: Int): List<Map<String, String>> {
        val pickedEvents = activities.pickRandom(size)

        return pickedEvents.map { parse(it) }
    }

    // 从数组中随机挑选 size 个
    private fun <T> List<T>.pickRandom(size: Int): List<T> {
        var result = this.toMutableList()
        for (j in 0 until this.size - size) {
            val index = random(iday, j) % result.size
            result.removeAt(index)
        }

        return result
    }

    // 解析占位符并替换成随机内容
    private fun parse(event: Map<String, String>): Map<String, String> {
        var result = event.toMutableMap()

        if (result[NAME]?.indexOf("%v") != -1) {
            result[NAME] = result[NAME]?.replace("%v", varNames[random(iday, 12) % varNames.size])!!
        }

        if (result[NAME]?.indexOf("%t") != -1) {
            result[NAME] = result[NAME]?.replace("%t", tools[random(iday, 11) % tools.size])!!
        }

        if (result[NAME]?.indexOf("%l") != -1) {
            result[NAME] = result[NAME]?.replace("%l", (random(iday, 12) % 247 + 30).toString())!!
        }

        return result.toMap()
    }

    // 添加预定义事件
    // Should return two lists: GOOD list and BAD list, the item of list is a map(dictionary)
    private fun pickSpecials(goodList: MutableList<Map<String, String>>, badList: MutableList<Map<String, String>>) {
        specials.forEach {
            if (iday.toString() == it[DATE]) {
                if (it[TYPE] == GOOD) {
                    goodList.add(mapOf(NAME to it[NAME]!!, GOOD to it[DESCRIPTION]!!))
                } else {
                    badList.add(mapOf(NAME to it[NAME]!!, BAD to it[DESCRIPTION]!!))
                }
            }
        }
    }

    // 生成今日运势
    // Two part: from specials events and random picked from activities
    fun genTodayLuck(): Pair<List<Map<String, String>>, List<Map<String, String>>> {
        var theActivities = if (isWeekend()) activities.filter { it[WEEKEND] == "true" } else activities

        val goodList = ArrayList<Map<String, String>>()
        val badList = ArrayList<Map<String, String>>()
        pickSpecials(goodList, badList)

        val numGood = random(iday, 98) % 3 + 2
        val numBad = random(iday, 87) % 3 + 2
        val pickedEvents = pickRandomActivity(theActivities, numGood + numBad)

        // Add random picked from activities to GOOD/BAD list
        for (i in 0 until numGood) {
            goodList.add(mapOf(NAME to pickedEvents[i][NAME]!!, DESCRIPTION to pickedEvents[i][GOOD]!!))
        }
        for (i in 0 until numBad) {
            badList.add(mapOf(NAME to pickedEvents[numGood + i][NAME]!!, DESCRIPTION to pickedEvents[numGood + i][BAD]!!))
        }
        return Pair(goodList, badList)
    }

    fun genTodayString(): String {
        return "今天是" + today.get(Calendar.YEAR) +
                "年" + (today.get(Calendar.MONTH) + 1) +
                "月" + today.get(Calendar.DAY_OF_MONTH) +
                "日 星期" + weeks[today.get(Calendar.DAY_OF_WEEK) - 1]
    }

    fun genDirection(): String {
        val index = random(iday, 2) % directions.size
        return directions[index]
    }

    fun genGirlsIndex(): String {
        return star(random(iday, 6) % 5 + 1)
    }

    fun genDrinks(): String {
        return drinks.pickRandom(2).joinToString(", ")
    }
}
Copy the code

The full code can be downloaded here.

New features used

From the code, you can see that some of the Kotlin language features are used in addition to the previous article:

Ranges

Can be understood as intervals, used to iterate over some range, as can be seen from the genTodayLuck method in the example. The following is also a simple supplement:


1
2
Copy the code
for (i in 1.. For (int I = 1; i <= 10; I ++) for (I in 0 until 10) // equivalent to for (int I = 0; i < 10; i++)Copy the code

You can use it if you like.


1
Copy the code
if (a in 1.. 10) // if (1 <= a && a <= 10)Copy the code

The default step size is 1, which can also be customized:


1
Copy the code
for (i in 1.. 10 step 2) // => for (int i = 1; i <= 10; i += 2)Copy the code

Extension function

You can add methods to existing classes that are neither inherited nor composed, much like categories in Object-C. This makes it very succinct to perform an operation based on a class, such as the pickRandom method in this example. If the normal implementation passes the list as a parameter, but the Extension function is used as if it were a method provided by the Collection itself. Readability and simplicity are greatly improved.

Companion object

Companion Object is used to declare a companion object within a class. When referring to a companion object member, you omit the name of its class, as in this example. When referencing the ProgrammerCalendar Companion Object EventKeys in CalendarActivity, you can omit:


1
Copy the code
ProgrammerCalendar.DESCRIPTION
Copy the code

The const keyword

Introduced variable on an article, use the var statement, constant val to declare, the const keyword is what the devil? It is used to declare a top-level property of a class (in other words, a non-inner class), which is equivalent to static final in Java:


1
Copy the code
Const val NAME = "NAME" // public static final String NAME = "NAME ";Copy the code

The resources

  • Building a UI with Kotlin and Anko