使用 Kotlin 對 ViewGroup 中的 View 進行函數式操作

使用 Kotlin 對 ViewGroup 中的 View 進行函數式操作

Collections, iterators, arrays, sequences...都對 轉換,排序或者其他對 item 的操作 提供了完整的支持。不過由于這些類在 Android 中被構造的方式不同, 某些部分在 Android SDK 中使不能使用的。

比如,我們只能獲取一個 ViewGroup 而不能直接獲得一個 View 的 list,像這樣的操作 是不可以的。但是我們還可以使用其他的特性。使用 Kotlin,我們可以 使用這些操作 準備任何類型的數據。這個竅門很容易:我們只需創建一個 Sequence。 在我們的例子中,sequence 是一組按順序排列的 View 集合。我們只需要去 實現一個返回 Iterator 的函數

假設我們有一個 Sequence,函數世界的大門就已經為我們打開。那么讓我們開始吧。

Note: 請看文章結尾部分 lakedaemon666 在評論里提到了一種更簡單的方式獲取相同的結果,而且不必使用 Sequence 。原文這里不會修改,不過我建議你去看一看他的解決辦法。


從 ViewGroup 中創建一個 Sequence

前面提到了,我們想創建一個 iterator,而且需要知道它是否有下一個 item, 下一個 item 是什么。可以通過創建一個 extension function(擴展函數), 為所有 ViewGroup 和它的子類提供一個簡單的方式完成這項工作:

fun ViewGroup.asSequence(): Sequence<View> = object : Sequence<View> {

    override fun iterator(): Iterator<View> = object : Iterator<View> {
        private var nextValue: View? = null
        private var done = false
        private var position: Int = 0

        override public fun hasNext(): Boolean {
            if (nextValue == null && !done) {
                nextValue = getChildAt(position)
                position++
                if (nextValue == null) done = true
            }
            return nextValue != null
        }

        override fun next(): View {
            if (!hasNext()) {
                throw NoSuchElementException()
            }
            val answer = nextValue
            nextValue = null
            return answer!!
        }
    }
}

檢索 View 的遞歸 list

獲取一個 view 的list 對其進行函數操作的 。我們首先創建一個所有一級 view 的 list, 然后使用它們去遍歷搜索 ViewGroups 里的其它 view。需要給 ViewGroup 新建一個 extension property (擴展屬性)。extension property 和 extension function 很像,可以被添加到任意一個 class 里:

public val ViewGroup.views: List<View>
    get() = asSequence().toList()

接下來,創建一個遞歸函數返回 layout 中每個 ViewGroup 里的所有 view。

public val ViewGroup.viewsRecursive: List<View>
    get() = views flatMap {
        when (it) {
            is ViewGroup -> it.viewsRecursive
            else -> listOf(it)
        }
    }

使用 flatap 操作將所有結果中的多個 list 轉換成單個的 list。 它會遍歷所有 view,返回僅有一個 item 的 list, 如果遍歷的對象是一個 ViewGroup,就會請求獲取 ViewGroup 內的 view。

用例

現在我們可以對 viewsRecursive 屬性執行我們想要的操作了。這里有兩個例子, 我創建了下面這樣的一個 layout:

img

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/container"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingBottom="@dimen/activity_vertical_margin"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"/>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello Java"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="Hello Kotlin"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end"
            android:text="Hello Scala"/>

    </FrameLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Check 1"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Check 2"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Check 3"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Check 4"/>

    </LinearLayout>

</RelativeLayout>

下面是代碼部分,可以在 MainActivity.onCreate() 執行。它把 Hello Kotlin 字符串轉換成大寫,然后還設置了 Checkox 的點擊事件:

val container: ViewGroup = find(R.id.container)
val views = container.viewsRecursive

// Set Kotlin TextView to Upper
val kotlinText = views.first {
    it is TextView && it.text.toString().contains("Kotlin")
} as TextView
kotlinText.text = kotlinText.text.toString().toUpperCase()

// Set even checkboxes as checked, and odd as unchecked
views filter {
    it is CheckBox
} forEach {
    with(it as CheckBox) {
        val number = text.toString().removePrefix("Check ").toInt()
        setChecked(number % 2 == 0)
    }
}

img

另一種思路

lakedaemon666 在評論中提到(感謝回復),創建并遍歷一個 sequence 沒有什么意義。 Sequences 意味著 lazy iteration,比如,當讀取 file 里的某一行時,只有需要的 item 被請求。而在我們設定的場景中,所有 item 都被使用了,所以一個普通的 list 就可以滿足我們的需求。

此外,還有更簡單的方法創建一個 view 的 list。

public val ViewGroup.views: List<View>
    get() = (0..getChildCount() - 1) map { getChildAt(it) }

結語

這個例子看起來可能有點傻,不過你可以從中學到些東西讓你的代碼更加函數化, 而不是循環或者其他更典型的迭代編程中的流程控制。

你可以通過我正在寫的這本書: Kotlin for Android Developers 來了解更多關于 Kotlin 的內容,這本書會教你從零開始使用 Kotlin 來完成一個 Android App。


所屬標簽

無標簽

25选5玩法中奖