第四章 UI

注:因为和第一行代码第二版高度重合,所以我就记了一些我不太熟的地方和不一样的地方

4.2常用控件

4.2.2Button

android:textAllCaps="false"//取消默认大写字体

设计点击事件:

  1. 匿名内部类式
button.setOnClickListener{
            
        }
  1. 实现接口式
class MainActivity : AppCompatActivity() , View.OnClickListener{
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener(this) 
    }

    override fun onClick(p0: View?) {
        when(p0?.id){
            R.id.button ->{
                
            }
        }
    }
}

补充:kotlin 的set/get方法的语法糖,一般直接写带有它们的格式然后利用代码提示tab来获取简便用法

4.2.4 ImageView

imageView.setImageResource(R.drawable.img_2)

4.2.5 ProgressBar

4.2.6 AlertDialog

AlertDialog.Builder(this).apply {
                setTitle("This is a Title")
                setMessage("This is a Message")
                setCancelable(false)
                setPositiveButton("OK"){
                    dialog,which ->
                }
                setNegativeButton("Cancel"){
                    dialog,which ->
                }
                show()
            }

4.3 布局

4.3.1 线性布局

4.3.2 相对布局

  1. 相对父布局位置
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="button1"
        />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="button2"
        />
    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="button3"
        />
    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentBottom="true"
        android:text="button4"
        />
    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:text="button5"
        />

</RelativeLayout>
  1. 相对其他控件位置
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/button3"
        android:layout_toLeftOf="@id/button3"
        android:text="button1"
        />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/button3"
        android:layout_toRightOf="@id/button3"
        android:text="button2"
        />
    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="button3"
        />
    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/button3"
        android:layout_toLeftOf="@id/button3"
        android:text="button4"
        />
    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/button3"
        android:layout_toRightOf="@id/button3"
        android:text="button5"
        />

</RelativeLayout>
  1. 边缘对齐
  • android:layout_alignLeft 表示一个控件和另一个控件左边缘对齐
  • 右边,底部和顶部类似

4.3.3 FrameLayout

控件默认摆放在左上角,xml中放在下面的控件压在上层,用layout_gravity定位

4.4 创建自定义控件

4.4.1 引入布局

  1. 写布局文件
  2. 利用 引入

补充:supportActioinBar?.hide( )隐藏标题栏

4.4.3 创建自定义控件

  1. 自定义控件布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:layout_height="wrap_content">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/titleBack"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:text="Back"
        android:textColor="#fff"
        ></Button>

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/titleText"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Title Text"
        android:textColor="#fff"
        android:textSize="24sp"
        ></TextView>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/titleEdit"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:text="Edit"
        android:textColor="#fff"
        ></Button>


</LinearLayout>
  1. 编写自定义控件代码
class TitleLayout(context: Context, attrs: AttributeSet):LinearLayout(context,attrs) {
    init{
        LayoutInflater.from(context).inflate(R.layout.title,this)//动态加载布局
        titleBack.setOnClickListener {
            val activity = context as Activity //as是kotlin中的强制类型转换
            activity.finish()
        }
        titleEdit.setOnClickListener {
            Toast.makeText(context,"You clicked Edit button",Toast.LENGTH_LONG).show()
        }
    }

}
  1. 引入控件
<android.bignerdranch.uilayouttest.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"></android.bignerdranch.uilayouttest.TitleLayout>

##4.5 ListView

4.5.1 简单用法

  1. 布局放入listview
  2. 代码
private val data = listOf("苹果","梨子","香蕉","西瓜","凤梨","火龙果","龙眼","枸杞","水蜜桃","樱桃")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val adapter = ArrayAdapter<String>(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item,data)
        listView.adapter = adapter
    }

4.5.2 定制 ListView 的界面

  1. 准备一组图片,放下android-xxhdpi下
  2. 定义实体类
class Fruit(val name:String,val imageId:Int) {
}
  1. 定义子项布局 fruit_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"/>

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp" />

</LinearLayout>
  1. 自定义适配器
class FruitAdapter(activity: Activity, val resourceId: Int,data:List<Fruit>) : ArrayAdapter<Fruit>(activity,resourceId,data){

    //每个子项被滚动到屏幕内的时候会被调用
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val view = LayoutInflater.from(context).inflate(resourceId,parent,false)//false 只让我们在父布局中声明的layout起作用,不会为这个view生命父布局。
        val fruitImage : ImageView = view.findViewById(R.id.fruitImage)
        val fruitName : TextView = view.findViewById(R.id.fruitName)
        val fruit =  getItem(position)
        if (fruit != null) {
            fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }
        return view
    }
}
  1. 编写代码
class MainActivity : AppCompatActivity() {

    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()
        val adapter = FruitAdapter(this, R.layout.fruit_item,fruitList)
        listView.adapter = adapter
    }

    private fun initFruits(){
        repeat(2){//kotlin中的标准函数,把Lambda中的代码执行两遍
            fruitList.add(Fruit("Apple",R.drawable.apple_pic))
            fruitList.add(Fruit("Banana",R.drawable.banana_pic))
            fruitList.add(Fruit("Orange",R.drawable.orange_pic))
            fruitList.add(Fruit("Watermelon",R.drawable.watermelon_pic))
            fruitList.add(Fruit("Pear",R.drawable.pear_pic))
            fruitList.add(Fruit("Grape",R.drawable.grape_pic))
            fruitList.add(Fruit("Pineapple",R.drawable.pineapple_pic))
            fruitList.add(Fruit("Strawberry",R.drawable.strawberry_pic))
            fruitList.add(Fruit("Cherry",R.drawable.cherry_pic))
            fruitList.add(Fruit("Mango",R.drawable.mango_pic))
        }
    }

}

4.5.3 提升 ListView 的运行效率

class FruitAdapter(activity: Activity, val resourceId: Int,data:List<Fruit>) : ArrayAdapter<Fruit>(activity,resourceId,data){

    //每个子项被滚动到屏幕内的时候会被调用
    //convertView 用于将之前加载好的布局进行缓存,以便重用
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val view:View
        val  viewHolder: ViewHolder
        if (convertView == null) {
            view = LayoutInflater.from(context).inflate(resourceId,parent,false)
            val fruitImage : ImageView = view.findViewById(R.id.fruitImage)
            val fruitName : TextView = view.findViewById(R.id.fruitName)
            //将实例存在ViewHolder里,然后再存在Tag里
            viewHolder = ViewHolder(fruitImage,fruitName)
            view.tag = viewHolder
        }else{
            view = convertView
          	//有缓存后从Tag里取出
            viewHolder = view.tag as ViewHolder
        }

        val fruit =  getItem(position)
        if (fruit != null) {
            viewHolder.fruitImage.setImageResource(fruit.imageId)
            viewHolder.fruitName.text = fruit.name
        }
        return view
    }

  
    inner class ViewHolder(val fruitImage: ImageView, val fruitName: TextView)
}

4.5.4 点击事件

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()
        val adapter = FruitAdapter(this, R.layout.fruit_item,fruitList)
        listView.adapter = adapter
        listView.setOnItemClickListener { _, _, position, _ ->
            val fruit = fruitList[position]
            Toast.makeText(this,fruit.name,Toast.LENGTH_LONG).show()
        }
    }

补充

  • 如果需要知道传入什么参数可以点进去看源码
  • kotlin中可以将没使用的参数用下划线替代

4.6 RecycleView

  1. 准备一组图片,子项视图,水果类,放置RecycleView
  2. 编写适配器
class FruitAdapter(val fruitList:List<Fruit>): RecyclerView.Adapter<FruitAdapter.ViewHolder>() {
    //创建ViewHolder实例
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitAdapter.ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false)
        return ViewHolder(view)
    }
    //给子项赋值,滚动到屏幕中间的时候执行
    override fun onBindViewHolder(holder: FruitAdapter.ViewHolder, position: Int) {
        val fruit = fruitList[position]
        holder.fruitImage.setImageResource(fruit.imageId)
        holder.fruitName.text = fruit.name
    }
    //告诉有多少子项
    override fun getItemCount() = fruitList.size

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
        val fruitName : TextView = view.findViewById(R.id.fruitName)
    }
}
  1. 编写代码
class MainActivity : AppCompatActivity() {
    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()
        val layoutManager = LinearLayoutManager(this)
        recycleView.layoutManager = layoutManager
        val adapter = FruitAdapter(fruitList)
        recycleView.adapter = adapter
    }

    private fun initFruits(){
        repeat(2){
            fruitList.add(Fruit("Apple",R.drawable.apple_pic))
            fruitList.add(Fruit("Banana",R.drawable.banana_pic))
            fruitList.add(Fruit("Orange",R.drawable.orange_pic))
            fruitList.add(Fruit("Watermelon",R.drawable.watermelon_pic))
            fruitList.add(Fruit("Pear",R.drawable.pear_pic))
            fruitList.add(Fruit("Grape",R.drawable.grape_pic))
            fruitList.add(Fruit("Pineapple",R.drawable.pineapple_pic))
            fruitList.add(Fruit("Strawberry",R.drawable.strawberry_pic))
            fruitList.add(Fruit("Cherry",R.drawable.cherry_pic))
            fruitList.add(Fruit("Mango",R.drawable.mango_pic))
        }
    }
}

4.6.2 横向滚动和瀑布流布局

  • 横向布局
  1. 横向布局
//布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="80dp"
    android:orientation="vertical"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"/>

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp" />

</LinearLayout>
  1. 代码
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
  • 瀑布流布局
  1. 布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_margin="5dp"
    android:orientation="vertical"
    android:layout_height="wrap_content">
		<--宽度由布局的列数自动适配,而不是固定值,所以宽度设为填充父布局-->
    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"/>

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:layout_marginTop="10dp" />

</LinearLayout>
  1. 代码
class MainActivity : AppCompatActivity() {
    private val fruitList = ArrayList<Fruit>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()
        val layoutManager = StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL)
        recycleView.layoutManager = layoutManager
        val adapter = FruitAdapter(fruitList)
        recycleView.adapter = adapter
    }

    private fun initFruits(){
        repeat(2){
            fruitList.add(Fruit(getRandomLengthString("Apple"),R.drawable.apple_pic))
            fruitList.add(Fruit(getRandomLengthString("Banana"),R.drawable.banana_pic))
            fruitList.add(Fruit(getRandomLengthString("Orange"),R.drawable.orange_pic))
            fruitList.add(Fruit(getRandomLengthString("Watermelon"),R.drawable.watermelon_pic))
            fruitList.add(Fruit(getRandomLengthString("Pear"),R.drawable.pear_pic))
            fruitList.add(Fruit(getRandomLengthString("Grape"),R.drawable.grape_pic))
            fruitList.add(Fruit(getRandomLengthString("Pineapple"),R.drawable.pineapple_pic))
            fruitList.add(Fruit(getRandomLengthString("Strawberry"),R.drawable.strawberry_pic))
            fruitList.add(Fruit(getRandomLengthString("Cherry"),R.drawable.cherry_pic))
            fruitList.add(Fruit(getRandomLengthString("Mango"),R.drawable.mango_pic))
        }
    }

    private fun getRandomLengthString(str:String):String{
        val n = (1..20).random()
        val builder = StringBuilder()
        repeat(n){
            builder.append(str)
        }
        return builder.toString()
    }
}

4.6.3 点击事件

viewHolder.itemView.setOnClickListener{
            val position = viewHolder.adapterPosition
            val fruit = fruitList[position]
            Toast.makeText(parent.context,"you clicked view ${fruit.name}",Toast.LENGTH_SHORT).show()
        }
        viewHolder.fruitImage.setOnClickListener{
            val position = viewHolder.adapterPosition
            val fruit = fruitList[position]
            Toast.makeText(parent.context,"you clicked view ${fruit.imageId}",Toast.LENGTH_SHORT).show()
        }

补充:dp是一种和屏幕密度无关的尺寸单位,可以保证在不同手机上显示效果尽可能的一致。

4.7 编写界面的实践

4.7.1 制作 9-Patch 图片

  1. 获得原始图片

[外链图片转存中…(img-5KFLVQC2-1660655504008)]

  1. 右击图片 Create 9 - Patch file
  2. 给四个边框绘制小黑点,左边框和上边框为设置需要拉伸就拉伸的区域。另外两个是放置内容的区域。shift擦除
  3. 删除原来的图片

4.7.2 编写精美的聊天界面

  1. 主界面
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:background="#d8e0e8">

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/recycleView"
        ></androidx.recyclerview.widget.RecyclerView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:id="@+id/inputText"
            android:layout_weight="1"
            android:hint="Type something here"
            android:maxLines="2"
            ></EditText>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send"
            android:id="@+id/send"
            ></Button>

    </LinearLayout>

</LinearLayout>
  1. 定义实体类Msg
class Msg(val content:String,val type:Int) {
    companion object{
        //常量关键字只能在单例类、companion object或顶层方法中才能用
        const val TYPE_RECEIVED = 0
        const val TYPE_SEND = 1
    }
}
  1. 编写子项布局 msg_left.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:background="@drawable/message_left_original"
        >


        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/leftMsg"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#fff"
            ></TextView>
    </LinearLayout>

</FrameLayout>

4.编写子项布局 msg_right.xml 。图片资源为第一行代码配套资源

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:background="@drawable/message_right"
        >


        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rightMsg"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#000"
            ></TextView>
    </LinearLayout>

</FrameLayout>
  1. 编写适配器
class MsgAdapter(val msgList:List<Msg>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
		//编写两个ViewHolder分别用于缓存两个布局中的控件
    inner class LeftViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val leftMsg: TextView = view.findViewById(R.id.leftMsg)
    }

    inner class RightViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val rightMsg: TextView = view.findViewById(R.id.rightMsg)
    }

    
    override fun getItemViewType(position: Int): Int {
        val msg = msgList[position]
        //返回当前 position 对应的消息类型
        return msg.type
    }

    //根据不同的 viewType 加载不同的布局,并创建不同的 ViewHolder
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = if(viewType == Msg.TYPE_RECEIVED){
        val view =
            LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item, parent, false)
        LeftViewHolder(view)
    }else{
        val view =
            LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item, parent, false)
        RightViewHolder(view)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val msg = msgList[position]
        when (holder) {
            is LeftViewHolder -> holder.leftMsg.text = msg.content
            is RightViewHolder -> holder.rightMsg.text = msg.content
            else -> throw  IllegalAccessException()
        }
    }

    override fun getItemCount() = msgList.size

}
  1. 编写代码
class MainActivity : AppCompatActivity() , View.OnClickListener{

    private val msgList = ArrayList<Msg>()

    private var adapter:MsgAdapter? = null
 		//问号表示该类型可为空

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initMsg()
        val layoutManager = LinearLayoutManager(this)
        recycleView.layoutManager = layoutManager
        adapter = MsgAdapter(msgList)
        recycleView.adapter = adapter
        send.setOnClickListener(this)
    }

    private fun initMsg() {
        val msg1 = Msg("Hello guy.", Msg.TYPE_RECEIVED)
        msgList.add(msg1)
        val msg2 = Msg("Hello. Who is that?",Msg.TYPE_SEND)
        msgList.add(msg2)
        val msg3 = Msg("This is Tom.Nice talking to you.",Msg.TYPE_RECEIVED)
        msgList.add(msg3)
    }

    override fun onClick(p0: View?) {
        when(p0){
            send ->{
                val content  = inputText.text.toString()
                if (content.isNotEmpty()) {
                    val msg = Msg(content, Msg.TYPE_SEND)
                    msgList.add(msg)
                  	//因为adapter是在创建变量之后别的地方初始化,所以使用里面的方法需要判空处理,否则编译不能通过
                    adapter?.notifyItemInserted(msgList.size-1)//当有消息时刷新RV中的显示
                    recycleView.scrollToPosition(msgList.size-1)//定位到最后一行
                    inputText.setText("")//清空输入框
                }
            }
        }
    }
}

4.8 Kotlin:延迟初始化和密封类

4.8.1 对变量延迟初始化

对上面的代码进行优化,利用 lateinit 延迟初始化关键字

class MainActivity : AppCompatActivity() , View.OnClickListener{

    private val msgList = ArrayList<Msg>()

    private lateinit var adapter:MsgAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initMsg()
        val layoutManager = LinearLayoutManager(this)
        recycleView.layoutManager = layoutManager
        //利用代码判断是否已经完成初始化,可以避免对一个变量的重复初始化
        if (!::adapter.isInitialized) {
            adapter = MsgAdapter(msgList)
        }
        recycleView.adapter = adapter
        send.setOnClickListener(this)
    }

    private fun initMsg() {
        val msg1 = Msg("Hello guy.", Msg.TYPE_RECEIVED)
        msgList.add(msg1)
        val msg2 = Msg("Hello. Who is that?",Msg.TYPE_SEND)
        msgList.add(msg2)
        val msg3 = Msg("This is Tom.Nice talking to you.",Msg.TYPE_RECEIVED)
        msgList.add(msg3)
    }

    override fun onClick(p0: View?) {
        when(p0){
            send ->{
                val content  = inputText.text.toString()
                if (content.isNotEmpty()) {
                    val msg = Msg(content, Msg.TYPE_SEND)
                    msgList.add(msg)
                    adapter.notifyItemInserted(msgList.size-1)//当有消息时刷新RV中的显示
                    recycleView.scrollToPosition(msgList.size-1)//定位到最后一行
                    inputText.setText("")//清空输入框
                }
            }
        }
    }
}
4.8.2 密封类
  • 问题
interface Result
class Success(val msg:String) : Result
class Failure(val error: Exception) :Result

fun getResultMsg(result: Result) = when (result) {
    is Success -> result.msg
    is Failure -> result.error.message
    //不得不编写,否则会认为缺少条件分支,无法编译通过。这里的代码永远不会走到,只是为了满足编译检查。并且如果Result增加一个实现接口,并且没在这里添加分支,程序就会崩溃
    else -> throw IllegalArgumentException()
}
  • 解决办法:密封类 sealed class
sealed class Result
//类可以继承,需要加括号
class Success(val msg:String) : Result()
class Failure(val error: Exception) :Result()

//当 when 语句传入密封类变量作为条件时,Kotlin 编译器会自动检查该密封类有哪些子类,并且强制要求你将每一个子类所对应的条件全部处理。当Result有新的实现类,此时这个方法就能报错,必须
//新增条件语句才能通过编译
fun getResultMsg(result: Result) = when (result) {
    is Success -> result.msg
    is Failure -> "Error is ${result.error.toString()}"
}
  • 优化上面的适配器代码
  1. 新建一个 MsgViewHolder.kt 代码
sealed class MsgViewHolder(view: View):RecyclerView.ViewHolder(view)
    //编写两个ViewHolder分别用于缓存两个布局中的控件
    class LeftViewHolder(view: View) : MsgViewHolder(view) {
        val leftMsg: TextView = view.findViewById(R.id.leftMsg)
    }

    class RightViewHolder(view: View) :  MsgViewHolder(view) {
        val rightMsg: TextView = view.findViewById(R.id.rightMsg)
    }
  1. 修改适配器中的语句
class MsgAdapter(val msgList:List<Msg>) : RecyclerView.Adapter<MsgViewHolder>() {


 		...

    override fun onBindViewHolder(holder:MsgViewHolder, position: Int) {
        val msg = msgList[position]
        when (holder) {
            is LeftViewHolder -> holder.leftMsg.text = msg.content
            is RightViewHolder -> holder.rightMsg.text = msg.content
        }
    }
		...

}