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 2019/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}")
}
})
}
}
好了,結束,如果你覺得本文對你有所幫忙,可以資助我繼續寫下去
總結
以上是生活随笔為你收集整理的android自定义view星空,自定义RecyclerView星空列表「多item且互相交错,自定义列表,ViewGroup级」...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 量子搜索算法(Grover Algori
- 下一篇: html left属性,css中left