Use Kotlin to perform function operations on ViewGroup views.
Original article title: Functional operations over Views in ViewGroup using Kotlin
Link: http://antonioleiva.com/functional-operations-viewgroup-kotlin/
Antonio Leiva (http://antonioleiva.com/about)
Original article published:
Set, iteration, array, sequence... all these share a set of usefulFunctions. These functions can be used to convert, sort, and perform other operations on their elements.. However, due to the class constructor, some functions are not available in the Android SDK.
For example, we cannot directly obtainViewGroup
Internal View list, so these operations are not available. But not all are lost. In Kotlin, We have methodsPrepare any data for these operations. Easy to learn: we only need to create oneSequence
. In our example, Sequence will be a group of orderedView
. We only needImplement a function and returnIterator
.
If we haveSequence
The functional operation field opens the door for us to use them. So let's start with it.
Note: Read the end of the article
As lakedaemon666 suggested in the comments, there is a simpler method without Sequence to get the same result. I will keep the original record, but I suggest you look at the alternative solution.
Create the Sequence of ViewGroup
As mentioned above, we will create an iterator which must know whether there is a next item and which is the next one. We also create an extension function to provide a simple method for any ViewGroup and inheritance class:
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 = true13 }14 return nextValue != null15 }16 17 override fun next(): View {18 if (!hasNext()) {19 throw NoSuchElementException()20 }21 val answer = nextValue22 nextValue = null23 return answer!!24 }25 }26 }
Retrieve the recursive list of views
Obtain a View list and perform function operations on it. Therefore, we can first create a top-level view list, and then use it to recursively retrieve the listViewGroup
.
Let'sViewGroup
Create newExtended attributes. Extended attributes are very similar to extended functions and can be used in any class:
1 public val ViewGroup.views: List<View>2 get() = asSequence().toList()
We can use this to create a recursive function that returns anyViewGroup
All internalView
:
1 public val ViewGroup.viewsRecursive: List<View>2 get() = views flatMap {3 when (it) {4 is ViewGroup -> it.viewsRecursive5 else -> listOf(it)6 }7 }
UseflatMap
To convert Multiple lists of all results to one list. It traverses any view. If it is a ViewGroup, it also traverses its own view. Otherwise, only one list is returned.
Usage example
Now, we getviewRecursive
Attribute to perform any operation we want. Here you can see two examples. I created such a simple layout:
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/8 android: paddingRight = "@ dimen/activity_horizontal_margin" 9 android: paddingTop = "@ dimen/activity_vertical_margin" 10 tools: context = ". mainActivity "> 11 12 <TextView13 android: layout_width =" wrap_content "14 android: layout_height =" wrap_content "15 android: text = "@ string/hello_world"/> 16 17 <FrameLayout18 android: layout_width = "match_parent" 19 android: layout_height = "wrap_content" 20 android: layout_centerInParent = "true"> 21 22 <TextView23 android: layout_width = "wrap_content" 24 android: layout_height = "wrap_content" 25 android: text = "Hello Java"/> 26 27 <TextView28 android: layout_width = "wrap_content" 29 android: layout_height = "wrap_content" 30 android: layout_gravity = "center_horizontal" 31 android: text = "Hello Kotlin"/> 32 33 <TextView34 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 <LinearLayout42 android: layout_width = "match_parent" 43 android: layout_height = "wrap_content" 44 android: layout_alignParentBottom = "true" 45 android: orientation = "horizontal"> 46 47 <CheckBox48 android: layout_width = "wrap_content" 49 android: layout_height = "wrap_content" 50 android: text = "Check 1"/> 51 52 <CheckBox53 android: layout_width = "wrap_content" 54 android: layout_height = "wrap_content" 55 android: text = "Check 2"/> 56 57 <CheckBox58 android: layout_width = "wrap_content" 59 android: layout_height = "wrap_content" 60 android: text = "Check 3"/> 61 62 <CheckBox63 android: layout_width = "wrap_content" 64 android: layout_height = "wrap_content" 65 android: text = "Check 4"/> 66 67 </LinearLayout> 68 69 </RelativeLayout>
For exampleMainActivity.onCreate()
Medium,
This Code can be applied. It setsHello Kotlin!
Convert the string to uppercase letters, and select the even check box:
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 unchecked11 views filter {12 it is CheckBox13 } forEach {14 with(it as CheckBox) {15 val number = text.toString().removePrefix("Check ").toInt()16 setChecked(number % 2 == 0)17 }18 }
Alternative solution
As mentioned in the comments of lakedaemon666 (Thank you for your explanation), if we traverse the entire sequence, it makes no sense to create a sequence. For example, when reading files by row, sequence is a lazy iteration. Only required items are required. However, we need to use all items, so a simple list is enough.
In addition, there are many simple ways to generate a View list. We can generate the View index list based on the Value Field and map them to the View list we need. All of this is just one line:
1 public val ViewGroup.views: List<View>2 get() = (0..getChildCount() - 1) map { getChildAt(it) }
Summary
This is a boring example, but this concept may function all your code and stop relying on typical iterative programming loops and other control flows.
Remember that from my book "Kotlin for Android Developers", you can learn this and many other features of Kotlin. You will learn Kotlin by creating an Android APP from 0.