初學Kotlin——在自定義View裏的應用

什麼是Kotlin

Kotlin,它是JetBrains開發的基於JVM的面向對象的語言。2017年的時候被Google推薦Android的官方語言,同時Android studio 3.0正式支持這門語言,在這個編譯器上建立一個Kotlin項目,很是方便,甚至能夠Java轉爲Kotlinhtml

我主要是在經過實現自定義View過程當中,說一下KotlinJava的異同,其實二者很是類似

Kotlin語法不是太瞭解的,能夠先去看看它的官方翻譯文檔java

以Barchart-Kotlin開始提及

Barchart-Kotlin是我用Kotlin寫的一個簡易靈活的柱狀圖庫,喜歡的能夠點個star!android

1.類的屬性

在一個類裏面咱們須要定義一些屬性來保存數據和狀態git

咱們先來看看Java代碼,在BarChartView定義了一些屬性github

private SpeedLinearLayoutManger mLayoutManager;
    private BarChartAdapter mAdapter;
    private ItemOnClickListener mClickListener;
    private int mDefaultWidth = 150;
複製代碼

而後咱們再看看Kotlin是怎麼定義這些屬性的,下面的是Kotlin代碼後端

private lateinit var mLayoutManager: SpeedLinearLayoutManger
    private lateinit var mAdapter: BarChartAdapter
    private var mClickListener: ItemOnClickListener? = null
    private val mDefaultWidth = 150
複製代碼

你會發現不同的聲明方式,但重要的是varval這兩個關鍵字api

var表明的是可變的變量,至關於如今Java聲明變量的方式安全

val表明的是不可變的變量,初始化後不能再修改,至關於加了final關鍵字的變量數據結構

並且在Kotlin中屬性是須要初始化的,沒有值的時候你能夠賦值null,否則編譯會報錯。加上?的意思是你不肯定是不是這個類型,或者說是否爲null。若是以爲實在是不方便你的使用邏輯,你可使用這兩種方式延遲初始化。app

懶初始化 by lazy

lazy是指推遲一個變量的初始化時機,只有在使用的時候纔會去實例化它。適用於一個變量直到使用時才須要被初始化。在我這個項目裏面沒有使用by lazy,它大體的用法是這樣的

val data: Data by lazy {
    Data(number,string)
   }

複製代碼
延遲初始化 lateinit

lateinit是指你保證接下來在使用以前或者使用的時候會實例化它,否則你就會crash掉,這不就跟咱們使用Java屬性的方式同樣麼。。。它適用於一些view和必須用到數據結構的初始化,我以爲仍是謹慎使用比較好。

2.空安全

Kotlin能夠說是分了兩個大類型,可空類型和不可空類型,這樣作的緣由是它但願在編譯階段就把空指針這問題顯式的檢測出來,把問題留在了編譯階段,讓程序更加健壯。它經過?來表達可爲空。

mClickListener?.invoke1(position)
mClickListener?.invoke2(position)
mClickListener?.invoke3(position)
複製代碼

若是mClickListenernull的話,後面的語句是不會執行的。並且Kotlin提供了更加簡潔的操做符let

mListener?.let {  
it.invoke1(position)
it.invoke2(position)
it.invoke3(position)  
}

複製代碼

只有在非空的狀況下才執行let裏面的操做,很是簡潔。

3.構造器

經過簡單瞭解以後,咱們開始寫一個自定義View,咱們須要繼承View,Java的實現方式是這樣的

public class BarChart extends View {

    public BarChart(Context context) {
        super(context);
    }

    public BarChart(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public BarChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

複製代碼

Kotlin你能夠實現的更簡潔

class BarChart @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : View(context, attrs, defStyleAttr) {
    
    private val mContext: Context = context

    init { }
複製代碼

你能夠在init代碼塊裏面得到構造函數的傳參,固然你也能夠直接在聲明屬性的時候得到,@JvmOverloads 若是你沒有加上這個註解,它只能重載相匹配的的構造函數,而不是所有。

並且可能你也發現了,你能夠在傳參裏面初始化,這相對於Java來講,靈活太多

fun shadow(width:Int=100,height:Int = 180){ }
//你能夠這麼使用
shadow()
shadow(140)
shadow(140,200)
複製代碼

4.UI佈局

通常用咱們創造view的佈局是xml,Kotlin也是支持的,可是它更推薦你使用Anko的佈局方式。

那是什麼是Anko呢,AnkoJetBrains開發的一個強大的庫,它主要的目的是用來替代之前xml的方式來使用代碼生成UI佈局的,它包含了不少的很是有幫助的函數和屬性來避免讓你寫不少的模版代碼。

有興趣的你能夠去看看它的源碼與更多使用方式 -- Anko

首先你要在Gradle裏添加Anko的引用

// Anko Layouts
compile "org.jetbrains.anko:anko-recyclerview-v7:$anko_version"
compile "org.jetbrains.anko:anko-recyclerview-v7-coroutines:$anko_version"
compile "org.jetbrains.anko:anko-sdk25:$anko_version"
compile "org.jetbrains.anko:anko-appcompat-v7:$anko_version"
// Coroutine listeners for Anko Layouts
compile "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version"
compile "org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version"

複製代碼

而後你能夠在代碼裏寫UI佈局的代碼了,就是用Kotlin代碼替代xml

private fun createView(attrs: AttributeSet? = null, defStyleAttr: Int = 0) {

   var height = dip(150)
   attrs?.let {
            val typeArray = mContext.obtainStyledAttributes(it, R.styleable.BarChartView, defStyleAttr, 0)
            height = typeArray.getDimension(R.styleable.BarChartView_chart_height, dip(150).toFloat()).toInt()
            typeArray.recycle()
        }

    verticalLayout {
            lparams(width = matchParent, height = matchParent)

            frameLayout {
                lparams(width = matchParent, height = wrapContent)

                mLineView = view {
                    backgroundColor = R.color.gray_light
                }.lparams(width = matchParent, height = dip(0.5f)) {
                    gravity = Gravity.BOTTOM
                    bottomMargin = dip(9)
                }

                mBarView = recyclerView {
                    lparams(width = matchParent, height = height)
                }
            }

            mDateView = relativeLayout {
                lparams(width = matchParent, height = wrapContent) {
                    leftPadding = dip(10)
                    rightPadding = dip(10)
                }

                mLeftTv = textView {
                    textSize = 15f
                }

                mRightTv = textView {
                    textSize = 15f
                }.lparams(width = wrapContent, height = wrapContent) {
                    alignParentRight()
                }
            }
        }
    }

複製代碼

能夠從代碼裏看見verticalLayout(其實就是LinearLayoutvertical模式)包裹了frameLayoutrelativeLayout,裏面又有各自的子view,並且你會發現xml有的屬性這裏也有,調用起來很是的簡潔明瞭,熟練xml的來寫這個,我以爲上手應該會很快,它的缺點就是沒有預覽效果,以及實現複雜的view結構的時候會比較繁瑣,考驗盲寫的功力了。。。。

5.擴展函數

擴展函數是Kotlin很是方便實用的一個功能,它可讓咱們隨意的擴展SDK的庫,你若是以爲SDK的api不夠用,這個時候你能夠用擴展函數徹底去自定義。

例如你須要這樣來獲取顏色,每次你都須要一個上下文context

mColor = ContextCompat.getColor(mContext, R.color.primary)
複製代碼

那你能夠經過擴展Context這個SDK的類來實現更方便的使用

fun Context.color(colorRes: Int) = ContextCompat.getColor(this, colorRes)
fun View.color(colorRes: Int) = context.color(colorRes)
複製代碼

並且爲了更方便在View裏面使用,又擴展了View。在第一個方法裏面能夠發現getColor所須要的this上下文,就是Context。一樣,View裏面的contextgetContext()所獲得,也就是View裏面自己具備的公有方法。

這實際上是很驚豔的功能

那咱們能夠想想爲啥能夠這樣作,咱們知道的是KotlinJava都是在Jvm上運行的,既然都是編譯成class字節碼,那咱們是否是能夠經過字節碼來了解一些事情。

經過Android studio 3.0上的Tools的工具Show Kotlin Bytecode,能夠將剛纔的擴展函數的代碼編譯成字節碼

// access flags 0x19
  public final static color(Landroid/content/Context;I)I
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "$receiver"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 12 L1
    ALOAD 0
    ILOAD 1
    INVOKESTATIC android/support/v4/content/ContextCompat.getColor (Landroid/content/Context;I)I
    IRETURN
   L2
    LOCALVARIABLE $receiver Landroid/content/Context; L0 L2 0
    LOCALVARIABLE colorRes I L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x19
  public final static color(Landroid/view/View;I)I
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "$receiver"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 14 L1
    ALOAD 0
    INVOKEVIRTUAL android/view/View.getContext ()Landroid/content/Context;
    DUP
    LDC "context"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
    ILOAD 1
    INVOKESTATIC shadow/barchart/ExtensionsKt.color (Landroid/content/Context;I)I
    IRETURN
   L2
    LOCALVARIABLE $receiver Landroid/view/View; L0 L2 0
    LOCALVARIABLE colorRes I L0 L2 1
    MAXSTACK = 3
    MAXLOCALS = 2

複製代碼

這就是編譯後的字節碼,看不懂是吧,我也看不懂。。可是咱們能夠反編譯啊,生成Java代碼

public static final int color(@NotNull Context $receiver, int colorRes) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return ContextCompat.getColor($receiver, colorRes);
   }

   public static final int color(@NotNull View $receiver, int colorRes) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      Context var10000 = $receiver.getContext();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "context");
      return color(var10000, colorRes);
   }

複製代碼

經過反編譯咱們能夠知道這是個靜態函數,Intrinsics.checkParameterIsNotNull($receiver, "$receiver");這個函數只起到了判空的做用,真正的代碼是return ContextCompat.getColor($receiver, colorRes);這個不就是咱們剛剛用的Java代碼嘛。

重點是$receiver接收的對象,接收的是Context實例,這樣的話就能夠調用這個類的全部公有方法和公有屬性,並且它是靜態函數,它能夠經過類直接調用。因此擴展函數的實現只不過是加了一個須要當前對象的靜態方法,調用的時候傳入一個當前對象而已。

咱們剛剛用到了反編譯,由於咱們知道KotlinJava的生成字節碼是同樣的,那咱們能夠了解一下Kotlin的編譯過程,它跟Java的區別是什麼。能夠看一下這篇文章Kotlin編譯過程分析

經過這篇文章你能夠了解到Kotlin在編譯過程當中,與Java是大體相同的,只是在最後生成目標代碼的時候作了不少相似於封裝的事情,生成相同的語法結構,Kotlin將咱們原本在代碼層作的一些封裝工做轉移到了編譯後端階段。那咱們可不能夠在學習Kotlin的時候去這樣理解,其實Kotlin是一種封裝了Java的強大的語法糖,Java作不到的事情,Kotlin其實也作不到,例如對象只能訪問公有屬性。

6.數據類

Kotlin中你要實現數據類是很是簡單的,並不須要手動加上get/set方法

data class BarItem(
        private val barData: BarData,
        var select: Boolean = false) {
    fun getData(): Double {
        return barData.getData()
    }

    fun getTag(): String {
        return barData.getTag()
    }
}
複製代碼

在這個類裏面你會發現,我還聲明瞭兩個方法,我須要的是BarData裏的數據,但又不只僅只須要這個數據,因此我聲明瞭一個類來封裝它,其實這個至關於裝飾者模式了。Kotlin有更好的方式實現這個模式

data class BarItem(
        private val barData: BarData,
        var select: Boolean = false) : BarData by barData
複製代碼

7.when

BarChartView裏用到一個與switch語法相似的語句

mSelectPosition = when (mStyle) {
            ScrollStyle.DEFAULT -> mDataList.size - 1
            ScrollStyle.START -> 0
            ScrollStyle.NONE -> -1
            ScrollStyle.CUSTOM -> mSelectPosition
            else -> { }
        }
複製代碼

它是起到了跟switch同樣的做用,而且更強大,由於它是表達式,因此是有返回值的,在Kotlin中控制流大都是表達式,都是能夠有返回值的。

8.集合

Kotlin是區分可變集合和不可變集合的,它給你提供這兩種選擇。

//不可變
 Set<out T>
 Map<K, out V>
 List<out T>
 //可變
 MutableSet<T>
 MutableMap<K, V>
 MutableList<T> 
複製代碼

不可變的集合提供只讀屬性,例如size,get等,Kotlin不提供專門的語法結構建立list或者set,是用標準庫獲取的,咱們能夠看一下它的源碼是怎樣實現。

/** * Returns an immutable list containing only the specified object [element]. * The returned list is serializable. * @sample samples.collections.Collections.Lists.singletonReadOnlyList */
@JvmVersion
public fun <T> listOf(element: T): List<T> = java.util.Collections.singletonList(element)

/** * Returns an empty new [MutableList]. * @sample samples.collections.Collections.Lists.emptyMutableList */
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()

複製代碼

從源碼能夠看見這是Javajava.util.Collections.singletonListArrayList,這就能夠理解爲啥不可變和可變的了。。。

總結

Kotlin相對於Java,更像是封裝了Java的強大語法糖,使用了更簡潔的語法提升了生產力。

相關文章
相關標籤/搜索