栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

简单实现一个关系图View

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

简单实现一个关系图View

前言

 

前两天,leader拿了一张图过来,竟然问我能不能在app里做出来...这不是废话么,时间给够就没有画不出来的...

分析一下,这就是一张关系图,目前看是三个列表中间有连接线进行关联,每列数据样式都不一样。

OK,这么简单的View,还是要设计一下

设计

首先,这个关系图顶点数量不固定,列数多半也不是固定的,所以定义适配器 Adapter 去把数据转换成 View

其次,将 View 正确排列到关系图中,最简单暴力的方式就是在 addView 的时候设置 LayoutParams 等参数;其次就是在 onMeasure & onLayout 方法中进行布局,这样即使之后要增加选中动画等复杂操作也比较容易;最优美的方式就是学习 RecyclerView 一样,通过 LayoutManager 进行处理,这样符合了单一原则,之后拓展样式时也不需要修改老代码。

最后,对于连接线的绘制,能看出线是链接两个顶点 View 左右两边的中点,并且都是一个颜色没有区别的。所以简单暴力的方法就是在 ViewGroup 的 draw 方法中直接根据左右两列中需要链接的 View 的坐标绘制一条线;复杂的方法就是定义 baseLine 将线条的绘制进行封装,方便以后拓展。

OK,夏姬八想了够久了,先按照最简单的方式写个 Demo 给组长交差吧

Demo 效果图

 

代码

view

package com.xxx

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import com.xxx.R


class FloorRelationMapView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
    ConstraintLayout(context, attrs, defStyleAttr) {

    
    var horizontalSpace: Int = 0

    
    var verticalSpace: Int = 0

    var lineWidth: Float = 0F

    var lineColor: Int = Color.GRAY

    var adapter: baseFloorRelationAdapter? = null
        set(value) {
            field = value
            value?.dataSetChangedListener =
                object : baseFloorRelationAdapter.IonDataSetChangedListener {
                    override fun dataSetChanged(floor: Int, position: Int) {
                        updateViews(floor, position)
                    }
                }
            updateViews()
        }
    
    
    private val pointViews: ArrayList> = ArrayList()

    constructor(context: Context) : this(context, null, 0)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    init {
        setWillNotDraw(false)

        // 获取自定义参数
        val array = getContext().obtainStyledAttributes(attrs, R.styleable.FloorRelationMapView)
        if (array.hasValue(R.styleable.FloorRelationMapView_verticalSpace)) {
            verticalSpace =
                array.getDimension(R.styleable.FloorRelationMapView_verticalSpace, 0F).toInt()
        }
        if (array.hasValue(R.styleable.FloorRelationMapView_horizontalSpace)) {
            horizontalSpace =
                array.getDimension(R.styleable.FloorRelationMapView_horizontalSpace, 0F).toInt()
        }
        if (array.hasValue(R.styleable.FloorRelationMapView_lineWidth)) {
            lineWidth = array.getDimension(R.styleable.FloorRelationMapView_lineWidth, 0F)
        }
        if (array.hasValue(R.styleable.FloorRelationMapView_lineColor)) {
            lineColor = array.getColor(R.styleable.FloorRelationMapView_lineColor, Color.GRAY)
        }
    }

    
    private fun updateViews(floor: Int = -1, position: Int = -1) {
        if (adapter == null) {
            return
        }

        if (floor != -1 && floor < adapter?.getFloorsCount() ?: 0 && floor < pointViews.size) {
            val floorViews: ArrayList = pointViews[floor]
            if (position != -1 && position < adapter?.getPointCount(floor) ?: 0 && position < floorViews.size) {
                // 仅替换一个
                var pointView: View? = floorViews[position]
                val layoutParam: ViewGroup.LayoutParams =
                    pointView?.layoutParams ?: LayoutParams(
                        0,
                        LayoutParams.WRAP_ConTENT
                    )
                removeView(pointView)
                pointView = adapter!!.getView(floor, position, this)
                pointView.layoutParams = layoutParam
                floorViews[position] = pointView
                addView(pointView)
            } else {
                // 替换整列view
                for (i in 0 until (adapter?.getPointCount(floor) ?: 0)) {
                    var pointView: View = floorViews[i]
                    removeView(pointView)

                    pointView = adapter!!.getView(floor, i, this)
                    pointView.id = generateViewId()
                    val layoutParam: LayoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT)
                    if (i == 0) {
                        // 每列第一个 view
                        layoutParam.verticalChainStyle = LayoutParams.CHAIN_SPREAD
                        layoutParam.topToTop = id

                        // 与左侧列关联
                        val leftView: View? =
                            if (floor > 0) null else pointViews[floor - 1][0]
                        if (leftView == null) {
                            layoutParam.leftToLeft = id
                            layoutParam.horizontalChainStyle = LayoutParams.CHAIN_SPREAD_INSIDE
                        } else {
                            layoutParam.leftToRight = leftView.id
                            (leftView.layoutParams as LayoutParams).rightToLeft = pointView.id
                        }

                        // 与右侧列关联
                        val rightView: View? =
                            if (floor < adapter!!.getFloorsCount() - 1) pointViews[floor - 1][0] else null
                        if (rightView == null) {
                            layoutParam.rightToRight = id
                        } else {
                            layoutParam.marginEnd = horizontalSpace

                            layoutParam.rightToLeft = rightView.id
                            (rightView.layoutParams as LayoutParams).leftToRight = pointView.id
                        }
                    } else {
                        layoutParam.topMargin = verticalSpace

                        val topView: View = pointViews[floor][i - 1]

                        // 上下成链
                        (topView.layoutParams as LayoutParams).bottomToTop = pointView.id
                        layoutParam.topToBottom = topView.id

                        // 左右对齐
                        layoutParam.leftToLeft = topView.id
                        layoutParam.rightToRight = topView.id
                    }

                    // 每列最后一个 view
                    if (i == adapter!!.getPointCount(floor) - 1) {
                        layoutParam.bottomToBottom = id
                    }

                    pointView.layoutParams = layoutParam
                    floorViews[position] = pointView
                    addView(pointView)
                }
            }
        } else {
            // 替换所有view
            removeAllViews()
            pointViews.clear()
            for (i in 0 until (adapter?.getFloorsCount() ?: 0)) {

                if (adapter!!.getPointCount(i) == 0) {
                    continue
                }

                val floorViews: ArrayList = ArrayList()
                for (j in 0 until (adapter?.getPointCount(i) ?: 0)) {
                    var pointView: View = adapter!!.getView(i, j, this)
                    pointView.id = generateViewId()

                    val layoutParam: LayoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT)
                    if (j == 0) {
                        // 每列第一个 view
                        layoutParam.verticalChainStyle = LayoutParams.CHAIN_SPREAD
                        layoutParam.topToTop = id

                        // 需要与左侧 view 关联
                        val leftView: View? =
                            if (i > 0) pointViews[i - 1][0] else null
                        if (leftView == null) {
                            layoutParam.leftToLeft = id
                            layoutParam.horizontalChainStyle = LayoutParams.CHAIN_SPREAD_INSIDE
                        } else {
                            layoutParam.leftToRight = leftView.id
                            (leftView.layoutParams as LayoutParams).rightToLeft = pointView.id
                        }

                        // 最后一列的第一个 view
                        if (i == adapter!!.getFloorsCount() - 1) {
                            layoutParam.rightToRight = id
                        } else {
                            layoutParam.marginEnd = horizontalSpace
                        }
                    } else {
                        layoutParam.topMargin = verticalSpace

                        val topView: View = floorViews[j - 1]

                        // 上下成链
                        (topView.layoutParams as LayoutParams).bottomToTop = pointView.id
                        layoutParam.topToBottom = topView.id

                        // 左右对齐
                        layoutParam.leftToLeft = topView.id
                        layoutParam.rightToRight = topView.id
                    }

                    // 每列最后一个 view
                    if (j == adapter!!.getPointCount(i) - 1) {
                        layoutParam.bottomToBottom = id
                    }

                    pointView.layoutParams = layoutParam
                    floorViews.add(pointView)
                    addView(pointView)
                }
                pointViews.add(floorViews)
            }
        }

        invalidate()
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        val paint: Paint = Paint()
        paint.color = lineColor
        paint.strokeWidth = lineWidth

        // 遍历左右两列数据的View,判断是否需要绘制连接线
        // 最好改成从 Adapter 中获取关联关系,然后根据关系再找对应的 View 信息,然后绘制
        for (i in 1 until pointViews.size) {
            for (l in pointViews[i - 1].indices) {
                for (r in pointViews[i].indices) {
                    if (adapter?.isRelationCollect(i - 1, l, i, r) == true) {
                        canvas?.drawLine(
                            (pointViews[i - 1][l].left + pointViews[i - 1][l].measuredWidth).toFloat(),
                            (pointViews[i - 1][l].top + pointViews[i - 1][l].measuredHeight / 2).toFloat(),
                            pointViews[i][r].left.toFloat(),
                            (pointViews[i][r].top + pointViews[i][r].measuredHeight / 2).toFloat(),
                            paint
                        )
                    }
                }
            }
        }
    }
}

adapter

package com.xxx

import android.view.View
import android.view.ViewGroup

abstract class baseFloorRelationAdapter {

    var dataSetChangedListener: IOnDataSetChangedListener? = null

    
    abstract fun getFloorsCount(): Int

    
    abstract fun getPointCount(floor: Int): Int

    
    abstract fun getView(floor: Int, position: Int, parent: ViewGroup): View

    
    abstract fun isRelationCollect(
        leftFloor: Int,
        leftPosition: Int,
        rightFloor: Int,
        rightPosition: Int
    ): Boolean

    
    fun notifyDataSetChanged(floor: Int = -1, position: Int = -1) {
        dataSetChangedListener?.dataSetChanged(floor, position)
    }

    
    interface IonDataSetChangedListener {
        
        fun dataSetChanged(floor: Int = -1, position: Int = -1)
    }
}

demoActivity

package com.xxx

import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.xxx.R

class FloorRelationMapDemoActivity : Activity() {

    val mapView: FloorRelationMapView by lazy { findViewById(R.id.map) }

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

        val data = listOf(
            listOf("a1", "a2", "a3"),
            listOf("b1", "b2", "b3", "b4", "b5"),
            listOf("c1", "c2", "c3")
        )

        val adapter: baseFloorRelationAdapter = object : baseFloorRelationAdapter() {
            override fun getFloorsCount(): Int = data.size

            override fun getPointCount(floor: Int): Int = data[floor].size

            override fun getView(floor: Int, position: Int, parent: ViewGroup): View {
                val view: View = LayoutInflater.from(this@FloorRelationMapDemoActivity)
                    .inflate(R.layout.b_item_floor_relation_demo, parent, false)
                view.findViewById(R.id.tv_text).text = data[floor][position]
                return view
            }

            
            override fun isRelationCollect(
                leftFloor: Int,
                leftPosition: Int,
                rightFloor: Int,
                rightPosition: Int
            ): Boolean = (leftFloor + leftPosition + rightFloor + rightPosition) % 3 == 0
        }
        mapView.adapter = adapter
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/658964.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号