第一个kotlin的android项目实践

周五和今天上午完成了kotlin基础笔记的整理,下面把自己写android项目的东西整理出来。

支持文档目录结构

[第一个kotlin项目基础笔记

anko相关的知识点

anko 是用来替换android中使用xml布局,改用代码布局的一个库更详细的资料

使用anko直接布局

下面是使用anko写的一个界面,里面就是dsl的一种写法,项目中还是使用传统的xml布局。现在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需要指定是谁的返回值。现在使用闭包就不知道返回给谁了,怎么办呢?
在闭包的前面加上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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容