fun main(args : Array) { println("Hello Kotliner!") println("Click this green button at the top right!") }
沒錯,這是一段可以嵌入到部落格中獨立執行的kotlin程式碼。要注意的是,您還能修改其中的程式碼,而不只是讓它執行起來。
// The code below doesn't compile. Add only one char to make it runnable again fun main(args : Array) { val fix = "Kotlin " val me = "is great" println(fixme) }
這很酷,不是嗎?讓上述程式碼也順利工作起來吧。
通常來說,您並不希望所有程式碼段都顯示出來,而只想展示當中最有趣和最重要的部分。這也是可以實現的。
fun main(args: Array) { val hint = "Click the plus button to see the full code" //sampleStart println (hint.length) println("yes, 42") //sampleEnd }
您也可以新增測試:
// Implement extension functions Int.r() and Pair.r() // and make them convert Int and Pair to RationalNumber. fun Int.r(): RationalNumber = TODO() fun Pair<Int, Int>.r(): RationalNumber = TODO() data class RationalNumber(val numerator: Int, val denominator: Int)
你也可以將JavaScript設定為目標平臺,甚至還能在canvas上繪製圖案
/** * In this example strange creatures are watching the kotlin logo. * You can drag'n'drop them as well as the logo. Doubleclick to add * more creatures but be careful. They may be watching you! */ package creatures import jquery.* import org.w3c.dom.* import kotlin.browser.document import kotlin.browser.window import kotlin.js.Math fun getImage(path: String): HTMLImageElement { val image = window.document.createElement("img") as HTMLImageElement image.src = path return image } val canvas = initalizeCanvas() fun initalizeCanvas(): HTMLCanvasElement { val canvas = document.createElement("canvas") as HTMLCanvasElement val context = canvas.getContext("2d") as CanvasRenderingContext2D context.canvas.width = window.innerWidth.toInt(); context.canvas.height = window.innerHeight.toInt(); document.body!!.appendChild(canvas) return canvas } val context: CanvasRenderingContext2D get() { return canvas.getContext("2d") as CanvasRenderingContext2D } abstract class Shape() { abstract fun draw(state: CanvasState) // these two abstract methods defines that our shapes can be dragged operator abstract fun contains(mousePos: Vector): Boolean abstract var pos: Vector var selected: Boolean = false // a couple of helper extension methods we'll be using in the derived classes fun CanvasRenderingContext2D.shadowed(shadowOffset: Vector, alpha: Double, render: CanvasRenderingContext2D.() -> Unit) { save() shadowColor = "rgba(100, 100, 100, $alpha)" shadowBlur = 5.0 shadowOffsetX = shadowOffset.x shadowOffsetY = shadowOffset.y render() restore() } fun CanvasRenderingContext2D.fillPath(constructPath: CanvasRenderingContext2D.() -> Unit) { beginPath() constructPath() closePath() fill() } } val Kotlin = Logo(v(250.0, 75.0)) class Logo(override var pos: Vector) : Shape() { val relSize: Double = 0.18 val shadowOffset = v(-3.0, 3.0) val imageSize = v(150.0, 150.0) var size: Vector = imageSize * relSize // get-only properties like this saves you lots of typing and are very expressive val position: Vector get() = if (selected) pos - shadowOffset else pos fun drawLogo(state: CanvasState) { size = imageSize * (state.size.x / imageSize.x) * relSize // getKotlinLogo() is a 'magic' function here defined only for purposes of demonstration but in fact it just find an element containing the logo state.context.drawImage(getImage("http://try.kotlinlang.org/static/images/kotlin_logo.svg"), 0.0, 0.0, imageSize.x, imageSize.y, position.x, position.y, size.x, size.y) } override fun draw(state: CanvasState) { val context = state.context if (selected) { // using helper we defined in Shape class context.shadowed(shadowOffset, 0.2) { drawLogo(state) } } else { drawLogo(state) } } override fun contains(mousePos: Vector): Boolean = mousePos.isInRect(pos, size) val centre: Vector get() = pos + size * 0.5 } val gradientGenerator: RadialGradientGenerator? = null get() { if (field == null) { field = RadialGradientGenerator(context) } return field } class Creature(override var pos: Vector, val state: CanvasState) : Shape() { val shadowOffset = v(-5.0, 5.0) val colorStops = gradientGenerator!!.getNext() val relSize = 0.05 // these properties have no backing fields and in java/javascript they could be represented as little helper functions val radius: Double get() = state.width * relSize val position: Vector get() = if (selected) pos - shadowOffset else pos val directionToLogo: Vector get() = (Kotlin.centre - position).normalized //notice how the infix call can make some expressions extremely expressive override fun contains(mousePos: Vector) = pos distanceTo mousePos < radius // defining more nice extension functions fun CanvasRenderingContext2D.circlePath(position: Vector, rad: Double) { arc(position.x, position.y, rad, 0.0, 2 * Math.PI, false) } //notice we can use an extension function we just defined inside another extension function fun CanvasRenderingContext2D.fillCircle(position: Vector, rad: Double) { fillPath { circlePath(position, rad) } } override fun draw(state: CanvasState) { val context = state.context if (!selected) { drawCreature(context) } else { drawCreatureWithShadow(context) } } fun drawCreature(context: CanvasRenderingContext2D) { context.fillStyle = getGradient(context) context.fillPath { tailPath(context) circlePath(position, radius) } drawEye(context) } fun getGradient(context: CanvasRenderingContext2D): CanvasGradient { val gradientCentre = position + directionToLogo * (radius / 4) val gradient = context.createRadialGradient(gradientCentre.x, gradientCentre.y, 1.0, gradientCentre.x, gradientCentre.y, 2 * radius) for (colorStop in colorStops) { gradient.addColorStop(colorStop.first, colorStop.second) } return gradient } fun tailPath(context: CanvasRenderingContext2D) { val tailDirection = -directionToLogo val tailPos = position + tailDirection * radius * 1.0 val tailSize = radius * 1.6 val angle = Math.PI / 6.0 val p1 = tailPos + tailDirection.rotatedBy(angle) * tailSize val p2 = tailPos + tailDirection.rotatedBy(-angle) * tailSize val middlePoint = position + tailDirection * radius * 1.0 context.moveTo(tailPos.x, tailPos.y) context.lineTo(p1.x, p1.y) context.quadraticCurveTo(middlePoint.x, middlePoint.y, p2.x, p2.y) context.lineTo(tailPos.x, tailPos.y) } fun drawEye(context: CanvasRenderingContext2D) { val eyePos = directionToLogo * radius * 0.6 + position val eyeRadius = radius / 3 val eyeLidRadius = eyeRadius / 2 context.fillStyle = "#FFFFFF" context.fillCircle(eyePos, eyeRadius) context.fillStyle = "#000000" context.fillCircle(eyePos, eyeLidRadius) } fun drawCreatureWithShadow(context: CanvasRenderingContext2D) { context.shadowed(shadowOffset, 0.7) { context.fillStyle = getGradient(context) fillPath { tailPath(context) context.circlePath(position, radius) } } drawEye(context) } } class CanvasState(val canvas: HTMLCanvasElement) { var width = canvas.width var height = canvas.height val size: Vector get() = v(width.toDouble(), height.toDouble()) val context = creatures.context var valid = false var shapes = mutableListOf() var selection: Shape? = null var dragOff = Vector() val interval = 1000 / 30 init { jq(canvas).mousedown { valid = false selection = null val mousePos = mousePos(it) for (shape in shapes) { if (mousePos in shape) { dragOff = mousePos - shape.pos shape.selected = true selection = shape break } } } jq(canvas).mousemove { if (selection != null) { selection!!.pos = mousePos(it) - dragOff valid = false } } jq(canvas).mouseup { if (selection != null) { selection!!.selected = false } selection = null valid = false } jq(canvas).dblclick { val newCreature = Creature(mousePos(it), this@CanvasState) addShape(newCreature) valid = false } window.setInterval({ draw() }, interval) } fun mousePos(e: MouseEvent): Vector { var offset = Vector() var element: HTMLElement? = canvas while (element != null) { val el: HTMLElement = element offset += Vector(el.offsetLeft.toDouble(), el.offsetTop.toDouble()) element = el.offsetParent as HTMLElement? } return Vector(e.pageX, e.pageY) - offset } fun addShape(shape: Shape) { shapes.add(shape) valid = false } fun clear() { context.fillStyle = "#D0D0D0" context.fillRect(0.0, 0.0, width.toDouble(), height.toDouble()) context.strokeStyle = "#000000" context.lineWidth = 4.0 context.strokeRect(0.0, 0.0, width.toDouble(), height.toDouble()) } fun draw() { if (valid) return clear() for (shape in shapes.reversed()) { shape.draw(this) } Kotlin.draw(this) valid = true } } class RadialGradientGenerator(val context: CanvasRenderingContext2D) { val gradients = mutableListOf<Array>>() var current = 0 fun newColorStops(vararg colorStops: Pair<Double, String>) { gradients.add(colorStops) } init { newColorStops(Pair(0.0, "#F59898"), Pair(0.5, "#F57373"), Pair(1.0, "#DB6B6B")) newColorStops(Pair(0.39, "rgb(140,167,209)"), Pair(0.7, "rgb(104,139,209)"), Pair(0.85, "rgb(67,122,217)")) newColorStops(Pair(0.0, "rgb(255,222,255)"), Pair(0.5, "rgb(255,185,222)"), Pair(1.0, "rgb(230,154,185)")) newColorStops(Pair(0.0, "rgb(255,209,114)"), Pair(0.5, "rgb(255,174,81)"), Pair(1.0, "rgb(241,145,54)")) newColorStops(Pair(0.0, "rgb(132,240,135)"), Pair(0.5, "rgb(91,240,96)"), Pair(1.0, "rgb(27,245,41)")) newColorStops(Pair(0.0, "rgb(250,147,250)"), Pair(0.5, "rgb(255,80,255)"), Pair(1.0, "rgb(250,0,217)")) } fun getNext(): Array> { val result = gradients.get(current) current = (current + 1) % gradients.size return result } } fun v(x: Double, y: Double) = Vector(x, y) class Vector(val x: Double = 0.0, val y: Double = 0.0) { operator fun plus(v: Vector) = v(x + v.x, y + v.y) operator fun unaryMinus() = v(-x, -y) operator fun minus(v: Vector) = v(x - v.x, y - v.y) operator fun times(koef: Double) = v(x * koef, y * koef) infix fun distanceTo(v: Vector) = Math.sqrt((this - v).sqr) fun rotatedBy(theta: Double): Vector { val sin = Math.sin(theta) val cos = Math.cos(theta) return v(x * cos - y * sin, x * sin + y * cos) } fun isInRect(topLeft: Vector, size: Vector) = (x >= topLeft.x) && (x <= topLeft.x + size.x) && (y >= topLeft.y) && (y <= topLeft.y + size.y) val sqr: Double get() = x * x + y * y val normalized: Vector get() = this * (1.0 / Math.sqrt(sqr)) } fun main(args: Array) { //sampleStart val state = CanvasState(canvas).apply { addShape(Kotlin) addShape(Creature(size * 0.25, this)) addShape(Creature(size * 0.75, this)) } window.setTimeout({ state.valid = false }, 1000) // You can drag this objects //sampleEnd } fun List.reversed(): List { val result = mutableListOf() var i = size while (i > 0) { result.add(get(--i)) } return result }
有時候您並不需要或者不能建立一個可執行的示例。此時,您可以運用highlight-only
特性,在相同的樣式下顯示這段不會執行的程式碼。
val simpleText1 = "It's just an ordinary Kotlin snippet" ... val simpleText239 = "It doesn't compile actually"
嵌入式Kotlin環境工作原理
一直以來,成千上萬的萌新通過try.kotlinlang.org這個網站作為一種互動方式來學習Kotlin。尤其是Kotlin Koans Online現在已經非常受歡迎。高階使用者可以直接利用這個開發環境來試執行小段程式碼,而無需開啟IDE,例如在StackOverflow上貼上程式碼來回答問題的時候。
Embedded Kotlin Playground在同樣的原理下工作,但是允許你在網頁中編寫及執行示例。編譯將在我們的後端伺服器上進行,然後執行在你的瀏覽器上(如果平臺是JS)或者執行在伺服器上(如果平臺是JVM)。
前端
只需要在header中新增簡單的一行程式碼,即可將該環境嵌入到你的頁面:
1 2 |
<script src="https://unpkg.com/kotlin-playground@1" data-selector="code"></script> |
現在頁面上所有程式碼塊都將被轉換為能執行的Kotlin程式碼片段。當然可以自定義data-selector
將指令碼應用到某些特定的類,也可以手動配置Kotlin執行環境。
1 2 3 4 5 6 7 |
<script src="https://unpkg.com/kotlin-playground@1"></script> <script> document.addEventListener('DOMContentLoaded', function() { KotlinPlayground('.code-blocks-selector'); }); </script> |
除此之外,還有很多其他的安裝及自定義選項,可以在文件中檢視。
後端
嵌入式環境將在後端編譯程式碼,並高亮顯示完成的資訊。通常情況下,你不需要擔心後端,直接使用我們的伺服器即可,除非你想引用自定義的JVM庫。
當編寫的示例需要引用到外部的庫,例如你在為自己的庫編寫可互動文件時,你需要配置及執行一個開發環境的例項。這是一件很容易的事:新增依賴,執行兩個預定製的gradle task,再執行docker-compose up
,看!伺服器已經跑起來了。更多的細節請檢視指引。
它正應用於這些地方
- 在官網的Kotlin文件中,我們已經大規模地去使用這個技術。所有新的文件,都含有可以執行的sample(參考Basic syntax,1.1、1.2、lambdas、Coroutines的更新內容)。對於標準庫的一些函式,也帶有這種形象的例子。
- Kotlin By Example也是採取Kotlin嵌入環境來編寫示例
- 我們也釋出了一個WordPress的外掛,增加了
kotlin
標籤,允許您在任意文章嵌入可互動的Kotlin開發環境。本文中所有示例都是在該外掛的幫助下完成編寫。 - 在Kotlin論壇裡,你可以通過
run-kotlin
標籤,在markdown語法下編寫kotlin程式碼來回答問題,但請保證語法正確。
應用場景
Kotlin嵌入環境改善了閱讀體驗,並增加了程式碼示例的表現力。讀者不僅能看到程式碼,還可以執行它,更改它,並再次執行已更改的程式碼。特別是在以下場合中,我們鼓勵所有作者都編寫可執行的Kotlin程式碼片段:
- 課堂上
- 幻燈片和書籍上的補充資料
- 庫及框架的文件
- 部落格文章中的示例
在不久的將來,我們還會為嵌入式Kotlin環境上的指令碼編寫提供支援。
fun main(args: Array) { //sampleStart println("請盡情享受Kotlin!") //sampleEnd }
原文連結:https://blog.jetbrains.com/kotlin/2018/04/embedding-kotlin-playground/