300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > android自定义view星空 自定义RecyclerView星空列表「多item且互相交错 自定义列表 ViewGroup级」...

android自定义view星空 自定义RecyclerView星空列表「多item且互相交错 自定义列表 ViewGroup级」...

时间:2021-06-06 23:31:10

相关推荐

android自定义view星空 自定义RecyclerView星空列表「多item且互相交错 自定义列表 ViewGroup级」...

自定义"RecyclerView"列表「多item且互相交错,自定义列表,ViewGroup级」

前段时间看到一个游戏的列表觉得挺不错的,模仿着做个一个类似的,文章的源码,demo用到的设计图「简单版」

因为这个列表对点的精确要求极高,所有我们必须使用屏幕适配方案,本篇文章使用开源库AndroidAutoSize,不清楚的同学可以看这里

整个项目是以360*640为基准进行适配的,,项目源代码已经上传到Github,项目使用kotlin语言,完全不熟悉的可以看这里

进度展示.png

首先,先看需求,这个item有什么特殊之处

item并不是完全一样的,以4个圆圈,也就是4个item为一个“周期”,但是注意,列表并不是只会出现4的倍数,而是有可能为5,6,7这样的数字,这个周期只是一个当item出现以4为倍数时才会出现

1,3,5,7,1,3,5 这样的规律,1表示就是第一个小圆圈,3表示的就大圆圈,5表示不同位置的小圆圈...等等

item之间的互相交错的,item与item之间会有重叠的部分,这就决定了我们不能使用recyclerView来做这个列表

item与item之间有一条线连着

思路

思路,最外层就是一个FrameLayout帧布局,然后我们的item实际上就只有两个,一个小圆圈item,一个大圆圈item,第1,3,4 个item只是位置变化了而已,其实都还是小圆圈

然后我们再根据返回的列表数据动态的将这些item添加到ViewGroup就可以了,至于item与item之间的连线,我们使用一个Path连接每一个item的中心点,再使用Paint画笔画出来就可以了

另外我们确定点的数值是通过蓝湖提供的标注的,所以注意对比着设计图看代码,Ok,看代码吧

创建实体类

我们需要一个实体类来模拟列表的内容实体类,当点击列表对应item时,会返回点击集合的索引

data class StarryBean(var chapterName: String)

记录item位置和圆点坐标的实体类

/**

* 控制item位置的实体类

* 1,item距离左边的距离

* 2,item距离顶部的距离

* 3,item中心x坐标,也就是圆点的x坐标

* 4,item中心y坐标,也就是圆点的y坐标

*/

data class CircleBean( var marginleft: Int,var marginTop: Int,var dotX: Int, var doty: Int)

创建item

之后我们创建item_cirrcle_bg.xml,也就是大圆圈(切图请前往蓝湖点击下载该页面全部切图,或参考源码)

小圆圈item和大圆圈一样,只是大小变了,item_cirrcle_small.xml

View代码

最终控件的代码,另外注意要在初始化时加上setWillNotDraw(false)出行,如果不加上这个属性在ViewGroup级自定义控件时无法绘制Path路径

#4DFFFFFF

#FFFFFF

需要用到的颜色

/**

* Created by 舍长 on /5/16

* describe:自定义复杂交错的列表

*/

class StarryListView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {

//进度实体列表

private var mList = ArrayList()

//item位置,圆点坐标实体类列表

private var mDistanceList = ArrayList()

//背景path路径,透明度30%,颜色为白色

private var mBackPath = Path()

//已经完成进度的path路径,透明度100%,白色

private var mCurrentPath = Path()

//背景画笔

private var mBackPaint = Paint()

//已经完成进度的画笔

private var mCurrentPaint = Paint()

//区间组件的间隔

public var mInterval=424f

init {

//初始化4个基准点的距离,具体坐标看蓝湖的设计图,

//注意圆点距离左边标注图给出的是86,但是还要加上圆点的半径,就是91

//这里在一开始就将单位数值为dp的数值转换成px,后面就直接用就可以了

mDistanceList.add(CircleBean(dp2px(22f), dp2px(0f), dp2px(91f), dp2px(69f)))

mDistanceList.add(CircleBean(dp2px(118f), dp2px(31f), dp2px(245f), dp2px(158f)))

mDistanceList.add(CircleBean(dp2px(38f), dp2px(209f), dp2px(107f), dp2px(278f)))

mDistanceList.add(CircleBean(dp2px(118f), dp2px(307f), dp2px(187f), dp2px(376f)))

//初始化背景路径画笔

mBackPaint.style = Paint.Style.STROKE

mBackPaint.strokeWidth = AutoSizeUtils.dp2px(context, 1f).toFloat()//画笔大小

mBackPaint.color = resources.getColor(R.color.color30FFFFFF)//画笔颜色

//初始化实际路径画笔

mCurrentPaint.style = Paint.Style.STROKE

mCurrentPaint.strokeWidth = AutoSizeUtils.dp2px(context, 1f).toFloat()//画笔大小

mCurrentPaint.color = resources.getColor(R.color.public_colorWhite)//画笔颜色

//在ViewGroup容器级自定义控件总,一定要加上这个属性,不然path无法绘制

setWillNotDraw(false)

}

/**

* 设置相应的数据

*/

fun setListData(list: ArrayList) {

this.mList = list

//路径的起点就是第一个圆点的坐标

mBackPath.moveTo(mDistanceList[0].dotX.toFloat(), mDistanceList[0].doty.toFloat()) //未解锁章节路径

mCurrentPath.moveTo(mDistanceList[0].dotX.toFloat(), mDistanceList[0].doty.toFloat())//已解锁章节路径

/**

* 遍历列表

* 每4个item为一个我们规划好的基础区间,在这个区间上4个item的基础位置是固定的,而到了第二个区间

* 例如4~8 ,9~12,就是分别在对应的区间加上424dp,即时第5个小圆圈顶部距离第一个小圆圈顶部的距离

* 计算区间[1,2(大圆圈),3,4],计算当前区间倍数的个数是当前所以/4(从第二个区间开始计算),例如5/4=1,就是1倍,第二个区间

* 所有基础位置数值要加上1*424dp

* 而判断是具体1,2,3,4哪个基础位置的计算公式为:i - 4 * multiple,

* 例如,第二个大圆圈,例如,5(数列从0开始)-4*1=1「数列从0开始」

*/

for (i in 0 until list.size) {

var multiple = 0//区间倍数

//当数列索引大于3,也就是第二个区间开始时才开始计算倍数

if (i > 3) {

multiple = i / 4

}

//绘制背景线段

mBackPath.lineTo(mDistanceList[i - 4 * multiple].dotX.toFloat(), mDistanceList[i - 4 * multiple].doty.toFloat() + dp2px( mInterval) * multiple)

//如果已经解锁

if(list[i].islock){

mCurrentPath.lineTo(mDistanceList[i - 4 * multiple].dotX.toFloat(), mDistanceList[i - 4 * multiple].doty.toFloat() + dp2px( mInterval) * multiple)

}

//设置布局参数

val params =

LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)

//计算item需要距离顶部多少距离,基准距离+倍数乘446

params.topMargin =

mDistanceList[i - 4 * multiple].marginTop + (dp2px(mInterval) * multiple)

//如果当前需要添加的item是大圆圈

if (i == 1 + 4 * multiple) {

val bigView = LayoutInflater.from(context).inflate(R.layout.item_circle_big, null, false)

//设置距离左边的距离

params.leftMargin = mDistanceList[i - 4 * multiple].marginleft

//初始化控件

val rlContainer = bigView.findViewById(R.id.rlContainer)//整个布局

val textView = bigView.findViewById(R.id.tvChapterName)//章节名称

//设置点击事件,当点击item时会回调数列的索引

rlContainer.setOnClickListener {

onClickListener.onSelect(i)

}

//设置章节标题

textView.text = list[i].chapterName

//将设置好的item View Add到ViewGroup上

addView(bigView, params)

} else {

//如果当前需要添加的item是小圆圈

val smallView = LayoutInflater.from(context).inflate(R.layout.item_circle_small, null, false)

//设置距离左边的距离

params.leftMargin = mDistanceList[i - 4 * multiple].marginleft

//初始化控件

val rlContainer = smallView.findViewById(R.id.rlContainer)//整个布局

val textView = smallView.findViewById(R.id.tvChapterName)//章节名称

//设置点击事件,当点击item时会回调数列的索引

rlContainer.setOnClickListener {

onClickListener.onSelect(i)

}

//设置章节标题

textView.text = list[i].chapterName

//将设置好的item View Add到ViewGroup上

addView(smallView, params)

}

}

invalidate()

}

override fun onDraw(canvas: Canvas?) {

super.onDraw(canvas)

canvas?.drawPath(mBackPath, mBackPaint)//绘制为解锁章节背景虚线

canvas?.drawPath(mCurrentPath, mCurrentPaint)//绘制已经解锁章节路径

}

/**

* 用来对选中item位置的回调,onSelect方法也可以根据需要设置成实体类,

* 我是返回集合索引,在Activity中进行处理

*/

private lateinit var onClickListener: OnClickListener

interface OnClickListener {

fun onSelect(index: Int)

}

fun setStarryOnClickListener(onProgressListener: OnClickListener) {

this.onClickListener = onProgressListener

}

}

Activity代码

最后在Activity的xml中使用,要使用ScrollView包住View,不然无法滑动

在Activity中使用

/**

* Created by 舍长

* describe:

*/

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

/**

* 已经解锁的章节的透明度是100%,未解锁章节的透明度是30%

*/

val list = ArrayList()

list.add(StarryBean("烟火", true))

list.add(StarryBean("守望", true))

list.add(StarryBean("烟火", true))

list.add(StarryBean("守望", true))

list.add(StarryBean("烟火", false))

list.add(StarryBean("守望", false))

list.add(StarryBean("守望", false))

list.add(StarryBean("守望", false))

starryList.setListData(list)

//设置点击回调监听

starryList.setStarryOnClickListener(object :StarryListView.OnClickListener{

override fun onSelect(index: Int) {

toast("点击了${list[index].chapterName}")

}

})

}

}

好了,结束,如果你觉得本文对你有所帮忙,可以资助我继续写下去

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。