前言:现在大部分app都有分类的这一功能,分类的需求一般都是左边是标题右边是内容,点击左边的标题会跳到相应的内容,滑动右边的内容会对应相应的标题,我们称为左右联动,早期的时候实现是两个listview,后期的时候则是RecycleView代替了ListView,今天我们用RecyclerView+StickyListHeadersListView更好的实现这一个需求。(注意:代码使用kotlin语言)
我们先看一下我们要实现的预览图
1.RecycleView介绍:是Android一个更强大的控件,其不仅可以实现和ListView同样的效果,还有优化了ListView中的各种不足。使用方法可以参考我的RecycleView系列博客(以下都是java代码开发的):
1)《Android开发之RecyclerView的基本使用(实现常用的4种效果)》
2)《Android开发之RecyclerView实现点击事件和长按事件》
3)《Android开发之RecyclerView的间隔线处理》
4)《Android开发之RecyclerView添加头部和底部》
5)《Android开发之实现滑动RecyclerView,浮动按钮的显示和隐藏(一)》
6)《Android开发之实现滑动RecyclerView,浮动按钮的显示和隐藏(二)》
7)《Android开发之RecyclerView的交互动画(实现拖拽和删除)》
2.StickyListHeadersListView介绍:顾名思义,就是粘列表标题,如下图所示
实现思路:整体分左边的RecycleView和右边的StickyListHeadersListView,通过点击左边item所在的position获得此typeId,通过遍历得到右边商品第一个位置的position,然后调用StickyListHeadersListView中setSelection()方法,就能联动右边的商品。滑动右边的商品,对StickyListHeadersListView的滑动监听,通过对比记录的左侧选中的position的位置和当前的position的对比,如果不一致,需要切换类别。下面开始代码实现。
1.包的结构图和json数据的结构图
注意json数据放在res下的raw包中了,用InputStream按行读取txt文件即可。
2.需要添加的依赖
//stickylistheaders
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
//design
implementation 'com.android.support:design:28.0.0'
//gson
implementation 'com.google.code.gson:gson:2.2.4'
3.通过json数据建立对应的bean对象
GoodsTypeInfo.kt
class GoodsTypeInfo {
var id: Int = 0//商品类型id
var name: String = ""//商品类型名称
var info: String = ""//特价信息
var list: List = listOf()//商品列表
}
GoodsInfo.kt
class GoodsInfo {
var id: Int = 0//商品id
var name: String = ""//商品名称
var icon: String = ""//商品图片
var form: String = ""//组成
var monthSaleNum: Int = 0//月销售量
var isBargainPrice: Boolean = false//特价
var isNew: Boolean = false//是否是新产品
var newPrice: String = ""//新价
var oldPrice: Int = 0//原价
var sellerId: Int = 0
//此商品属于那一个类别id以及类别名称
var typeId: Int = 0
var typeName: String = ""
var count: Int =0
}
4.需要两个Adapter,分别对应左边类别的adapter和右边商品的adapter
GoodsTypeRvAdapter.kt
import android.graphics.Color
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.fly.recyclerviewdemo.ui.MainActivity
import com.fly.recyclerviewdemo.R
import com.fly.recyclerviewdemo.beans.GoodsTypeInfo
class GoodsTypeRvAdapter(val mainActivity: MainActivity, val goodsTypeList: List) : RecyclerView.Adapter() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val goodsTypeItemHolder = holder as GoodsTypeItemHolder
goodsTypeItemHolder.bindData(goodsTypeList.get(position), position)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val itemView = LayoutInflater.from(mainActivity).inflate(R.layout.item_type, parent, false)
return GoodsTypeItemHolder(itemView)
}
override fun getItemCount(): Int {
return goodsTypeList.size
}
var selectPosition = 0 //选中的位置
inner class GoodsTypeItemHolder(val item: View) : RecyclerView.ViewHolder(item) {
val tvType: TextView
var mPosition: Int = 0
lateinit var goodsTypeInfo: GoodsTypeInfo
init {
tvType = item.findViewById(R.id.type)
item.setOnClickListener {
selectPosition = mPosition
notifyDataSetChanged()
//step2:右侧列表跳转到该类型中第一个商品
val typeId = goodsTypeInfo.id
//遍历所有商品,找到此position
val position = mainActivity.getGoodsPositionByTypeId(typeId)
mainActivity.slhlv.setSelection(position)
}
}
fun bindData(goodsTypeInfo: GoodsTypeInfo, position: Int) {
mPosition = position
this.goodsTypeInfo = goodsTypeInfo
if (position == selectPosition) {
//选中的为白底加粗黑字,
item.setBackgroundColor(Color.WHITE)
tvType.setTextColor(Color.BLACK)
tvType.setTypeface(Typeface.DEFAULT_BOLD)
} else {
//未选中是灰色背景 普通字体
item.setBackgroundColor(Color.parseColor("#b9dedcdc"))
tvType.setTextColor(Color.GRAY)
tvType.setTypeface(Typeface.DEFAULT)
}
tvType.text = goodsTypeInfo.name
}
}
}
对应的 item_type.xml布局文件
右边的adapter:
GoodsAdapter.kt
import android.graphics.Color
import android.graphics.Paint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
import com.fly.recyclerviewdemo.ui.MainActivity
import com.fly.recyclerviewdemo.R
import com.fly.recyclerviewdemo.beans.GoodsInfo
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter
class GoodsAdapter(val mainActivity: MainActivity, val goodsList: List) : BaseAdapter(), StickyListHeadersAdapter {
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var itemView: View
val goodsItemHolder: GoodsItemHolder
if (convertView == null) {
itemView = LayoutInflater.from(mainActivity).inflate(R.layout.item_goods, parent, false)
goodsItemHolder = GoodsItemHolder(itemView)
itemView.tag = goodsItemHolder
} else {
itemView = convertView
goodsItemHolder = convertView.tag as GoodsItemHolder
}
goodsItemHolder.bindData(goodsList.get(position))
return itemView
}
override fun getItem(position: Int): Any {
return goodsList.get(position)
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getCount(): Int {
return goodsList.size
}
override fun getHeaderId(position: Int): Long {
val goodsInfo: GoodsInfo = goodsList.get(position)
return goodsInfo.typeId.toLong()
}
override fun getHeaderView(position: Int, convertView: View?, parent: ViewGroup?): View {
val goodsInfo: GoodsInfo = goodsList.get(position)
val typeName = goodsInfo.typeName
val textView: TextView = LayoutInflater.from(mainActivity).inflate(R.layout.item_type_header, parent, false) as TextView
textView.text = typeName
textView.setTextColor(Color.BLACK)
return textView
}
inner class GoodsItemHolder(itemView: View) {
val tvName: TextView
val tvForm: TextView
val tvMonthSale: TextView
val tvNewPrice: TextView
val tvOldPrice: TextView
val tvCount: TextView
init {
tvName = itemView.findViewById(R.id.tv_name)
tvForm = itemView.findViewById(R.id.tv_form)
tvMonthSale = itemView.findViewById(R.id.tv_month_sale)
tvNewPrice = itemView.findViewById(R.id.tv_newprice)
tvOldPrice = itemView.findViewById(R.id.tv_oldprice)
tvCount = itemView.findViewById(R.id.tv_count)
}
fun bindData(goodsInfo: GoodsInfo) {
tvName.text = goodsInfo.name
tvForm.text = goodsInfo.form
tvMonthSale.text = "月售${goodsInfo.monthSaleNum}份"
tvNewPrice.text = goodsInfo.newPrice
tvOldPrice.text = "¥${goodsInfo.oldPrice}"
tvOldPrice.paint.flags = Paint.STRIKE_THRU_TEXT_FLAG
tvCount.text = goodsInfo.count.toString()
}
}
}
item_goods.xml
item_type_header.xml
5.在activity中进行这两个控件的使用。
MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.AbsListView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.fly.recyclerviewdemo.utils.DataUtil
import com.fly.recyclerviewdemo.R
import com.fly.recyclerviewdemo.adapter.GoodsAdapter
import com.fly.recyclerviewdemo.adapter.GoodsTypeRvAdapter
import com.fly.recyclerviewdemo.beans.GoodsInfo
import com.fly.recyclerviewdemo.beans.GoodsTypeInfo
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.json.JSONObject
import se.emilsjolander.stickylistheaders.StickyListHeadersListView
class MainActivity : AppCompatActivity() {
val allTypeGoodsList : ArrayList = arrayListOf()
var goodstypeList: List = arrayListOf()
lateinit var rvGoodsType: RecyclerView
lateinit var slhlv: StickyListHeadersListView
lateinit var goodsAdapter: GoodsAdapter
lateinit var goodsTypeAdapter : GoodsTypeRvAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getDatas()
rvGoodsType = findViewById(R.id.rv_goods_type)
slhlv = findViewById(R.id.slhlv)
goodsAdapter = GoodsAdapter(this, allTypeGoodsList)
slhlv.adapter = goodsAdapter
rvGoodsType.layoutManager = LinearLayoutManager(this!!)
goodsTypeAdapter = GoodsTypeRvAdapter(this, goodstypeList)
rvGoodsType.adapter = goodsTypeAdapter
slhlv.setOnScrollListener(object : AbsListView.OnScrollListener{
override fun onScroll(view: AbsListView?, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
//先找出旧的类别
val oldPosition = goodsTypeAdapter.selectPosition
val newTypeId = allTypeGoodsList.get(firstVisibleItem).typeId
//把新的id找到它对应的position
val newPositon = getTypePositionByTypeId(newTypeId)
//当newPositon与旧的不同时,证明需要切换类别了
if(newPositon!=oldPosition){
goodsTypeAdapter.selectPosition = newPositon
goodsTypeAdapter.notifyDataSetChanged()
}
}
override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {
}
})
}
private fun getDatas() {
var json : String= DataUtil.readFromRaw(
this,
R.raw.goodsdata
);
val gson = Gson()
val jsoObj = JSONObject(json)
val allStr = jsoObj.getString("list")
goodstypeList = gson.fromJson(allStr, object : TypeToken>() {}.type)
Log.e("business", "该商家一共有" + goodstypeList.size + "个类别商品")
for( i in 0 until goodstypeList.size){
val goodsTypeInfo = goodstypeList.get(i)
val aTypeList:List = goodsTypeInfo.list
for(j in 0 until aTypeList.size){
val goodsInfo = aTypeList.get(j)
//建立双向绑定关系
goodsInfo.typeName = goodsTypeInfo.name
goodsInfo.typeId = goodsTypeInfo.id
}
allTypeGoodsList.addAll(aTypeList)
}
}
//根据类别id找到其在左侧列表中的position
fun getTypePositionByTypeId(newTypeId: Int):Int {
var position = -1 //-1表示未找到
for(i in 0 until goodstypeList.size){
val goodsTypeInfo = goodstypeList.get(i)
if(goodsTypeInfo.id == newTypeId){
position = i
break;
}
}
return position
}
//根据商品类别id找到此类别第一个商品的位置
fun getGoodsPositionByTypeId(typeId: Int): Int {
var position = -1 //-1表示未找到
for(j in 0 until allTypeGoodsList.size){
val goodsInfo = allTypeGoodsList.get(j)
if(goodsInfo.typeId == typeId){
position = j
break;
}
}
return position
}
}
对应的布局代码
activity_main.xml
6.读取raw下的txt文件代码
DataUtil.java
import android.content.Context;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* Created by Fly on 2019/1/10/010.
*/
public class DataUtil {
/**
* 从raw中按行读取txt
*/
public static String readFromRaw(Context context,int id) {
try {
InputStream is = context.getResources().openRawResource(id);
InputStreamReader reader = new InputStreamReader(is);
BufferedReader bufferedReader = new BufferedReader(reader);
StringBuffer buffer = new StringBuffer("");
String str;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
buffer.append("\n");
}
return buffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "没有读取到";
}
}
7.json数据
点击下载goodsdata.json
8.运行效果见上图,没截成动图见谅。
最后说句话,大家加油!