假设需求,展示一堆水果数据数据。
一、Android 高级控件 RecyclerView
1.1先看效果运行图:
1.2我们看看布局代码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:orientation="vertical">
<ImageView
android:id="@+id/ivBreak"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="10dp"
android:layout_marginTop="40dp"
android:src="@mipmap/ic_break" />
<TextView
android:id="@+id/tvMarquee"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginTop="10dp"
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:text="喜爱水果虽好,不可以过量食用,不然引以腹泻相关等不良反应哦!"
android:textColor="@color/red"
android:textSize="18sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:overScrollMode="never" />
<!-- 滑动到底部,会有回弹效果,如果不需要,则不设置该条语句即可-->
<!-- android:overScrollMode="never"-->
</LinearLayout>
1.3 我们再来看看Activity的代码
class ListActivity : BaseActivity<ActivityListBinding>() {
private val dataList= listOf(
FruitData("苹果", R.mipmap.ic_apple,
"苹果是蔷薇科苹果亚科苹果属植物,其树为落叶乔木。" +
"苹果的果实富含矿物质和维生素,是经常食用的水果之一。苹果中营养成分可溶性大,易被人体吸收,故有“活水”之称,有利于溶解硫元素,使皮肤润滑柔嫩。" +
"苹果中含有铜、碘、锰、锌、钾等元素,具有降低胆固醇、宁神安眠、缓解疲劳等功效。"),
FruitData("香蕉", R.mipmap.ic_banana, "香蕉为芭蕉科植物甘蕉的果实,果肉香甜,除可生食外,还可制成多种加工品。" +
"香蕉营养高、热量低,具有降低血压、缓解抽筋、改善便秘等功效。"),
FruitData("梨", R.mipmap.ic_pear, "梨味美汁多、甜中带酸,而且营养丰富,含有多种维生素、纤维素等,既能生吃," +
"也可以煮水或煲汤后食用,梨具有清热镇静、化痰止咳、润肠通便的功效。"),
FruitData("葡萄", R.mipmap.ic_grape,"葡萄原产亚洲西部,现世界各地均有栽培,为常见水果,可生食或制葡萄干," +
"并酿酒,含有矿物质钙、钾、磷、铁以及多种维生素B1、维生素B2、维生素B6、维生素C和维生素P等,还含有多种人体所需的氨基酸,具有生津消食、缓解疲劳、补血益气的功效。"),
FruitData("西瓜", R.mipmap.ic_watermelon, "西瓜为葫芦科西瓜属植物西瓜的果瓤,味甘,性寒,归心、胃、膀胱经,具有清热利尿、解暑生津的功效。"),
FruitData("桃子", R.mipmap.ic_peach, "桃子果肉多汁,多为白色、黄色、橙黄色或红色,味道香甜。桃子具有利尿通便、保护血管、改善贫血等功效。"),
FruitData("李子", R.mipmap.ic_plum, "李子具有活血化瘀、美容养颜、促进消化、改善贫血、抗氧化等功效与作用。")
)
override fun initData(savedInstanceState: Bundle?) {
binding.tvMarquee.isSelected = true // 设置跑马灯效果
binding.ivBreak.setOnClickListener { finish() } //关闭当前界面
//设置adapter 并给相关点击事件设置回调
binding.rvList.adapter=FruitListAdapter(dataList).setClickCall { v, position, fruitData, event ->
when (event) {
0 -> "点击第${position}行中的${fruitData.fruitName}图片".showToast()
1 -> "点击第${position}行中的${fruitData.fruitName}名称".showToast()
2 -> "点击第${position}行中的${fruitData.fruitName}功效".showToast()
}
}
//设置线性布局管理器,锤子方向排列,reverseLayout 参数表示是否倒叙排序
//如果你想水平方向排序,只需要RecyclerView.VERTICAL换成RecyclerView.HORIZONTAL即可
binding.rvList.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
//布局还可以实现 网格和瀑布流展示
//网格GridLayoutManager :表示3列,即一行只有三个item ,每一个Item都是均分宽度
// binding.rvList.layoutManager=GridLayoutManager(this,3)
//瀑布流StaggeredGridLayoutManager表示3列,即一行只有三个item ,高度在自适应每一个Item,宽度同样均分
// binding.rvList.layoutManager=StaggeredGridLayoutManager(3,RecyclerView.VERTICAL)
}
}
FruitData是我们写的一个data类内容如下:
//三个参数的名称意义分别是: 水果的名称,水果的资源图片id,水果对应的功效好处
data class FruitData(val fruitName: String, val imageId: Int, val fruitEfficacy: String)
FruitListAdapter则是我们写的一个adapter,将他设置到RecyclerView的adapter,我们要的效果就出现了。接下来我们看看FruitListAdapter的具体代码:
class FruitListAdapter(dataList: List<FruitData>) :
BaseAdapter<FruitData, ItemListBinding>(dataList) {
override fun setItemDataView(binding: ItemListBinding, data: FruitData, position: Int) {
// binding.ivFruitImage.setImageResource(data.imageId)
GlideUtils.glideLoad(binding.ivFruitImage,data.imageId,10) //使用Glide加载图片,并设置圆角,Glide在讲第四章ImageView时候使用过的哦,
binding.tvFruitName.text = "水果名称:" + data.fruitName
binding.tvEfficacy.text = "水果功效:" + data.fruitEfficacy
//同一行数据,不同的点击事件
binding.ivFruitImage.setOnClickListener {
clickItemCall?.invoke(it, position, data, 0)
}
binding.tvFruitName.setOnClickListener {
clickItemCall?.invoke(it, position, data, 1)
}
binding.tvEfficacy.setOnClickListener {
clickItemCall?.invoke(it, position, data, 2)
}
}
}
FruitListAdapter继承了BaseAdapter,其中有两个泛型,一个是数据类型,一个是Item的布局,我们在看看Item的布局代码。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/ivFruitImage"
android:layout_width="120dp"
android:layout_height="120dp"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/tvFruitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="水果名称"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/ivFruitImage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@+id/ivFruitImage"
app:layout_constraintTop_toTopOf="@+id/ivFruitImage"
app:layout_constraintVertical_bias="0" />
<TextView
android:id="@+id/tvEfficacy"
android:layout_width="0dp"
android:layout_height="0dp"
android:ellipsize="end"
android:maxLines="4"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:text="功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效功效"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/ivFruitImage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="@+id/tvFruitName"
app:layout_constraintTop_toBottomOf="@+id/tvFruitName"
app:layout_constraintVertical_bias="0" />
</androidx.constraintlayout.widget.ConstraintLayout>
布局很简单,相信小伙伴们都看的懂。
接下来,我们看看BaseAdapter里都写了什么代码。
//使用泛型,D代表的是你传递进来集合中的数据类型,比如Int,String等等,V则是你每一行的布局展示
abstract class BaseAdapter<D, V : ViewBinding>(private val dataList: List<D>) : RecyclerView.Adapter<MyViewHolder<V>>() {
init { // ObservableArrayList 是用于监听,数据List变化的,他继承与ArrayList。
// 如果你传递的List是 ObservableArrayList的时候,我们可以在这里监听数据的变化, 自动刷新数据ui展示。
if (dataList is ObservableArrayList<D>) dataList.addOnListChangedCallback(object : ObservableList.OnListChangedCallback<ObservableList<D>>() {
override fun onChanged(contributorViewModels: ObservableList<D>) {
notifyDataSetChanged()
}
override fun onItemRangeChanged(contributorViewModels: ObservableList<D>, i: Int, i1: Int) {
notifyItemRangeChanged(i, i1)
}
override fun onItemRangeInserted(contributorViewModels: ObservableList<D>, i: Int, i1: Int) {
notifyItemRangeInserted(i, i1)
}
override fun onItemRangeMoved(contributorViewModels: ObservableList<D>, i: Int, i1: Int, i2: Int) {
notifyItemMoved(i, i1)
}
override fun onItemRangeRemoved(contributorViewModels: ObservableList<D>, i: Int, i1: Int) {
notifyDataSetChanged()
}
})
}
//点击事件的回调
var clickItemCall: ((v: View, position: Int, D,event : Int) -> Unit)? = null
//设置点击事件的回调
//参数意义:返回点击二点View ,返回点击的是多少行,返回点击所在行的数据,如果一行有多个点击事件可以使用event来区分
fun setClickCall(clickCall: ((v: View, position: Int, D,event : Int) -> Unit)): BaseAdapter<D, V> {
this.clickItemCall = clickCall
return this
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder<V> {
val type = this.javaClass.genericSuperclass //使用反射拿到布局,设置到 MyViewHolder当中
val inflater = LayoutInflater.from(parent.context)
val tClass = (type as ParameterizedType).actualTypeArguments[1] as Class<V>
val method = tClass.getMethod("inflate", LayoutInflater::class.java)
val binding = method.invoke(null, inflater) as V
try {
binding.root.layoutParams = parent.layoutParams
} catch (e: Exception) {
e.printStackTrace()
}
return MyViewHolder(binding)
}
override fun getItemCount(): Int { //返回有多少行数据
return dataList.size
}
override fun onBindViewHolder(holder: MyViewHolder<V>, position: Int) {
//这里是数据展示界面的ui
setItemDataView(holder.viewBinding, dataList[position], position)
}
abstract fun setItemDataView(binding: V, data: D, position: Int)
}
代码里面都写了注释,不算太难,在看看MyViewHolder代码
class MyViewHolder<V : ViewBinding>(val viewBinding: V) :
RecyclerView.ViewHolder(viewBinding.root) {
}
代码就两行。
以上RecyclerView基本使用就差不多了,当然还有进阶使用,比如,拖动item与item之间进行数据交互,比如微信发朋友选图排版一样可以拖动更换位置,这个在后面使用到,我们再讲解。
二、看看Compose 中的使用相对RecyclerView在代码上则要简单点,先看看布局:
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FruitListLayout(dataList: List<FruitData>,breakCall : ((Int)->Unit)){
Column(
Modifier
.fillMaxSize()
.background(Color.White)) {
Image(painter = painterResource(id = R.mipmap.ic_break),//设置资源
contentDescription = null,
Modifier.padding(top = 40.dp, start = 10.dp, end = 10.dp)
.clickable { breakCall.invoke(0) } //点击事件
.size(35.dp) //设置大小
.clip(RoundedCornerShape(10.dp)), //设置角
contentScale= ContentScale.FillHeight) //拉满整个高度
// basicMarquee 方法是实现跑马灯效果
Text(
text = "喜爱水果虽好,不可以过量食用,不然引以腹泻相关等不良反应哦!",
modifier = Modifier.basicMarquee(iterations = Int.MAX_VALUE)
.padding(start = 10.dp, end = 10.dp),
color = Color.Red,
style = MaterialTheme.typography.body1.copy(fontSize = 16.sp))
//垂直排列
LazyColumn(content = {
items(dataList){
ItemFruitListLayout(data = it)
}
})
// //水平排列
// LazyRow(content = {
// items(dataList){
// ItemFruitListLayout(data = it)
// }
// })
//实现网格布局
// LazyVerticalGrid(columns = GridCells.Fixed(3), content = {
// items(dataList.size) {
// ItemFruitListLayout(dataList[it])
// }
// })
}
}
@Composable
fun ItemFruitListLayout(data : FruitData){
Row(
Modifier.padding(top = 10.dp, start = 5.dp, bottom = 10.dp)
.fillMaxWidth()) {
Image(painter = painterResource(id = data.imageId),//设置资源
contentDescription = null,
Modifier.clickable {
"点击了${data.fruitName}的图片".showToast()
}
.size(120.dp) //设置大小
.clip(RoundedCornerShape(10.dp)), //设置角
contentScale= ContentScale.FillHeight) //拉满整个高度
Text(text = data.fruitName+"\n"+data.fruitEfficacy,
modifier = Modifier.clickable {
"点击了${data.fruitName}的文字".showToast()
}
.padding(start = 10.dp, end = 5.dp)
.weight(1f)
.height(120.dp), overflow = TextOverflow.Ellipsis)
}
}
接下来,我们在Activity里去加载这个这个布局
class FruitListActivity : BaseActivity() {
//准备数据源
private val dataList= listOf(
FruitData("苹果", R.mipmap.ic_apple,
"苹果是蔷薇科苹果亚科苹果属植物,其树为落叶乔木。" +
"苹果的果实富含矿物质和维生素,是经常食用的水果之一。苹果中营养成分可溶性大,易被人体吸收,故有“活水”之称,有利于溶解硫元素,使皮肤润滑柔嫩。" +
"苹果中含有铜、碘、锰、锌、钾等元素,具有降低胆固醇、宁神安眠、缓解疲劳等功效。"),
FruitData("香蕉", R.mipmap.ic_banana, "香蕉为芭蕉科植物甘蕉的果实,果肉香甜,除可生食外,还可制成多种加工品。" +
"香蕉营养高、热量低,具有降低血压、缓解抽筋、改善便秘等功效。"),
FruitData("梨", R.mipmap.ic_pear, "梨味美汁多、甜中带酸,而且营养丰富,含有多种维生素、纤维素等,既能生吃," +
"也可以煮水或煲汤后食用,梨具有清热镇静、化痰止咳、润肠通便的功效。"),
FruitData("葡萄", R.mipmap.ic_grape,"葡萄原产亚洲西部,现世界各地均有栽培,为常见水果,可生食或制葡萄干," +
"并酿酒,含有矿物质钙、钾、磷、铁以及多种维生素B1、维生素B2、维生素B6、维生素C和维生素P等,还含有多种人体所需的氨基酸,具有生津消食、缓解疲劳、补血益气的功效。"),
FruitData("西瓜", R.mipmap.ic_watermelon, "西瓜为葫芦科西瓜属植物西瓜的果瓤,味甘,性寒,归心、胃、膀胱经,具有清热利尿、解暑生津的功效。"),
FruitData("桃子", R.mipmap.ic_peach, "桃子果肉多汁,多为白色、黄色、橙黄色或红色,味道香甜。桃子具有利尿通便、保护血管、改善贫血等功效。"),
FruitData("李子", R.mipmap.ic_plum, "李子具有活血化瘀、美容养颜、促进消化、改善贫血、抗氧化等功效与作用。")
)
@Composable
@Preview
override fun InitView() {
FruitListLayout(dataList){
finish() //关闭界面
}
}
override fun initData() {
}
}
整体上,代码就上少了一点。使用起来也方便的。
三、鸿蒙开发之List
参见: