本节内容对应《第一行代码 第三版》:第四章
文章目录
- 一、AlertDialog
- PS:隐藏标题栏
- 二、ListView
- 1. 简单显示一串文本,需要两步:
- 2. 定制ListView的界面
- 3.优化
- 4.实现点击事件
- 三、RecycleView的用法
- 1、在gradle中引入依赖库
- 2、在布局文件中添加相应的recycleView组件
- 3、自建继承自RecyclerView.Adapter的布局适配器
- 4、在MainActivity中载入布局
- - 疑难点:ViewHolder是什么?
- 5、其他布局模式
- 6、添加点击事件
- 四、编写一个对话程序
- 1、9-Patch图片
- 2、编写相应的主界面布局和两个子界面布局
- 3、建立消息的实体类,方便管理每个消息对象
- 4、创建自己的MsgAdapter
- 5、载入布局
- 6、再谈RecycleView复用机制:onCreateView和onBindView调用关系
- 五、Kotlin课堂——延迟初始化
详解参见:Android之AlertDialog的基本使用
AlertDialog.Builder(this).apply {
setTitle("This is a dialog")
setMessage("something important")
setCancelable(false)//点击对话框以外的区域是否让对话框消失,默认为true
setPositiveButton("OK"){
dialog, which -> {
}
}
setNegativeButton("Cancel"){
dialog, which ->
}
}.show()
supportActionBar?.hide()
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listView"
/>
val data = listOf<String>("apple","banana","a","d","dd","ad","dfasf","df","apple","banana","a","d","dd","ad","dfasf","df","apple","banana","a","d","dd","ad","dfasf","df")
//参数:Activity实例,ListView子项布局id,数据源
val adapter = ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data)//这里使用了一个默认的子项布局R.layout.simple_list_item_1
listView.adapter = adapter//设置适配器
R.layout.simple_list_item_1
,而是自己新建一个layout文件作为子项布局,从而做到自定义,这里使用fruit_item
。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/fruitImage"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/fruitName"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"/>
LinearLayout>
ArrayAdapter
,而是自己新建一个类FruitAdapter
,继承ArrayAdapter
,并在getView
函数中完成数据和子项布局的绑定//接收ArrayAdapter的三个参数:Activity实例,ListView子项布局id,数据源
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)//暂时理解为标准写法
//在ListView的适配器里无法使用import kotlinx.android.synthetic.main.activity_main.*插件,所以只能先获取控件
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
}
}
另外,为了方便,新建了一个类Fruit
,用于存放每份的数据资源
class Fruit (val name:String, val imageId:Int) {
}
FruitAdapter
和fruit_item
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))
}
}
由于在每个子项被滚动到屏幕内的时候都会调用getView
这个方法,这会导致
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)
反复加载,使得效率降低。
所以用getView()
自带的一个参数convertView: View?
缓存界面,并使用内部类viewHolder
暂时存储控件的实例,并把viewHolder
存储在view.tag
里
//接收ArrayAdapter的三个参数:Activity实例,ListView子项布局id,数据源
class FruitAdapter(activity: Activity, 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)//暂时理解为标准写法
//在ListView的适配器里无法使用import kotlinx.android.synthetic.main.activity_main.*插件,所以只能先获取控件
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
}
}
listView.setOnItemClickListener {
parent, view, position, id ->
val fruit = fruitList[position]
Toast.makeText(this,fruit.name,Toast.LENGTH_SHORT).show()
}
implementation 'androidx.recyclerview:recyclerview:1.1.0'
/*RecycleView适配器的标准写法*/
//RecyclerView.Adapter为了优化性能,自带一个FruitAdapter.ViewHolder的泛型,用于缓存界面数据
//参数fruitList: List是数据源
class FruitAdapter(val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() {
//这里使用内部类的方式创建ViewHolder,并完成界面组件绑定
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view){
val fruitImage:ImageView = view.findViewById(R.id.fruitImage)
val fruitName:TextView = view.findViewById(R.id.fruitName)
}
/*以下三个函数是自动重写*/
//这里创建ViewHolder的实例,并加载布局,最后将ViewHolder的实例返回
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)
return ViewHolder(view)
}
//用于告诉recycleView一共有多少子项,返回数据源的长度
override fun getItemCount(): Int {
return fruitList.size
}
//在每个子项被滚动到屏幕的时候执行,用于对recycleView的子项布局中的组件进行赋值
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val fruit = fruitList[position]
holder.fruitImage.setImageResource(fruit.imageId)
holder.fruitName.text = fruit.name
}
}
class MainActivity : AppCompatActivity() {
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initFruits()
//设置布局方式,LinearLayoutManager是线性布局
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))
}
}
}
viewHolder是recyclerView缓存管理的对象,每一个item项目都有一个对应的viewHolder用来保存界面组件。
在item首次被滑动进界面时,每一个viewHolder将会在函数onCreateViewHolder中进行初始化,并留下相应的缓存(这个缓存保存在ArrayList mCachedViews里),并同时进行viewHolder和数据的绑定,即调用onCreateViewHolder和onBindViewHolder各一次。
当item被划出界面时,程序会自动回收那些已经看不见的item,但是他们的viewHolder缓存仍然被保存着,当这个item再次被划入时,就不需要对该item的界面组件进行初始化,即不需要调用onCreateViewHolder,只需要调用onBindViewHolder绑定数据即可。
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
recy_view.setLayoutManager(new GridLayoutManager(this,4));
recy_view.setLayoutManager(new GridLayoutManager(this, 4, LinearLayoutManager.HORIZONTAL, false));
val layoutManager = StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL)
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{
//在这里设置item的点击事件
}
viewHolder.fruitImage.setOnClickListener {
//在这里设置fruitImage的点击事件
}
viewHolder.fruitName.setOnClickListener {
//在这里设置fruitName的点击事件
}
return viewHolder
}
与普通图片不同,使用9-Patch图片作为屏幕或按钮的背景时,当屏幕的尺寸或按钮的大小改变时,图片可自动缩放,达到不失真的效果。
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#d8e0e8">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/recyclerView"
android:layout_weight="1"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type something here"
android:maxLines="2"
android:id="@+id/inputText"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send"
android:id="@+id/send"/>
LinearLayout>
LinearLayout>
msg_left_item.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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"/>
LinearLayout>
FrameLayout>
msg_right_item.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:background="@drawable/message_right_original">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#000"
android:id="@+id/rightMsg"/>
LinearLayout>
FrameLayout>
//消息的实体类
class Msg(val content:String, val type:Int) {
//先前讲过的伪静态方法
companion object{
//定义常量,只有在companion object或顶层方法中才能使用const
const val TYPE_RECEIVED = 0
const val TYPE_SEND = 1
}
}
class MsgAdapter(val msgList: List<Msg>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
//创建了两个不同的ViewHolder
inner class LeftViewHolder(view: View): RecyclerView.ViewHolder(view){
val leftMsg = view.findViewById<TextView>(R.id.leftMsg)
}
inner class RightViewHolder(view: View): RecyclerView.ViewHolder(view){
val rightMsg = view.findViewById<TextView>(R.id.rightMsg)
}
//不重写的话,会默认返回0,现在重写,让它返回我们自己定义的类型(整形变量),以便于在onCreateViewHolder获取对于的viewType,绑定对应的ViewHolder
/*源码:
public int getItemViewType(int position) {
return 0;
}
* */
override fun getItemViewType(position: Int): Int {
val msg = msgList[position]
return msg.type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
Log.d("ceshi", "调用一次onCreateViewHolder")
//返回对应的viewHolder
if (viewType == Msg.TYPE_RECEIVED){
val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item,parent,false)
return LeftViewHolder(view)
}else{
val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item,parent,false)
return RightViewHolder(view)
}
}
override fun getItemCount(): Int {
return msgList.size
}
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
}
Log.d("ceshi", "调用一次onBindViewHolder")
}
}
class MainActivity : AppCompatActivity() {
private val msgList:ArrayList<Msg> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initMsgList()
val adapter: MsgAdapter = MsgAdapter(msgList)
recyclerView.adapter = adapter
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
send.setOnClickListener {
val content = inputText.text.toString()
if (content.isNotEmpty()){
val msg = Msg(inputText.text.toString(),Msg.TYPE_SEND)
msgList.add(msg)
//通知列表有新的数据插入,从而刷新新的数据
//同类型函数:adapter.notifyDataSetChanged()会刷新所有的数据
adapter.notifyItemInserted(msgList.size - 1)
//将显示的数据定位的最后一行
recyclerView.scrollToPosition(msgList.size - 1)
inputText.setText("")
}
}
}
fun initMsgList(){
repeat(8){
var msg = Msg("Hello guy", Msg.TYPE_RECEIVED)
msgList.add(msg)
msg = Msg("Hello. Who is that?", Msg.TYPE_SEND)
msgList.add(msg)
msg = Msg("This is Tom", Msg.TYPE_RECEIVED)
msgList.add(msg)
}
}
}
如果我们明确知道一个变量一定会在其他位置进行初始化,而不像定义如下的可空变量时:
private var ad: MsgAdapter? = null
我们可以这样来进行延迟初始化:
private lateinit var ad: MsgAdapter