自定义表格控件界面
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:scrollbars="none"
android:id="@+id/sv_table">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:overScrollMode="never"
android:fadingEdge="none"
android:scrollbars="none"
android:id="@+id/hsv_table">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#99ccff"
android:descendantFocusability="beforeDescendants">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/rv_table_title"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:descendantFocusability="beforeDescendants">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/rv_table_content"/>
</RelativeLayout>
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</ScrollView>
</LinearLayout>
自定义表格控件实现
/**
* 自定义表格控件,HorizontalScrollView和RecyclerView组合
*/
class MyTableView<T> : LinearLayout{
lateinit var svTable : ScrollView
lateinit var hsvTable : HorizontalScrollView
/**表头RecyclerView */
lateinit var rvTableTitle: RecyclerView
/**表头适配器 */
var titleAdapter : MyRecyclerViewAdapter<TableTitleEntity>?= null
/**内容RecyclerView */
lateinit var rvTableContent : RecyclerView
/**内容适配器 */
var contentAdapter : MyRecyclerViewAdapter<T>?= null
/**表头数据集 */
var titleObj : ArrayList<TableTitleEntity> = ArrayList()
/**内容数据集 */
var contentObj : ArrayList<T> = ArrayList()
/**内边距 */
var padding : Int = 60
/**字体大小 */
var textSize : Int = 80
/**点击事件监听器 */
var mOntableClickListener : OnTableClickListerner<T> ?= null
/**加载更多事件监听器 */
var mOnLoadMoreListerner : OnLoadMoreListerner ?= null
/**是否允许自身上下滑动 不允许则根据功能界面整体滑动(用于仅仅展示,不做分页加载),允许则该表格控件自身滑动(用于有加载更多功能)*/
var isCanSrorll = true
/**被点击到的行 */
var isHorizontalScroll : Boolean = false
var lastScrollY : Int = 0
var lastScrollX : Int = 0
var lastLoadTime : Long = 0
constructor(context: Context) : super(context)
constructor(context: Context,attributeSet: AttributeSet) : super(context,attributeSet){
val view = LayoutInflater.from(context).inflate(R.layout.my_table_view,this)
initView(view)
}
/**初始化界面 */
@SuppressLint("ClickableViewAccessibility")
private fun initView(view: View) {
padding = MathUtils.formatPxToDip(context,padding)
textSize = MathUtils.formatPxToDip(context,textSize)
rvTableTitle = view.findViewById(R.id.rv_table_title)
rvTableContent = view.findViewById(R.id.rv_table_content)
hsvTable = view.findViewById(R.id.hsv_table)
svTable = view.findViewById(R.id.sv_table)
//根据设置 限制表格是否能够滑动
svTable.setOnTouchListener { v, event ->
!isCanSrorll
}
//监听滑动控件是否滑动到最底部
svTable.viewTreeObserver.addOnScrollChangedListener {
if (!svTable.canScrollVertically(1) && isCanSrorll){
//滑动到最底部
if (((lastScrollY != svTable.scrollY || lastScrollY == 0) && lastScrollX == hsvTable.scrollX)){
if (mOnLoadMoreListerner != null){
val currentTime = System.currentTimeMillis()
if (currentTime - lastLoadTime >= 1000) {
lastLoadTime = currentTime
mOnLoadMoreListerner!!.onLoadMore()
}
}
}
}
lastScrollX = hsvTable.scrollX
lastScrollY = svTable.scrollY
}
}
private fun setTitleAdapter(){
titleAdapter = object : MyRecyclerViewAdapter<TableTitleEntity>(context,titleObj,
R.layout.item_table_view,null,false,false,null){
override fun onBindData(holder: BaseViewHolder, position: Int, item: TableTitleEntity) {
val llTableItem = holder.itemView.findViewById<LinearLayout>(R.id.ll_table_item)
addTextView(llTableItem,item,null)
}
}
titleAdapter!!.setOnItemClickListerner(object : MyRecyclerViewAdapter.OnItemClickListerner{
override fun onItemClick(position: Int) {
if (mOntableClickListener != null){
mOntableClickListener!!.onTitleItemClick(position)
}
}
override fun onAddClick() {
}
})
rvTableTitle.layoutManager = LinearLayoutManager(context,LinearLayoutManager.HORIZONTAL,false)
rvTableTitle.adapter = titleAdapter
}
private fun setContentAdapter(){
contentAdapter = object : MyRecyclerViewAdapter<T>(context,contentObj,
R.layout.item_table_view,null,false,false,null){
override fun onBindData(holder: BaseViewHolder, position: Int, item: T) {
val llTableItem = holder.itemView.findViewById<LinearLayout>(R.id.ll_table_item)
titleObj.forEach {
addTextView(llTableItem,it,item)
}
}
}
contentAdapter!!.setOnItemClickListerner(object : MyRecyclerViewAdapter.OnItemClickListerner{
override fun onItemClick(position: Int) {
if (mOntableClickListener != null){
mOntableClickListener!!.onContentItemClick(contentObj[position],position)
}
}
override fun onAddClick() {
}
})
rvTableContent.layoutManager = object : LinearLayoutManager(context){
override fun canScrollVertically(): Boolean {
return false
}
}
rvTableContent.adapter = contentAdapter
}
/**设置文字大小 */
fun settextSize(size : Int){
textSize = MathUtils.formatPxToDip(context,size)
initCell()
}
/**设置内边距大小 */
fun setTablePadding(i : Int){
padding = MathUtils.formatPxToDip(context,i)
initCell()
}
/**设置表头标题 */
inline fun <reified T>setTableTitle(title: String,bindField: String): TableTitleEntity {
val t = TableTitleEntity()
t.Title = title
t.BindField = bindField
val d = T::class.java.getDeclaredField(bindField)
d.isAccessible = true
t.CellType = d.type.name
t.Field = d
return t
}
/**设置表头标题(需要根据枚举获取值) */
@SuppressLint("UseSparseArrays")
inline fun <reified T,reified T2 : Enum<T2>>setTableTitle(title: String, bindField: String, auto : Boolean = false): TableTitleEntity {
val t = TableTitleEntity()
try {
t.Title = title
t.BindField = bindField
val d = T::class.java.getDeclaredField(bindField)
d.isAccessible = true
t.CellType = d.type.name
t.Field = d
//将T2枚举反射回来弄成Map集合保存到表头数据中
val d2 = T2::class.java
val dd2 = d2.enumConstants
val hm = HashMap<Int,String>()
val getTitle = d2.getMethod("getTitle")
val getValue = d2.getMethod("getValue")
dd2?.forEach {
val vvv = getValue.invoke(it)?.toString()?.toInt()
val ttt = getTitle.invoke(it)?.toString()
if (vvv != null && ttt != null){
hm[vvv] = ttt
}
}
t._isAutoDraw = auto
t.Enum = hm
}catch (e:Exception){
LogFileUtils.OperationLog("[MyTableView]枚举转换异常:${e.cause?.message}")
}
return t
}
/**设置表数据 */
fun setTableData(t : ArrayList<TableTitleEntity>, d : ArrayList<T>){
titleObj.clear()
titleObj.addAll(t)
contentObj.clear()
contentObj.addAll(d)
initCell()
}
/**跟新表格数据 传入的List仅传更新部分的内容 */
fun setUpdataContent(d : ArrayList<T>){
contentObj.addAll(d)
initCell()
}
/**初始化单元格 */
private fun initCell() {
titleObj.forEach {
val maxWidth = getMaxWidth(it,contentObj)
it.CellWidth = maxWidth
}
setTitleAdapter()
setContentAdapter()
}
/**计算每一列最长的宽度 */
private fun getMaxWidth(t: TableTitleEntity, d: ArrayList<T>): Int {
var maxWidth = createTextView(t.Title).measuredWidth
d.forEach {
val res = getFieldValue(t,it)
val textView = createTextView(res)
val itemWidth = textView.measuredWidth
if (maxWidth <= itemWidth){
maxWidth = itemWidth
}
}
return maxWidth
}
/**创建TextView */
private fun createTextView(res: String): TextView {
val textView = TextView(context)
textView.textSize = textSize.toFloat()
textView.setPadding(padding,padding,padding,padding)
textView.text = res
textView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
textView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) //测量大小
return textView
}
/**通过反射获取某字段的值 */
private fun getFieldValue(t: TableTitleEntity, it: T): String {
return if (t.Field?.get(it) != null){
try {
if (t._isAutoDraw) {
when (t.CellType) {
"java.util.HashMap" -> {
(t.Field!!.get(it) as HashMap<*, *>).values.joinToString()
}
"java.util.Map" -> {
(t.Field!!.get(it) as Map<*, *>).values.joinToString()
}
"java.util.Date" -> {
myTime.DateFormat.format(t.Field!!.get(it))
}
"java.util.ArrayList" -> {
(t.Field!!.get(it) as ArrayList<*>).joinToString()
}
"java.util.Array" -> {
(t.Field!!.get(it) as Array<*>).joinToString()
}
else -> {
t.Field!!.get(it)!!.toString()
}
}
}else{
if (t.Enum != null){
t.Enum!![t.Field!!.get(it)!!.toString().toInt()] ?: t.Field!!.get(it)!!.toString()
}else{
""
}
}
}catch (e:Exception){
""
}
}else{
""
}
}
@SuppressLint("SetTextI18n")
private fun addTextView(view: LinearLayout, title : TableTitleEntity, content : T?){
//内容Text
val textView = TextView(context)
if (content == null){
textView.gravity = Gravity.CENTER
textView.text = title.Title
textView.background = ContextCompat.getDrawable(context,
R.drawable.my_table_title
)
}else{
textView.gravity = Gravity.LEFT
textView.text = getFieldValue(title,content)
textView.background = ContextCompat.getDrawable(context,
R.drawable.my_table_content
)
}
textView.textSize = textSize.toFloat()
textView.setPadding(padding,padding,padding,padding)
textView.layoutParams = ViewGroup.LayoutParams(title.CellWidth, ViewGroup.LayoutParams.WRAP_CONTENT)
// 将TextView添加到布局中
view.addView(textView)
}
/**表格是否可以上下滑动 */
fun isCanTableScroll(b : Boolean){
isCanSrorll = b
}
interface OnTableClickListerner<T>{
fun onTitleItemClick(position : Int)
fun onContentItemClick(data : T,position: Int)
}
interface OnLoadMoreListerner{
fun onLoadMore()
}
fun setOnTableClickListerner(onTableClickListerner: OnTableClickListerner<T>){
mOntableClickListener = onTableClickListerner
}
fun setOnLoadMore(onLoadMoreListerner: OnLoadMoreListerner){
mOnLoadMoreListerner = onLoadMoreListerner
}
}
class TableTitleEntity {
var _isAutoDraw = true
/**单元格内容类型 */
var CellType : String? = null
/**表头内容 */
var Title : String = ""
/**表头对其方式 17-居中 */
var TitleGravity : Int = 17
/**单元格宽度 */
var CellWidth : Int = 100
/**表格内容对其方式 3左对齐 */
var CellGravity : Int = 3
/** 字段名 */
var BindField : String? = null
var Field : Field ?= null
var Enum : HashMap<Int,String> ?= null
}
表头样式
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#777777"/>
</shape>
</item>
<item android:right="1dp"
android:bottom="1dp">
<shape>
<solid android:color="#99ccff"/>
</shape>
</item>
</layer-list>
单元格样式
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#777777"/>
</shape>
</item>
<item android:right="1dp"
android:bottom="1dp">
<shape>
<solid android:color="#ffffff"/>
</shape>
</item>
</layer-list>