乐趣区

关于android:第一行代码第三版第四章UI-开发的点点滴滴

软件也要拼脸蛋,UI 开发的点点滴滴

该如何编写程序界面

在过来,Android 应用程序的界面次要是通过编写 XML 的形式来实现的。写 XML 的益处是,咱们不仅可能理解界面背地的实现原理,而且编写进去的界面还能够具备很好的屏幕适配性。等你齐全把握了应用 XML 来编写界面的办法之后,不论是进行高复杂度的界面实现,还是剖析和批改以后现有的界面,对你来说都将是手到擒来。

不过最近几年,Google 又推出了一个全新的界面布局:ConstraintLayout。和以往传统的布局不同,ConstraintLayout 不是非常适合通过编写 XML 的形式来开发界面,而是更加适宜在可视化编辑器中应用拖放控件的形式来进行操作,并且 Android Studio 中也提供了十分齐备的可视化编辑器。

尽管当初 Google 官网更加举荐应用 ConstraintLayout 来开发程序界面,但因为 ConstraintLayout 的特殊性,很难展现如何通过可视化编辑器来对界面进行动静操作。因而本书中咱们依然采纳编写 XML 的传统形式来开发程序界面,并且这也是我认为你必须把握的基本技能。

讲了这么多实践的货色,也是时候学习一下到底如何编写程序界面了,咱们就从 Android 中几种常见的控件开始吧。

罕用控件的应用办法

Android 给咱们提供了大量的 UI 控件,正当地应用这些控件就能够十分轻松地编写出相当不错的界面,上面咱们就筛选几种罕用的控件,具体介绍一下它们的应用办法。

首先新建一个 UIWidgetTest 我的项目。简略起见,咱们还是容许 Android Studio 主动创立 Activity,Activity 名和布局名都应用默认值。

TextView

TextView 的根本用法很简略,这里就不解说了。这里咱们来解说一个类 SpannableString,SpannableString 其实和 Sring 一样,Textview 能够间接通过设置 SpannableString 来显示文本,只是 SpannableString 能够显示更多的款式格调,咱们来看这样一个成果,给一段文本设置下划线,只有下划线的中央能够点击,这样一个成果只用 TextView 是无奈实现的,然而用 SpannableString 就很简略了。

咱们来实现一下这个成果,布局代码很简略,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="15sp" />
</LinearLayout>

MainActivity.kt 的代码如下所示:

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setSpecialEffect()}

    private fun setSpecialEffect() {
        tv_1.text = "已浏览并批准"

        val clickString = SpannableString("软件许可及服务协定")
        clickString.setSpan(object : ClickableSpan() {override fun onClick(widget: View) {Toast.makeText(this@MainActivity, "点击了下划线局部内容", Toast.LENGTH_SHORT).show()}

            override fun updateDrawState(ds: TextPaint) {super.updateDrawState(ds)
                // 设置色彩
                ds.color = resources.getColor(R.color.purple_200, null) 
            }
        }, 0, clickString.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

        tv_1.append(clickString)
        // 设置点击成果
        tv_1.highlightColor = Color.TRANSPARENT
        // 开始响应点击事件
        tv_1.movementMethod = LinkMovementMethod.getInstance()}
}

咱们来运行程序看一下成果:

当然 SpannableString 的能够设置的成果还有很多,有趣味的能够去查看材料。

Button

同样,Button 的根本用法很简略,咱们也不讲,咱们来讲如何给 Button 设置一个点击成果,咱们先来看看具体是怎么个成果,如下所示,点击按钮的时候,有一个反馈:

咱们来实现一下这个成果,要想实现这个成果咱们只须要在 drawable 下写一个背景选择器 selector 就能够了,具体代码如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape>
            <solid android:color="@color/purple_200_alpha"/>
        </shape>
    </item>
    <item>
        <shape>
            <solid android:color="@color/purple_200"/>
        </shape>

    </item>
</selector>

而后在布局文件中设置 background 就能够实现点击成果了,具体代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:gravity="center"
    android:orientation="vertical">

    <Button
        style="?android:attr/borderlessButtonStyle"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:background="@drawable/button_bg"
        android:text="登录"
        android:textSize="18sp" />
        
</LinearLayout>

咱们来运行一下看一下成果:

EditText

EditText 是程序用于和用户进行交互的另一个重要控件,它容许用户在控件里输出和编辑内容,并能够在程序中对这些内容进行解决。EditText 的利用场景应该算是十分广泛了,发短信、发微博、聊 QQ 等等,在进行这些操作时,你不得不应用到 EditText。那咱们来看一看如何在界面上退出 EditText 吧,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical">

    <EditText
        android:id="@+id/et_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="type something here"
        android:textColorHint="@color/purple_200" />
    
</LinearLayout>

看到这里,预计你曾经总结出 Android 控件的应用法则了。用法都很类似,给控件定义一个 id,指定控件的宽度和高度,而后再适当退出些控件特有的属性就差不多了,所以应用 XML 来编写界面其实一点都不难。当初运行一下程序,EditText 就曾经在界面上显示进去了,并且咱们是能够在外面输出内容的。

ImageView

ImageView 是用于在界面上展现图片的一个控件,它能够让咱们的程序界面变得更加丰富多彩。图片通常是放在以 drawable 结尾的目录下的,并且要带上具体的分辨率。当初最支流的手机屏幕分辨率大多是 xxhdpi 的,所以咱们在 res 目录下再新建一个 drawable-xxhdpi 目录,而后将当时筹备好的图片复制到该目录当中就能够应用 ImageView 控件来将它们显示在界面上。

罕用的控件必定不止下面这几种,然而用起来都比较简单,所以这里就不讲了。

最罕用和最难用的控件:ListView

ListView 在过来相对能够称得上是 Android 中最罕用的控件之一,简直所有的应用程序都会用到它。因为手机屏幕空间比拟无限,可能一次性在屏幕上显示的内容并不多,当咱们的程序中有大量的数据须要展现的时候,就能够借助 ListView 来实现。ListView 容许用户通过手指高低滑动的形式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据会滚动出屏幕。你其实每天都在应用这个控件,比方查看 QQ 聊天记录,翻阅微博最新消息,等等。不过比起后面介绍的几种控件,ListView 的用法绝对简单了很多,因而咱们就独自来对 ListView 进行十分具体的解说。

ListView 的简略用法

首先新建一个 ListViewTest 我的项目,并让 Android Studio 主动帮咱们创立好 Activity。而后批改 activity_main.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<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:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

在布局中退出 ListView 控件还算非常简单,先为 ListView 指定一个 id,而后将宽度和高度都设置为 match_parent,这样 ListView 就占满了整个布局的空间。

接下来批改 MainActivity 中的代码,如下所示:

class MainActivity : AppCompatActivity() {

    private val data = listOf("Apple", "Banana", "Orange", "Watermelon",
            "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango",
            "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape",
            "Pineapple", "Strawberry", "Cherry", "Mango")

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

}

既然 ListView 是用于展现大量数据的,那咱们就应该先将数据提供好。这些数据能够从网上下载,也能够从数据库中读取,应该视具体的应用程序场景而定。这里咱们就简略应用一个 data 汇合来进行测试,外面蕴含了很多水果的名称,初始化汇合的形式应用的是 listOf() 函数。

不过,汇合中的数据是无奈间接传递给 ListView 的,咱们还须要借助适配器来实现。Android 中提供了很多适配器的实现类,其中我认为最好用的就是 ArrayAdapter。它能够通过泛型来指定要适配的数据类型,而后在构造函数中把要适配的数据传入。ArrayAdapter 有多个构造函数的重载,你应该依据理论状况抉择最合适的一种。因为咱们这里提供的数据都是字符串,因而将 ArrayAdapter 的泛型指定为 String,而后在 ArrayAdapter 的构造函数中顺次传入 Activity 的实例、ListView 子项布局的 id,以及数据源。留神,咱们应用了 android.R.layout.simple_list_item_1 作为 ListView 子项布局的 id,这是一个 Android 内置的布局文件,外面只有一个 TextView,可用于简略地显示一段文本。这样适配器对象就构建好了。

最初,还须要调用 ListView 的 setAdapter() 办法,将构建好的适配器对象传递进去,这样 ListView 和数据之间的关联就建设实现了。

当初运行一下程序,成果如下图所示,能够通过滚动的形式查看屏幕外的数据。

定制 ListView 的界面

只能显示一段文本的 ListView 切实是太枯燥了,咱们当初就来对 ListView 的界面进行定制,让它能够显示更加丰盛的内容。

首先须要筹备好一组图片资源,别离对应下面提供的每一种水果,待会咱们要让这些水果名称的旁边都有一张相应的图片。

接着定义一个实体类,作为 ListView 适配器的适配类型。新建 Fruit 类,代码如下所示:

class Fruit(val name: String, val imageId: Int)

Fruit 类中只有两个字段:name 示意水果的名字,imageId 示意水果对应图片的资源 id。

而后须要为 ListView 的子项指定一个咱们自定义的布局,在 layout 目录下新建 fruit_item.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<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>

在这个布局中,咱们定义了一个 ImageView 用于显示水果的图片,又定义了一个 TextView 用于显示水果的名称,并让 ImageView 和 TextView 都在垂直方向上居中显示。

接下来须要创立一个自定义的适配器,这个适配器继承自 ArrayAdapter,并将泛型指定为 Fruit 类。新建类 FruitAdapter,代码如下所示:

class FruitAdapter(activity: Activity, private 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)
        val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
        val fruitName: TextView = view.findViewById(R.id.fruitName)
        // 获取以后项的 Fruit 实例
        val fruit = getItem(position)
        if (fruit != null) {fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }
        return view
    }
}

FruitAdapter 定义了一个主构造函数,用于将 Activity 的实例、ListView 子项布局的 id 和数据源传递进来。另外又重写了 getView() 办法,这个办法在每个子项被滚动到屏幕内的时候会被调用。

在 getView() 办法中,首先应用 LayoutInflater 来为这个子项加载咱们传入的布局。LayoutInflater 的 inflate() 办法接管 3 个参数,前两个参数咱们曾经晓得是什么意思了,第三个参数指定成 false,示意只让咱们在父布局中申明的 layout 属性失效,但不会为这个 View 增加父布局。因为一旦 View 有了父布局之后,它就不能再增加到 ListView 中了。如果你当初还不能了解这段话的含意,也没关系,只须要晓得这是 ListView 中的规范写法就能够了,当你当前对 View 了解得更加粗浅的时候,再来读这段话就没有问题了。

咱们持续往下看,接下来调用 View 的 findViewById() 办法别离获取到 ImageView 和 TextView 的实例,而后通过 getItem() 办法失去以后项的 Fruit 实例,并别离调用它们的 setImageResource() 和 setText() 办法设置显示的图片和文字,最初将布局返回,这样咱们自定义的适配器就实现了。

须要留神的是,kotlin-android-extensions 插件在 ListView 的适配器中也能失常工作,将上述代码中的两处 findViewById() 办法别离替换成 view.fruitImage 和 view.fruitName,成果是截然不同的,你能够本人入手尝试一下。最初批改 MainActivity 中的代码,如下所示:

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) {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))
        }
    }

}

能够看到,这里增加了一个 initFruits() 办法,用于初始化所有的水果数据。在 Fruit 类的构造函数中将水果的名字和对应的图片 id 传入,而后把创立好的对象增加到水果列表中。另外,咱们应用了一个 repeat 函数将所有的水果数据增加了两遍,这是因为如果只增加一遍的话,数据量还不足以充斥整个屏幕。repeat 函数是 Kotlin 中另外一个十分罕用的规范函数,它容许你传入一个数值 n,而后会把 Lambda 表达式中的内容执行 n 遍。接着在 onCreate() 办法中创立了 FruitAdapter 对象,并将它作为适配器传递给 ListView,这样定制 ListView 界面的工作就实现了。

当初从新运行程序,成果如下图所示:

尽管目前咱们定制的界面还很简略,然而置信你曾经领悟到了窍门,只有批改 fruit_item.xml 中的内容,就能够定制出各种简单的界面了。

晋升 ListView 的运行效率

之所以说 ListView 这个控件很难用,是因为它有很多细节能够优化,其中运行效率就是很重要的一点。目前咱们 ListView 的运行效率是很低的,因为在 FruitAdapter 的 getView() 办法中,每次都将布局从新加载了一遍,当 ListView 疾速滚动的时候,这就会成为性能的瓶颈。

仔细观察你会发现,getView() 办法中还有一个 convertView 参数,这个参数用于将之前加载好的布局进行缓存,以便之后进行重用,咱们能够借助这个参数来进行性能优化。批改 FruitAdapter 中的代码,如下所示:

class FruitAdapter(activity: Activity, private val resourceId: Int, data: List<Fruit>) :
    ArrayAdapter<Fruit>(activity, resourceId, data) {override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        // Kotlin 中 ?: 意思是如果 convertView 为空,View 就等于冒号前面的,否则 View 就等于 convertView
        val view: View =
            convertView ?: LayoutInflater.from(context).inflate(resourceId, parent, false)
        val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
        val fruitName: TextView = view.findViewById(R.id.fruitName)
        // 获取以后项的 Fruit 实例
        val fruit = getItem(position)
        if (fruit != null) {fruitImage.setImageResource(fruit.imageId)
            fruitName.text = fruit.name
        }
        return view
    }
    
}

能够看到,当初咱们在 getView() 办法中进行了判断:如果 convertView 为 null,则使 LayoutInflater 去加载布局;如果不为 null,则间接对 convertView 进行重用。这样就大大提高了 ListView 的运行效率,在疾速滚动的时候能够体现出更好的性能。

不过,目前咱们的这份代码还是能够持续优化的,尽管当初曾经不会再反复去加载布局,然而每次在 getView() 办法中依然会调用 View 的 findViewById() 办法来获取一次控件的实例。咱们能够借助一个 ViewHolder 来对这部分性能进行优化,批改 FruitAdapter 中的代码,如下所示:

class FruitAdapter(activity: Activity, private val resourceId: Int, data: List<Fruit>) :
    ArrayAdapter<Fruit>(activity, resourceId, data) {inner class ViewHolder(val fruitImage: ImageView, val fruitName: TextView)

    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 = ViewHolder(fruitImage, fruitName)
            view.tag = viewHolder
        } else {
            view = convertView
            viewHolder = view.tag as ViewHolder
        }

        // 获取以后项的 Fruit 实例
        val fruit = getItem(position)
        if (fruit != null) {viewHolder.fruitImage.setImageResource(fruit.imageId)
            viewHolder.fruitName.text = fruit.name
        }
        return view
    }

}

咱们新增了一个外部类 ViewHolder,用于对 ImageView 和 TextView 的控件实例进行缓存,Kotlin 中应用 inner class 关键字来定义外部类。当 convertView 为 null 的时候,创立一个 ViewHolder 对象,并将控件的实例寄存在 ViewHolder 里,而后调用 View 的 setTag() 办法,将 ViewHolder 对象存储在 View 中。当 convertView 不为 null 的时候,则调用 View 的 getTag() 办法,把 ViewHolder 从新取出。这样所有控件的实例都缓存在了 ViewHolder 里,就没有必要每次都通过 findViewById() 办法来获取控件实例了。

通过这两步优化之后,ListView 的运行效率就曾经十分不错了。

ListView 的点击事件

话说回来,ListView 的滚动毕竟只是满足了咱们视觉上的成果,可是如果 ListView 中的子项不能点击的话,这个控件就没有什么理论的用处了。因而,咱们就来学习一下 ListView 如何能力响应用户的点击事件。

批改 MainActivity 中的代码,如下所示:

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
        listView.setOnItemClickListener { parent, view, position, id ->
            val fruit = fruitList[position]
            Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()}
    }
    
    ...

}

能够看到,咱们应用 setOnItemClickListener() 办法为 ListView 注册了一个监听器,当用户点击了 ListView 中的任何一个子项时,就会回调到 Lambda 表达式中。这里咱们能够通过 position 参数判断用户点击的是哪一个子项,而后获取到相应的水果,并通过 Toast 将水果的名字显示进去。

更弱小的滚动控件:RecyclerView

ListView 因为弱小的性能,在过来的 Android 开发当中能够说是奉献卓越,直到明天依然还有成千上万的程序在应用 ListView。不过 ListView 并不是白璧无瑕的,比方如果不应用一些技巧来晋升它的运行效率,那么 ListView 的性能就会十分差。还有,ListView 的扩展性也不够好,它只能实现数据纵向滚动的成果,如果咱们想实现横向滚动的话,ListView 是做不到的。

为此,Android 提供了一个更弱小的滚动控件——RecyclerView。它能够说是一个增强版的 ListView,不仅能够轻松实现和 ListView 同样的成果,还优化了 ListView 存在的各种不足之处。目前 Android 官网更加举荐应用 RecyclerView,将来也会有更多的程序逐步从 ListView 转向 RecyclerView,那么咱们就来具体解说一下 RecyclerView 的用法。

首先新建一个 RecyclerViewTest 我的项目,并让 Android Studio 主动帮咱们创立好 Activity。

RecyclerView 的根本用法

接下来批改 activity_main.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

在布局中退出 RecyclerView 控件也是非常简单的,先为 RecyclerView 指定一个 id,而后将宽度和高度都设置为 match_parent,这样 RecyclerView 就占满了整个布局的空间。须要留神的是,因为 RecyclerView 并不是内置在零碎 SDK 当中的,所以须要把残缺的包门路写进去。

这里咱们想要应用 RecyclerView 来实现和 ListView 雷同的成果,须要为 RecyclerView 筹备一个适配器,新建 FruitAdapter 类,让这个适配器继承自 RecyclerView.Adapter,并将泛型指定为 FruitAdapter.ViewHolder。其中,ViewHolder 是咱们在 FruitAdapter 中定义的一个外部类,代码如下所示:

class FruitAdapter(private val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() {inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val fruitName: TextView = view.fruitName
        val fruitImage: ImageView = view.fruitImage
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {val fruit = fruitList[position]
        holder.fruitName.text = fruit.name
        holder.fruitImage.setImageResource(fruit.imageId)
    }

    override fun getItemCount() = fruitList.size}

这是 RecyclerView 适配器规范的写法,尽管看上去如同多了好几个办法,但其实它比 ListView 的适配器要更容易了解。这里咱们首先定义了一个外部类 ViewHolder,它要继承自 RecyclerView.ViewHolder。而后 ViewHolder 的主构造函数中要传入一个 View 参数,这个参数通常就是 RecyclerView 子项的最外层布局,那么咱们就能够通过 findViewById() 办法来获取布局中 ImageView 和 TextView 的实例了。

FruitAdapter 中也有一个主构造函数,它用于把要展现的数据源传进来,咱们后续的操作都将在这个数据源的根底上进行。

持续往下看,因为 FruitAdapter 是继承自 RecyclerView.Adapter 的,那么就必须重写 onCreateViewHolder()、onBindViewHolder() 和 getItemCount() 这 3 个办法。onCreateViewHolder() 办法是用于创立 ViewHolder 实例的,咱们在这个办法中将 fruit_item 布局加载进来,而后创立一个 ViewHolder 实例,并把加载进去的布局传入构造函数当中,最初将 ViewHolder 的实例返回。onBindViewHolder() 办法用于对 RecyclerView 子项的数据进行赋值,会在每个子项被滚动到屏幕内的时候执行,这里咱们通过 position 参数失去以后项的 Fruit 实例,而后再将数据设置到 ViewHolder 的 ImageView 和 TextView 当中即可。getItemCount() 办法就非常简单了,它用于通知 RecyclerView 一共有多少子项,间接返回数据源的长度就能够了。

适配器筹备好了之后,咱们就能够开始应用 RecyclerView 了,批改 MainActivity 中的代码,如下所示:

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)
        recyclerView.layoutManager = layoutManager
        val adapter = FruitAdapter(fruitList)
        recyclerView.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))
        }
    }

}

能够看到,这里应用了一个同样的 initFruits() 办法,用于初始化所有的水果数据。接着在 onCreate() 办法中先创立了一个 LinearLayoutManager 对象,并将它设置到 RecyclerView 当中。LayoutManager 用于指定 RecyclerView 的布局形式,这里应用的 LinearLayoutManager 是线性布局的意思,能够实现和 ListView 相似的成果。接下来咱们创立了 FruitAdapter 的实例,并将水果数据传入 FruitAdapter 的构造函数中,最初调用 RecyclerView 的 setAdapter() 办法来实现适配器设置,这样 RecyclerView 和数据之间的关联就建设实现了。

当初运行一下程序,成果如下图所示:

能够看到,咱们应用 RecyclerView 实现了和 ListView 截然不同的成果,虽说在代码量方面并没有显著的缩小,然而逻辑变得更加清晰了。当然这只是 RecyclerView 的根本用法而已,接下来咱们就看一看 RecyclerView 还能实现哪些 ListView 实现不了的成果。

实现横向滚动和瀑布流布局

咱们曾经晓得,ListView 的扩展性并不好,它只能实现纵向滚动的成果,如果想进行横向滚动的话,ListView 就做不到了。那么 RecyclerView 就能做失去吗?当然能够,不仅能做失去,还非常简单。接下来咱们就尝试实现一下横向滚动的成果。

首先要对 fruit_item 布局进行批改,因为目前这个布局外面的元素是程度排列的,实用于纵向滚动的场景,而如果咱们要实现横向滚动的话,应该把 fruit_item 里的元素改成垂直排列才比拟正当。修 fruit_item.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="80dp"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <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>

能够看到,咱们将 LinearLayout 改成垂直方向排列,并把宽度设为 80dp。这里将宽度指定为固定值是因为每种水果的文字长度不统一,如果用 wrap_content 的话,RecyclerView 的子项就会有长有短,十分不美观,而如果用 match_parent 的话,就会导致宽度过长,一个子项占满整个屏幕。

而后咱们将 ImageView 和 TextView 都设置成了在布局中程度居中,并且应用 layout_marginTop 属性让文字和图片之间放弃肯定间隔。

接下来批改 MainActivity 中的代码,如下所示:

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // 初始化水果数据
    initFruits()
    val layoutManager = LinearLayoutManager(this)
    // 只须要加这一行代码即可
    layoutManager.orientation = LinearLayoutManager.HORIZONTAL
    recyclerView.layoutManager = layoutManager
    val adapter = FruitAdapter2(fruitList)
    recyclerView.adapter = adapter
}

从新运行一下程序,成果如下图所示:

你能够用手指在程度方向上滑动来查看屏幕外的数据。

为什么 ListView 很难或者根本无法实现的成果在 RecyclerView 上这么轻松就实现了呢?这次要得益于 RecyclerView 杰出的设计。ListView 的布局排列是由本身去治理的,而 RecyclerView 则将这个工作交给了 LayoutManager。LayoutManager 制订了一套可扩大的布局排列接口,子类只有依照接口的标准来实现,就能定制出各种不同排列形式的布局了。

除了 LinearLayoutManager 之外,RecyclerView 还给咱们提供了 GridLayoutManager 和 StaggeredGridLayoutManager 这两种内置的布局排列形式。GridLayoutManager 能够用于实现网格
布局,StaggeredGridLayoutManager 能够用于实现瀑布流布局。这里咱们来实现一下成果更加炫酷的瀑布流布局。

首先还是来批改一下 fruit_item.xml 中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:orientation="vertical">

    <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>

这里做了几处小的调整,首先将 LinearLayout 的宽度由 80dp 改成了 match_parent,因为瀑布流布局的宽度应该是依据布局的列数来主动适配的,而不是一个固定值。其次咱们应用了 layout_margin 属性来让子项之间互留一点间距,这样就不至于所有子项都紧贴在一些。最初还将 TextView 的对齐属性改成了居左对齐,因为待会咱们会将文字的长度变长,如果还是居中显示就会感觉怪怪的。

接着批改 MainActivity 中的代码,如下所示:

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)
        recyclerView.layoutManager = layoutManager
        val adapter = FruitAdapter2(fruitList)
        recyclerView.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()}

}

首先,在 onCreate() 办法中,咱们创立了一个 StaggeredGridLayoutManager 的实例。StaggeredGridLayoutManager 的构造函数接管两个参数:第一个参数用于指定布局的列数,传入 3 示意会把布局分为 3 列;第二个参数用于指定布局的排列方向,传入 StaggeredGridLayoutManager.VERTICAL 示意会让布局纵向排列。最初把创立好的实例设置到 RecyclerView 当中就能够了,就是这么简略!

没错,仅仅批改了一行代码,咱们就曾经胜利实现瀑布流布局的成果了。不过因为瀑布流布局须要各个子项的高度不统一能力看出显著的成果,为此我又应用了一个小技巧。这里咱们把眼光聚焦到 getRandomLengthString() 这个办法上,这个办法中调用了 Range 对象的 random() 函数来发明一个 1 到 20 之间的随机数,而后将参数中传入的字符串随机反复几遍。在 initFruits() 办法中,每个水果的名字都改成调用 getRandomLengthString() 这个办法来生成,这样就能保障各水果名字的长短差距比拟大,子项的高度也就各不相同了。

当初从新运行一下程序,成果如下图所示:

RecyclerView 的点击事件

和 ListView 一样,RecyclerView 也必须能响应点击事件才能够,不然的话就没什么理论用处了。不过不同于 ListView 的是,RecyclerView 并没有提供相似于 setOnItemClickListener() 这样的注册监听器办法,而是须要咱们本人给子项具体的 View 去注册点击事件。这相比于 ListView 来说,实现起来要简单一些。

那么你可能就有疑难了,为什么 RecyclerView 在各方面的设计都要优于 ListView,偏偏在点击事件上却没有解决得十分好呢?其实不是这样的,ListView 在点击事件上的解决并不人性化,setOnItemClickListener() 办法注册的是子项的点击事件,但如果我想点击的是子项里具体的某一个按钮呢?尽管 ListView 也能做到,然而实现起来就绝对比拟麻烦了。为此,RecyclerView 罗唆间接摒弃了子项点击事件的监听器,让所有的点击事件都由具体的 View 去注册,就再没有这个困扰了。

上面咱们来具体学习一下如何在 RecyclerView 中注册点击事件,批改 FruitAdapter 中的代码,如下所示:

class FruitAdapter2(private val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter2.ViewHolder>() {
    
    ...

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item, parent, false)
        val viewHolder = ViewHolder(view)

        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 image ${fruit.name}", Toast.LENGTH_SHORT).show()}

        return viewHolder
    }
    
    ...
}

能够看到,这里咱们是在 onCreateViewHolder() 办法中注册点击事件。上述代码别离为最外层布局和 ImageView 都注册了点击事件,itemView 示意的就是最外层布局。RecyclerView 的弱小之处也在于此,它能够轻松实现子项中任意控件或布局的点击事件。咱们在两个点击事件中先获取了用户点击的 position,而后通过 position 拿到相应的 Fruit 实例,再应用 Toast 别离弹出两种不同的内容以示区别。

编写界面的最佳实际

既然曾经学习了那么多 UI 开发的常识,是时候实战一下了。这次咱们要综合使用后面所学的大量内容来编写出一个较为简单且相当好看的聊天界面,首先先创立一个 UIBestPractice 我的项目。

制作 9-Patch 图片

在实战正式开始之前,咱们须要先学习一下如何制作 9-Patch 图片。你之前可能没有据说过这个名词,它是一种被非凡解决过的 png 图片,可能指定哪些区域能够被拉伸、哪些区域不能够。

那么 9-Patch 图片到底有什么理论作用呢?咱们还是通过一个例子来看一下吧。首先在 UIBestPractice 我的项目中搁置一张气泡款式的图片 message_left.png,如下图所示。

咱们将这张图片设置为 LinearLayout 的背景图片,批改 activity_main.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@drawable/message_left_original">
    
    
</LinearLayout>

这里将 LinearLayout 的宽度指定为 match_parent,将它的背景图设置为 message_left。当初运行程序,成果如下图所示:

能够看到,因为 message_left 的宽度不足以填满整个屏幕的宽度,整张图片被平均地拉伸了!这种成果十分差,用户必定是不能容忍的,这时就能够应用 9-Patch 图片来进行改善。

制作 9-Patch 图片其实并不简单,只有把握好规定就行了,那么当初咱们就来学习一下。

在 Android Studio 中,咱们能够将任何 png 类型的图片制作成 9-Patch 图片。首先对着 message_left.png 图片右击 →Create 9-Patch file,会创立一张以 9.png 为后缀的同名图片,选中这张图片。这时 Android Studio 会显示如下图所示的编辑界面:

咱们能够在图片的 4 个边框绘制一个个的小黑点,在上边框和左边框绘制的局部示意当图片须要拉伸时就拉伸黑点标记的区域,在下边框和左边框绘制的局部示意内容容许被搁置的区域。应用鼠标在图片的边缘拖动就能够进行绘制了,按住 Shift 键拖动能够进行擦除。绘制实现后成果如下图所示:

最初记得要将原来的 message_left.png 图片删除,只保留制作好的 message_left.9.png 图片即可,因为 Android 我的项目中不容许同一文件夹下有两张雷同名称的图片(即便后缀名不同也不行)。从新运行程序,成果如下图所示:

[外链图片转存失败, 源站可能有防盗链机制, 倡议将图片保留下来间接上传(img-ssAYjiNU-1620372326265)(https://p1-juejin.byteimg.com…]

这样当图片须要拉伸的时候,就能够只拉伸指定的区域,程序在外观上也有了很大的改良。有
了这个常识储备之后,咱们就能够进入实战环节了。

编写精美的聊天界面

既然是要编写一个聊天界面,那必定要有收到的音讯和收回的音讯。上一大节中咱们制作的 message_left.9.png 能够作为收到音讯的背景图,那么毫无疑问你还须要再制作一张 message_right.9.png 作为收回音讯的背景图。制作过程是齐全一样的,我就不再反复演示了。

图片都筹备好了之后,就能够开始编码了,接下来开始编写主界面,批改 activity_main.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#d8e0e8"
    android:orientation="vertical">

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

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

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

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

    </LinearLayout>

</LinearLayout>

咱们在主界面中搁置了一个 RecyclerView 用于显示聊天的音讯内容,又搁置了一个 EditText 用于输出音讯,还搁置了一个 Button 用于发送音讯。

而后定义音讯的实体类,新建 Msg,代码如下所示:

class Msg(val content: String, val type: Int) {
    
    companion object {
        const val TYPE_RECEIVED = 0
        const val TYPE_SENT = 1
    }
    
}

Msg 类中只有两个字段:content 示意音讯的内容,type 示意音讯的类型。其中音讯类型有两个值可选:TYPE_RECEIVED 示意这是一条收到的音讯,TYPE_SENT 示意这是一条收回的音讯。这里咱们将 TYPE_RECEIVED 和 TYPE_SENT 定义成了常量,定义常量的关键字是 const,留神只有在单例类、companion object 或顶层办法中才能够应用 const 关键字。

接下来开始编写 RecyclerView 的子项布局,新建 msg_left_item.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">

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

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

这是接管音讯的子项布局。这里咱们让收到的音讯居左对齐,并应用 message_left.9.png 作为背景图。

相似地,咱们还须要再编写一个发送音讯的子项布局,新建 msg_right_item.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">

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

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

这里咱们让收回的音讯居右对齐,并应用 message_right.9.png 作为背景图,基本上和方才的 msg_left_item.xml 是差不多的。

接下来须要创立 RecyclerView 的适配器类,新建类 MsgAdapter,代码如下所示:

class MsgAdapter(private val msgList: List<Msg>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {inner class LeftViewHolder(view: View) : RecyclerView.ViewHolder(view) {val leftMsg: TextView = view.leftMsg}

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

    override fun getItemViewType(position: Int): Int {val msg = msgList[position]
        return msg.type
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
            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 IllegalArgumentException()}
    }

    override fun getItemCount() = msgList.size}

上述代码中用到了一个新的知识点:依据不同的 viewType 创立不同的界面。首先咱们定义了 LeftViewHolder 和 RightViewHolder 这两个 ViewHolder,别离用于缓存 msg_left_item.xml 和 msg_right_item.xml 布局中的控件。而后要重写 getItemViewType() 办法,并在这个办法中返回以后 position 对应的音讯类型。

接下来的代码你应该就比拟相熟了,和咱们之前学习的 R ecyclerView 用法是比拟类似的,只是要在 onCreateViewHolder() 办法中依据不同的 viewType 来加载不同的布局并创立不同的 ViewHolder。而后在 onBindViewHolder() 办法中判断 ViewHolder 的类型:如果是 LeftViewHolder,就将内容显示到右边的音讯布局;如果是 RightViewHolder,就将内容显示到左边的音讯布局。

最初批改 MainActivity 中的代码,为 RecyclerView 初始化一些数据,并给发送按钮退出事件响应,代码如下所示:

class MainActivity : AppCompatActivity(), View.OnClickListener {private val msgList = ArrayList<Msg>()

    private var mAdapter: MsgAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initMsg()
        
        val layoutManager = LinearLayoutManager(this)
        recyclerView.layoutManager = layoutManager
        mAdapter = MsgAdapter(msgList)
        recyclerView.adapter = mAdapter
        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_SENT)
        msgList.add(msg2)
        val msg3 = Msg("This is Tom. Nice talking to you.", Msg.TYPE_RECEIVED)
        msgList.add(msg3)
    }

    override fun onClick(v: View?) {when (v) {
            send -> {val content = inputText.text.toString()
                if (content.isNotEmpty()) {val msg = Msg(content, Msg.TYPE_SENT)
                    msgList.add(msg)
                    // 当有新音讯时,刷新 RecyclerView 中的显示
                    mAdapter?.notifyItemInserted(msgList.size - 1)
                    // 将 RecyclerView 定位到最初一行
                    recyclerView.scrollToPosition(msgList.size - 1)
                    // 清空输入框中的内容
                    inputText.setText("")

                }
            }
        }
    }

}

咱们先在 initMsg() 办法中初始化了几条数据用于在 RecyclerView 中显示,接下来依照规范的形式构建 RecyclerView,给它指定一个 LayoutManager 和一个适配器。

而后在发送按钮的点击事件里获取了 EditText 中的内容,如果内容不为空字符串,则创立一个新的 Msg 对象并增加到 msgList 列表中去。之后又调用了适配器的 notifyItemInserted() 办法,用于告诉列表有新的数据插入,这样新增的一条音讯才可能在 RecyclerView 中显示进去。或者你也能够调用适配器的 notifyDataSetChanged() 办法,它会将 RecyclerView 中所有可见的元素全副刷新,这样不论是新增、删除、还是批改元素,界面上都会显示最新的数据,但毛病是效率会绝对差一些。接着调用 RecyclerView 的 scrollToPosition() 办法将显示的数据定位到最初一行,以保障肯定能够看失去最初收回的一条音讯。最初调用 EditText 的 setText() 办法将输出的内容清空。

这样所有的工作都实现了,终于能够测验一下咱们的成绩了。运行程序之后,你将会看到十分好看的聊天界面,并且能够输出和发送音讯,如下所示:

小结

本章从 Android 中的一些常见控件动手,顺次介绍了根本布局的用法、ListView 的具体用法以及 RecyclerView 的应用,根本曾经将重要的 UI 知识点全副笼罩了。

尽管本章的内容很多,但我感觉学习起来应该还是挺欢快的吧。不同于上一章中咱们来来回回应用那几个按钮,本章能够说是应用了各种各样的控件,制作出了丰富多彩的界面。

退出移动版