第一個kotlin的android項目實踐

周五和今天上午完成了kotlin基礎筆記的整理,下面把自己寫android項目的東西整理出來。

支持文檔目錄結構

[第一個kotlin項目基礎筆記

anko相關的知識點

anko 是用來替換android中使用xml布局,改用代碼布局的一個庫更詳細的資料

使用anko直接布局

下面是使用anko寫的一個界面,里面就是dsl的一種寫法,項目中還是使用傳統的xml布局?,F在anko布局有個問題就是android stdio不能夠預覽,現在kotlin已經是官方推薦語言了,不能預覽的問題可能很快就會恢復。

setContentView(rootVertical {
            paddingWithBottomFixed = dip(16)
            backgroundResource = R.color.entrance_bg

            relativeLayout {
                verticalLayout {
                    linearLayout {
                        orientation = LinearLayout.HORIZONTAL
                        imageView {
                            gravity = Gravity.LEFT
                            imageResource = R.drawable.back_white

                            onClick { finish() }
                        }.lparams {
                            width = dip(30)
                            height = dip(30)
                        }

                        textView {
                            textColor = resources.getColor(R.color.white)
                            textSize = 16f
                            text = "手機號登錄"
                            gravity = Gravity.RIGHT
                            onClick { finish() }
                        }.lparams {
                            width = matchParent
                            minimumHeight = dip(40)
                        }
                    }.lparams {
                        width = matchParent
                        height = wrapContent
                    }

                    textView {
                        textSize = 22f
                        textColor = resources.getColor(R.color.white)
                        text = "登錄"
                    }.lparams {
                        width = wrapContent
                        height = wrapContent
                        topMargin = dip(50)
                    }
                    linearLayout {
                        orientation = LinearLayout.VERTICAL
                        textView {
                            textSize = 16f
                            textColor = resources.getColor(R.color.white)
                            text = "用戶名"
                        }.lparams {
                            width = wrapContent
                            height = wrapContent
                            topMargin = dip(20)
                        }
                        loginName = autoCompleteTextView {
                            textColor = resources.getColor(R.color.white)
                            textSize = 16f
                            maxLength = 11
                            maxLines = 1
                            hintTextColor = resources.getColor(R.color.white)
                            singleLine = true
                            backgroundResource = R.color.transparent
                        }.lparams {

                            width = matchParent
                            height = wrapContent
                        }

                        view {
                            backgroundResource = R.color.white
                        }.lparams {
                            width = matchParent
                            height = dip(0.5f)
                        }
                    }.lparams {
                        width = matchParent
                        height = wrapContent
                    }
                    linearLayout {
                        orientation = LinearLayout.VERTICAL
                        textView {
                            textSize = 16f
                            textColor = resources.getColor(R.color.white)
                            text = "密碼"
                        }.lparams {
                            width = wrapContent
                            height = wrapContent
                            topMargin = dip(20)
                        }
                        password = editText {
                            textColor = resources.getColor(R.color.white)
                            textSize = 16f
                            maxLength = 11
                            hintTextColor = resources.getColor(R.color.white)
                            backgroundResource = R.color.transparent
                        }.lparams {

                            width = matchParent
                        }

                        view {
                            backgroundResource = R.color.white
                        }.lparams {
                            width = matchParent
                            height = dip(0.5f)
                        }
                    }.lparams {
                        width = matchParent
                        height = wrapContent
                    }

                }.lparams {
                    width = matchParent
                    height = matchParent
                }

                val nextIv = imageView {
                    imageResource = R.drawable.next_white

                    onClick { login() }
                }.lparams {
                    width = dip(30)
                    height = width
                    alignParentRight()
                    alignParentBottom()
                }

                errorHint = textView {
                    textColor = resources.getColor(R.color.white)
                    drawableLeft = R.drawable.error_tip.toDrawable(context)
                    gravity = Gravity.CENTER_VERTICAL
                    text = "請輸入正確的用戶名和密碼"
                }.lparams {
                    height = dip(30)
                    sameBottomWith(nextIv)
                }
            }
        })
使用anko特性,不再使用findViewById(int id)

在我們使用 xml 布局的時候,找到界面的控件一般使用findViewById(int id)或者使用ButeerKnife注解的方式,使用了anko后,變得更加簡單,像導入一個java類一樣導入這個布局,就可以直接使用界面中定義的控件了。在登陸界面中,如下:

//導入這個布局
import kotlinx.android.synthetic.main.activity_login.*
//  對按鈕設置一個點擊事件,email_sign_in_button 這個名字就是界面中定義的名字
email_sign_in_button.setOnClickListener { attemptLogin() }

項目基礎

1. BaseActivity
被繼承的類需要使用open關鍵字修飾
package practice.yhai.com.kotlinpractice.common

import android.support.v7.app.AppCompatActivity

open class BaseActivity : AppCompatActivity()

因為這個類不需要有類體,所以{}可以省略不寫

2. App
使用了延遲加載和伴隨對象
伴隨對象

因為kotlin使用伴隨對象實現的static類似的功能,

LaunchActivity中使用到的特性

在啟動頁中,利用Handler類的postDelayed延遲兩秒跳轉到下一個界面,postDelayed需要兩個參數,一個是Runnable,另一個是Int

匿名類

定義一個匿名類,實現Runnable接口

Handler().postDelayed(object: Runnable{
            override fun run() {
            }
        },2000)
閉包作為參數

因為Runnable接口只有一個方法,這個方法沒有返回值,那么對應的閉包寫法如下

        Handler().postDelayed({

        },2000)
LoginActivity 中的特性

下面代碼生成的登陸界面的模板代碼,里面涉及到終止的概念,昨天我在寫基礎中,對這個沒有詳細說,借助下面這段代碼聊一下,生成的代碼這個樣子的:

  password.setOnEditorActionListener(TextView.OnEditorActionListener{ _, id, _ ->
            if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
                attemptLogin()
                return@OnEditorActionListener true//這里有個返回,但是這個返回比較奇怪,return@OnEditorActionListener true,那前面的@OnEditorActionListener  是什么指的是代碼塊前面TextView 的OnEditorActionListener的接口。
            }
            false
        })

上面的這個代碼我認為是通過如下步驟轉換的。
第一步:

     password.setOnEditorActionListener(object: TextView.OnEditorActionListener {
            override fun onEditorAction(p0: TextView?, p1: Int, p2: KeyEvent?): Boolean {
//                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
                return true
            }

        })

因為上面的這個回調中只有一個函數,那這可以寫成一個閉包的方式

        password.setOnEditorActionListener({ _, id, _ ->
            if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
                attemptLogin()
            }
            false
        })

上面的代碼省略的if判斷里面的返回,這個地方的return需要指定是誰的返回值?,F在使用閉包就不知道返回給誰了,怎么辦呢?
在閉包的前面加上TextView.OnEditorActionListener 接口名,在返回的時候指定我這個返回值是給OnEditorActionListener。這里還有一種方式參考資料

_ 忽略的值

閉包代碼中,->的左邊出現了_下劃線,意思是這個字段被忽略掉。

點擊事件的處理

自動生成的點擊事件是如下:

email_sign_in_button.setOnClickListener { attemptLogin() }

手寫是通過如下演變過來的:

  • 第一步,使用object : 實現一個匿名內部類
 email_sign_in_button.setOnClickListener(object : View.OnClickListener{
            override fun onClick(p0: View?) {
                attemptLogin()
            }
        })
  • 第二步,因為這個接口只有一個方法,所以可以寫成lambda
 email_sign_in_button.setOnClickListener({attemptLogin()})
  • 第三步,省略外面圓括號,這就成了代碼生成的樣子了。
email_sign_in_button.setOnClickListener { attemptLogin() }

但是,我們使用了anko庫,那么還有更好的寫法

email_sign_in_button.onClick {  attemptLogin() }
使用擴展函數,增強Int類和Context類

我們一般獲取資源文件的時候,使用如下代碼

 getResources().getDrawable(R.drawable.ic_launcher_background)
        
  getResources().getString(R.string.abc_action_bar_home_description)

利用擴展方法的特性實現,對Int類型擴展兩個方法,將resources.getString(this),ContextCompat.getColor(context, this)的功能擴展到Int類型的身上。


fun Int.toString(resources: Resources): String{
    return resources.getString(this)
}
//兼容低版本
fun Int.toColor(context: Context): Int{
    return ContextCompat.getColor(context, this)
}

fun Int.toDrawable(context: Context): Drawable {
    return ContextCompat.getDrawable(context,this)
}

那么,在項目中就可以如下使用

       //設置文本
        titleTv.text = R.string.modify.toString(resources)
        //設置背景圖片
        leftIcon.background = R.drawable.ic_arrow_back_white.toDrawable(this)
object 幾種使用情況

學習kotlin中,接觸到三種關于object 的使用

  • 匿名內部類
   email_sign_in_button.setOnClickListener(object : View.OnClickListener{
            override fun onClick(p0: View?) {
                attemptLogin()
            }

        })
  • object+類名+{}
    實現java中單例的一種方法。
    區別Kotlin中的object和companion object關鍵字
    object ProfileQuery {
        val PROJECTION = arrayOf(
                ContactsContract.CommonDataKinds.Email.ADDRESS,
                ContactsContract.CommonDataKinds.Email.IS_PRIMARY)
        val ADDRESS = 0
        val IS_PRIMARY = 1
    }
    
  • companion object,伴隨對象的使用
    伴隨對象代碼塊必須要在一個類中,因為kotlin寫代碼比較隨意,擴展名.kt的文件內就可以寫代碼,伴隨對象的代碼必須寫在class 聲明的類體重去寫。
    跟上面一種object 的使用方式的區別:我理解為*** companion object*** 修飾的是部分為static,上面一種是全局的
   companion object {

        /**
         * Id to identity READ_CONTACTS permission request.
         */
        private val REQUEST_READ_CONTACTS = 0

        /**
         * A dummy authentication store containing known user names and passwords.
         * TODO: remove after connecting to a real authentication system.
         */
        private val DUMMY_CREDENTIALS = arrayOf("foo@example.com:hello", "bar@example.com:world")
        fun  test(){
            
        } 
    }
定義組合式控件

項目中UrlConfigView為組合控件,感覺沒什么好說的!

網絡請求

使用retrofit構建網絡層
  • 初始化Retrofit對象

import okhttp3.*
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

/**
 * Created by yh on 9/23/17.
 */
object YHaiServer {
    val BASE_URL = "http://192.168.1.53:8888/yibaad/sdk/"
    private val DEFAULT_TIMEOUT = 45

    private var retrofit: Retrofit
    var apiStore: ApiStore


    init {
        val clientBuilder = OkHttpClient.Builder()
        clientBuilder.connectTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.SECONDS)
        clientBuilder.writeTimeout(10000, TimeUnit.SECONDS)
        clientBuilder.readTimeout(10000, TimeUnit.SECONDS)

        retrofit = Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(clientBuilder.build())
                .addConverterFactory(GsonConverterFactory.create())
                .build()

        apiStore = retrofit.create(ApiStore::class.java)
    }

}

apiStore的定義


import retrofit2.Call
import retrofit2.http.*

/**
 * Created by yh on 9/23/17.
 */

interface ApiStore  {

    @Headers("Content-type:application/json;charset=UTF-8")
    @POST("modifyAdSourceDeployDetail")
    fun modifyAdSourceDeployDetail(@Body detailData: BaseResponse): Call<BaseResponse>
}

Retrofit處理兩個baseUrl的資料

使用retrofit 一般結合Rxjava使用 ,我在項目中沒有使用Rxjava,代碼一樣很好理解;

 doAsync {//開啟一個異步線程
            val param = BaseResponse()
            val result  = YHaiServer.apiStore.modifyAdSourceDeployDetail(param).execute().body() as BaseResponse
            if (result.code == 200){
                uiThread {//刷新UI界面
                }
            }else{ //錯誤的情況
                uiThread {//刷新UI界面
                }
            }
        }

在上面的調用中,我遇到了一個不能獲取正確數據的情況,但是程序也沒有發生異常,后面發現是doAsync代碼塊中已經把異常捕獲了。如果想知道具體是什么異常,像java一樣try{}catch(e: Exception){}finally{}就能捕獲到時什么異常

RecyclerView 展示數據
        recyclerView.layoutManager =  LinearLayoutManager(this)
        recyclerView.setHasFixedSize(true)
        recyclerView.adapter = MainRecyclerAdapter(this,datas)
      //請求數據
        doAsync {
            val data = YHaiServer.apiStore.getWeather("CN101010100").execute().body() as Data
            datas.addAll(data.HeWeather5!![0].hourly_forecast)
                uiThread {
                recyclerView.adapter.notifyDataSetChanged()
                }
        }
    inner class MainRecyclerAdapter(val context: Context, val datas: MutableList<HourlyForecastBean>) : RecyclerView.Adapter<MainViewHolder>() {
        override fun onCreateViewHolder(p0: ViewGroup?, p1: Int): MainViewHolder {
            val view = LayoutInflater.from(context).inflate(R.layout.item_weather_view,null)
            return MainViewHolder(view)
        }

        override fun onBindViewHolder(holder: MainViewHolder?, postion: Int) {
            val data = datas[postion]
        //因為holder 被轉換了一次,后面就知道通過內置的itemView找到界面上的控件
            holder as MainViewHolder
            holder.itemView.time.text = "time  = ${data.date}"
            holder.itemView.code.text = "cond.code = ${data.cond.code}"
            holder.itemView.txt.text =  "cond.txt = ${data.cond.txt}"
            holder.itemView.dir.text = "wind.dir = ${data.wind.dir}"
            holder.itemView.sc.text = "wind.sc = ${data.wind.sc}"
            holder.itemView.deg.text = "wind.deg = ${data.wind.deg}"
            holder.itemView.spd.text = "wind.spd = ${data.wind.spd}"

        }

        override fun getItemCount(): Int {
            return datas.size
        }

    }

    class MainViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView)

看起來舒服的資料

Demo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容