原文标题:Functional operations over Views in ViewGroup using Kotlin
原文链接:http://antonioleiva.com/functional-operations-viewgroup-kotlin/
原文作者:Antonio Leiva(http://antonioleiva.com/about/)
原文发布:2015-07-29
集合、迭代、数组、序列 ... 所有这些共用一套好用的函数,这组函数可帮助对它们的元素进行转换、排序以及其它操作。但是,由于类的构造方法,在Android SDK中,有部分函数还不能用。
例如,我们不能直接获得ViewGroup
内部视图列表,所以这些操作是不可能使用的。但是并非所有一切都失去了。在Kotlin中,我们有方法为这些操作准备任何数据。诀窍简单:我们只需要创建一个Sequence
。在我们的例子中,Sequence将是一组有序的View
。我们只需要实现一个函数,其返回Iterator
。
如果我们有了Sequence
,函数式操作领域就为我们打开了使用它们的大门。所以让我们从它开始。
注:阅读文章结尾
如lakedaemon666在评论中所建议的那样,有一个不用Sequence的更简单的方法可以获得同样的结果。我会保留原文记录,但是建议你看看替代的解决方案。
创建ViewGroup的Sequence
如前所述,我们将创建迭代器(iterator),它必须知道是否有下一项,下一项是哪个。我们还创建一个扩展函数,为了任何 ViewGroup 和继承类提供一个简单的方法做那项工作:
1 fun ViewGroup.asSequence(): Sequence<View> = object : Sequence<View> { 2 3 override fun iterator(): Iterator<View> = object : Iterator<View> { 4 private var nextValue: View? = null 5 private var done = false 6 private var position: Int = 0 7 8 override public fun hasNext(): Boolean { 9 if (nextValue == null && !done) { 10 nextValue = getChildAt(position) 11 position++ 12 if (nextValue == null) done = true 13 } 14 return nextValue != null 15 } 16 17 override fun next(): View { 18 if (!hasNext()) { 19 throw NoSuchElementException() 20 } 21 val answer = nextValue 22 nextValue = null 23 return answer!! 24 } 25 } 26 }
检索视图递归列表
获得一个视图列表,并对其进行函数操作是非常有用的。因此,我们可以先创建顶层视图列表,然后,用它以递归方式逐级检索ViewGroup
中视图。
让我们为ViewGroup
创建新扩展属性。扩展属性非常类似扩展函数,可用于任何类:
1 public val ViewGroup.views: List<View> 2 get() = asSequence().toList()
我们可以用这,创建递归函数,它返回布局中任何ViewGroup
内部的所有View
:
1 public val ViewGroup.viewsRecursive: List<View> 2 get() = views flatMap { 3 when (it) { 4 is ViewGroup -> it.viewsRecursive 5 else -> listOf(it) 6 } 7 }
用flatMap
,我们把全部结果的多个列表转换到一个列表中。它将遍历任何视图;如果是ViewGroup,它还会遍历自己的视图。否则,就返回仅有一项的列表。
用法实例
现在,我们得到viewRecursive
属性,执行我们想要的任何操作。这里你可以看到两个例子。我创建这样一个简单的布局:
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:id="@+id/container" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" 6 android:paddingBottom="@dimen/activity_vertical_margin" 7 android:paddingLeft="@dimen/activity_horizontal_margin" 8 android:paddingRight="@dimen/activity_horizontal_margin" 9 android:paddingTop="@dimen/activity_vertical_margin" 10 tools:context=".MainActivity"> 11 12 <TextView 13 android:layout_width="wrap_content" 14 android:layout_height="wrap_content" 15 android:text="@string/hello_world"/> 16 17 <FrameLayout 18 android:layout_width="match_parent" 19 android:layout_height="wrap_content" 20 android:layout_centerInParent="true"> 21 22 <TextView 23 android:layout_width="wrap_content" 24 android:layout_height="wrap_content" 25 android:text="Hello Java"/> 26 27 <TextView 28 android:layout_width="wrap_content" 29 android:layout_height="wrap_content" 30 android:layout_gravity="center_horizontal" 31 android:text="Hello Kotlin"/> 32 33 <TextView 34 android:layout_width="wrap_content" 35 android:layout_height="wrap_content" 36 android:layout_gravity="end" 37 android:text="Hello Scala"/> 38 39 </FrameLayout> 40 41 <LinearLayout 42 android:layout_width="match_parent" 43 android:layout_height="wrap_content" 44 android:layout_alignParentBottom="true" 45 android:orientation="horizontal"> 46 47 <CheckBox 48 android:layout_width="wrap_content" 49 android:layout_height="wrap_content" 50 android:text="Check 1"/> 51 52 <CheckBox 53 android:layout_width="wrap_content" 54 android:layout_height="wrap_content" 55 android:text="Check 2"/> 56 57 <CheckBox 58 android:layout_width="wrap_content" 59 android:layout_height="wrap_content" 60 android:text="Check 3"/> 61 62 <CheckBox 63 android:layout_width="wrap_content" 64 android:layout_height="wrap_content" 65 android:text="Check 4"/> 66 67 </LinearLayout> 68 69 </RelativeLayout>
例如,在MainActivity.onCreate()
中,
可应用这段代码。它将Hello Kotlin!
串转换为大写字母,选中偶数复选框:
1 val container: ViewGroup = find(R.id.container) 2 val views = container.viewsRecursive 3 4 // Set Kotlin TextView to Upper 5 val kotlinText = views.first { 6 it is TextView && it.text.toString().contains("Kotlin") 7 } as TextView 8 kotlinText.text = kotlinText.text.toString().toUpperCase() 9 10 // Set even checkboxes as checked, and odd as unchecked 11 views filter { 12 it is CheckBox 13 } forEach { 14 with(it as CheckBox) { 15 val number = text.toString().removePrefix("Check ").toInt() 16 setChecked(number % 2 == 0) 17 } 18 }
替代的解决方案
如lakedaemon666在评论中所提及的那样(谢谢解释),如果之后我们遍历整个sequence,那么创建sequence就没有什么意义了。例如,当按行读取文件时,sequence是懒惰迭代。只有要求必要的项目。而我们却要使用所有项,所以简单的列表就足够了。
另外,有许多简单的方法可以产生视图列表。我们可以依赖值域产生视图的索引列表,将它们映射到我们需要的视图列表中。所有这一切就一行:
1 public val ViewGroup.views: List<View> 2 get() = (0..getChildCount() - 1) map { getChildAt(it) }
总结
这是个无聊的例子,但是这个概念或许可使你的所有代码函数化,停止依靠那些典型迭代式编程的循环和其它控制流。
记住从我写的书《Android开发者的Kotlin》中,你能够学习到Kotlin的这点以及许多其它能力,你将通过从0开始创建Android APP学习Kotlin。