1. 程式人生 > >Tornadofx學習筆記(2)——FxRecyclerView控制元件的打造

Tornadofx學習筆記(2)——FxRecyclerView控制元件的打造

Tornadofx是基於javafx的一個kotlin框架,用來寫些電腦版的小程式

基於Scroll Pane控制元件,仿造Android中的RecyclerView,實現的一款tornadofx的控制元件
github

需求

由於我的之前做的幾個專案都是那種類似下載列表的功能,藍奏雲批量下載和m3u8下載合併器

之所以拋棄了javafx的原因,是因為javafx中的動態新增控制元件比較麻煩,於是便是轉到了tornadofx這個框架來。

tornadofx中動態新增控制元件的步驟雖然比javafx中要簡單,但是,我還是覺得有些麻煩

於是我就參考了Android中的RecyclerView使用思路,打造出這個名為FxRecyclerView的控制元件,可以更加方便的動態進行控制元件的增刪查改

功能介紹

  • 動態新增ItemView
  • 動態刪除ItemView
  • 動態更新itemView
  • 快捷繫結單擊/右擊事件

功能演示

上波gif動態圖就能很好說明了

1.新增一組資料

2.新增一個數據

3.指定座標插入一個數據

4.更新指定座標的資料

5.單擊/右擊事件

6.移出指定座標資料/移出所有資料

測試的jar包

使用

1.複製FxRecyclerView原始碼

下載我下面給出的kt檔案,複製到你的tornadofx專案中
FxRecyclerView.kt

2.建立bean類

這個沒啥好說的,就是一個存資料的bean類,如一個Person類,根據自己的情況與需求建立

data class Person(var name: String,var age:String)

3.建立ItemView

這個就是列表中的每一項View,需要繼承tornadofx中的View,我這裡就是顯示Person的name和age屬性,比較簡單演示一下

為了簡單起見,我的ItemView起名為ItemView,各位使用的過程中可以自由更改名字

import javafx.scene.control.Button
import javafx.scene.text.Text
import tornadofx.*

/**
 *
 * @author StarsOne
 * @date Create in  2020/1/21 0021 18:36
 * @description
 *
 */
class ItemView : View("My View") {
    var nameTv by singleAssign<Text>()
    var ageTv by singleAssign<Text>()
    var deleteBtn by singleAssign<Button>()
    override val root = hbox {
        spacing = 20.0
        nameTv = text()
        ageTv = text()
        deleteBtn = button("刪除")

    }
}
data class Person(var name: String,var age:String)

4.介面新增FxRecyclerView

package com.example.demo.view

import tornadofx.*

class MainView : View("Hello TornadoFX") {
    //建立FxRecyclerView需要使用到泛型,第一個是bean類,第二個是ItemView
    val rv = FxRecyclerView<Person,ItemView>()
    override val root = vbox {
        //省略...
        this+=rv
    }
}

4.建立RvAdapter

RvAdapter是抽象類,所以得通過繼承並實現其中的幾個方法

//建立資料
val dataList = arrayListOf<Person>()
for (i in 0..10) {
    dataList.add(Person("張三$i",(18+i).toString()))
}
//重寫RVAdapter的方法
val adapter = object :RVAdapter<Person,ItemView>(dataList){

    override fun onRightClick(itemView: ItemView, position: Int) {
        //右擊事件
        println("右擊$position")
    }

    override fun onClick(itemView: ItemView, position: Int) {
        //單擊事件
        println("單擊$position")
    }

    override fun onCreateView(): ItemView {
        //必須實現
        //返回ItemVIew的例項
        return ItemView()
    }

    override fun onBindData(itemView: ItemView, bean: Person, position: Int) {
        //必須實現
        //將bean類中的資料繫結到itemView中
        itemView.nameTv.text = bean.name
        itemView.ageTv.text  = bean.age
        itemView.deleteBtn.setOnAction {
            rv.remove(position)
        }
    }
}
//設定FxRecyclerView的adapter
rv.adapter = adapter

使用補充

PS:以下的方法都是rv呼叫(FxRecyclerView物件)

方法名 引數說明 方法說明
setWidth(double) double型別的數值 設定寬度
setHegiht(double) double型別的數值 設定高度
setIsShowHorizontalBar(String) 顯示方式,never(不顯示) always(一直顯示) asneed(自動根據需要顯示) 設定是否顯示水平滾動條
addList(arraylist) ArrayList型別的一組資料 新增一組資料,同時更新檢視
addList(list) List型別的一組資料 新增一組資料,同時更新檢視
add(beanT) 新增一個數據,同時更新檢視
add(bean,int) 在列表的指定位置插入指定bean資料對應的itemView。 將當前位於該位置的itemView(如果有)和任何後續的itemView(向其索引新增一個)移動。
update(bean,int) 更新指定位置的資料及itemView檢視
update(bean,oldBean) 更新列表中存在的資料,替換為新的資料,同時更新檢視
remove(bean) 移出某個資料,同時更新檢視
remove(index) 移出列表中指定位置的資料,同時更新檢視
removeAll() 移出列表所有資料,同時更新檢視

FxRecyclerView原始碼

由於kotlin檔案可以寫多個類,我的類都寫在了一個檔案裡

package com.starsone.fxrecyclerview.view

import javafx.scene.control.ScrollPane
import javafx.scene.input.MouseButton
import javafx.scene.layout.VBox
import tornadofx.*

/**
 *
 * @author StarsOne
 * @date Create in  2020/1/20 0020 21:19
 * @description
 *
 */
class FxRecyclerView<beanT : Any, itemViewT : View> : View {
    var adapter: RVAdapter<beanT, itemViewT>? = null
        set(value) {
            field = value
            val adapter = value as RVAdapter<beanT, itemViewT>
            val beanList = adapter.beanList
            val itemViewList = adapter.itemViewList
            for (index in 0 until beanList.size) {
                val itemView = adapter.onCreateView()
                //繫結bean資料到itemView
                adapter.onBindData(itemView, beanList[index], index)
                //itemView新增到列表中
                itemViewList.add(itemView)
                //新增到RecyclerView的主容器中
                container.add(itemView)
                itemView.root.setOnMouseClicked {
                    if (it.button == MouseButton.PRIMARY) {
                        //單擊事件回撥
                        adapter.onClick(itemView, index)
                    }
                    if (it.button == MouseButton.SECONDARY) {
                        //右擊事件回撥
                        adapter.onRightClick(itemView, index)
                    }
                }
            }
        }

    var container = vbox { }

    constructor() {
        root.add(container)
    }

    constructor(vBox: VBox) {
        container = vBox
        root.add(container)
    }

    override val root = scrollpane {
        vbox { }
    }

    /**
     * 設定寬度
     */
    fun setWidth(width: Double) {
        root.prefWidth = width
    }

    /**
     * 設定[height]
     */
    fun setHeight(height: Double) {
        root.prefHeight = height
    }

    /**
     * 設定水平滾動條的顯示方式
     * @param way 顯示方式,never(不顯示) always(一直顯示) asneed(自動根據需要顯示)
     */
    fun setIsShowVerticalBar(way: String) {
        when (way) {
            "never" -> root.hbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
            "always" -> root.hbarPolicy = ScrollPane.ScrollBarPolicy.ALWAYS
            "asneed" -> root.hbarPolicy = ScrollPane.ScrollBarPolicy.AS_NEEDED
        }
    }

    /**
     * 新增一個列表的資料(arraylist)
     */
    fun addList(beanList: ArrayList<beanT>) {
        for (bean in beanList) {
            add(bean)
        }
    }

    /**
     * 新增一個列表的資料(list)
     */
    fun addList(beanList: List<beanT>) {
        for (bean in beanList) {
            add(bean)
        }
    }
    fun add(bean: beanT) {
        val beanList = adapter?.beanList
        val itemViewList = adapter?.itemViewList
        val index = beanList?.size as Int - 1
        beanList.add(bean)
        val itemView = adapter?.onCreateView() as itemViewT
        //invoke onBindData method to bind the bean data to te item view
        adapter?.onBindData(itemView, bean, index)
        //add the item view in the item view list
        itemViewList?.add(itemView)
        //add to the recyclerview container
        container.add(itemView)
        itemView.root.setOnMouseClicked {
            if (it.button == MouseButton.PRIMARY) {
                //單擊
                adapter?.onClick(itemView, index)
            }
            if (it.button == MouseButton.SECONDARY) {
                //右擊
                adapter?.onRightClick(itemView, index)
            }

        }
    }

    /**
     * 在列表的指定位置插入指定bean資料對應的itemView。 將當前位於該位置的itemView(如果有)和任何後續的itemView(向其索引新增一個)移動。
     * @param bean bean資料
     * @param index 要插入的下標
     */
    fun add(bean: beanT, index: Int) {
        val beanList = adapter?.beanList
        val itemViewList = adapter?.itemViewList
        beanList?.add(index, bean)
        val itemView = adapter?.onCreateView() as itemViewT
        //invoke onBindData method to bind the bean data to te item view
        adapter?.onBindData(itemView, bean, index)
        //add the item view in the item view list
        itemViewList?.add(index, itemView)
        //add to the recyclerview container
        container.addChildIfPossible(itemView.root, index)
        itemView.root.setOnMouseClicked {
            if (it.button == MouseButton.PRIMARY) {
                //單擊
                adapter?.onClick(itemView, index)
            }
            if (it.button == MouseButton.SECONDARY) {
                //右擊
                adapter?.onRightClick(itemView, index)
            }
        }
        //更新點選事件的回撥
        for (i in index + 1 until itemViewList?.size as Int) {
            val itemView1 = itemViewList[i]
            adapter?.onBindData(itemView1, beanList!![i], i)
            itemView1.root.setOnMouseClicked {
                if (it.button == MouseButton.PRIMARY) {
                    //單擊
                    adapter?.onClick(itemView1, i)
                }
                if (it.button == MouseButton.SECONDARY) {
                    //右擊
                    adapter?.onRightClick(itemView1, i)
                }
            }
        }
    }

    /**
     * 更新指定位置的itemView
     */
    fun update(bean: beanT, index: Int) {
        remove(index)
        add(bean, index)
    }

    /**
     * 尋找列表中與oldBean相同的第一個元素,將其內容進行修改,同時更新介面的顯示
     * @param bean 新的資料
     * @param oldBean 列表中已存在的資料
     */
    fun update(bean: beanT, oldBean: beanT) {
        val beanList = adapter?.beanList
        val index = beanList?.indexOf(oldBean) as Int
        if (index != -1) {
            update(bean, index)
        } else {
            println("列表中不存在該元素")
        }
    }

    fun remove(bean: beanT) {
        val beanList = adapter?.beanList
        val index = beanList?.indexOf(bean) as Int
        remove(index)
    }

    /**
     * 移出指定下標的itemview
     * @param index 下標
     */
    fun remove(index: Int) {
        val beanList = adapter?.beanList
        val itemViewList = adapter?.itemViewList
        beanList?.removeAt(index)
        val itemView = itemViewList!![index]
        itemView.removeFromParent()
        itemViewList.remove(itemView)
        for (i in index until beanList?.size as Int) {
            adapter?.onBindData(itemViewList[i], beanList[i], i)
            val itemView = itemViewList[i]
            itemView.root.setOnMouseClicked {
                if (it.button == MouseButton.PRIMARY) {
                    //單擊
                    adapter?.onClick(itemView, i)
                }
                if (it.button == MouseButton.SECONDARY) {
                    //右擊
                    adapter?.onRightClick(itemView, i)
                }
            }
        }
    }

    /**
     * 移出所有控制元件
     */
    fun removeAll() {
        val itemViewList = adapter?.itemViewList as ArrayList<itemViewT>
        val beanList = adapter?.beanList as ArrayList<beanT>
        for (itemView in itemViewList) {
            itemView.removeFromParent()
        }
        itemViewList.removeAll(itemViewList)
        beanList.removeAll(beanList)
    }
}

/**
 * 介面卡
 * @author StarsOne
 * @date Create in  2020/1/20 0020 21:51
 * @description
 *
 */
abstract class RVAdapter<beanT : Any, itemViewT : View> {
    val beanList = arrayListOf<beanT>()
    val itemViewList = arrayListOf<itemViewT>()

    constructor(bean: beanT) {
        beanList.add(bean)
    }

    constructor(beanList: List<beanT>) {
        this.beanList.addAll(beanList)
    }

    constructor(beanList: ArrayList<beanT>) {
        this.beanList.addAll(beanList)
    }

    /**
     * 設定返回ItemView
     */
    abstract fun onCreateView(): itemViewT

    abstract fun onBindData(itemView: itemViewT, bean: beanT, position: Int)

    abstract fun onClick(itemView: itemViewT, position: Int)//單擊

//    abstract fun onDoubleClick(itemView: itemViewT, position: Int)//雙擊

    abstract fun onRightClick(itemView: itemViewT, position: Int)//右擊
}