5

I have an activity which contains 2 group of views, which CAN'T be located into same LAYOUT group but belong to same LOGIC group, meaning that they should be shown or hidden and bind click event at same time. The thing is that I feel really awful to write something like this:

fun hide() {
    view1.visibility = View.GONE
    view2.visibility = View.GONE
    view3.visibility = View.GONE
    // ...
    view9.visibility = View.GONE
}


fun show() {
    view1.visibility = View.VISIBLE
    view2.visibility = View.VISIBLE
    view3.visibility = View.VISIBLE
    // ...
    view9.visibility = View.VISIBLE

    view1.setOnClickListener{ run() }
    view2.setOnClickListener{ run() }
    view3.setOnClickListener{ run() }
    // ...
    view9.setOnClickListener{ run() }
}

I did read a post which describes a kotlin skill to simplify this mess by somehow grouping those views then just handle the groups, but unfortunately I can no longer find that post..

Help would be appreciated!

========= Update 2019-07-31 =========

I found the solution but forgot to update this question, the 'grouping' I was looking for, is in fact not a Kotlin specific feature but simply using vararg, and we can use Kotlin extension (which is AWESOME) to simplify a bit more:

// assume we have a base activity or fragment, then put below functions in there
fun View.show() {
    visibility = View.VISIBLE
}

fun show(vararg views: View) {
    views.forEach { it.show() }
}

fun View.hide() {
    visibility = View.GONE
}

fun hide(vararg views: View) {
    views.forEach { it.hide() }
}

// then in any activity or fragment
show(v1, v2, v3, v4)
v9.hide()

============= updated 2020-03-07 ================

This is exactly androidx.constraintlayout.widget.Group designed to do, which can logically group a bunch of views from anywhere and control their visibility by only changing group's visibility.

ZhouX
  • 1,866
  • 18
  • 22
  • add them in one single layout like linear layout... – Amit Jangid Oct 16 '18 at 12:45
  • @AmitJangid I wish it would be that simple but those views belong to same logic group can't be simply added into one layout group..they are all mixed up together.. – ZhouX Oct 16 '18 at 12:49

9 Answers9

16

Since ConstraintLayout 1.1 you can use Group instead of LayoutGroup. You can simply add this code to you XML layout

<android.support.constraint.Group
    android:id="@+id/profile"
    app:constraint_referenced_ids="profile_name,profile_image" />

And then you can call it from code to achieve behavior, that you need

profile.visibility = GONE
profile.visibility = VISIBLE

For more details read this article https://medium.com/androiddevelopers/introducing-constraint-layout-1-1-d07fc02406bc

karenkov_id
  • 630
  • 5
  • 8
5

You need to create extension functions.

For example:

fun View.showGroupViews(vararg view: View) {
    view.forEach {
        it.show()
    }
}

fun View.hideGroupViews(vararg view: View) {
    view.forEach {
        it.hide()
    }
}
ross
  • 2,684
  • 2
  • 13
  • 22
J.Meirlen
  • 173
  • 1
  • 7
  • Thanks! This is the one which is closest to what I was looking for and currently using, I have updated the solution I am using in my question post. Cheers. – ZhouX Jul 31 '19 at 03:00
1

Depending on how is your layout structured you might want to group those views in a ViewGroup like LinearLayout, RelativeLayout, FrameLayout or ConstraintLayout.

Then you can change visibility just on the ViewGroup and all of its children will change it too.

Edit:

Without ViewGroup the only solution to eliminating this boilerplate is to enable databinding in your project and set it like this:

In your Activity/Fragment:

val groupVisible = ObservableBoolean()

fun changeVisibility(show: Boolean) {
    groupVisible.set(show)
}

In your xml:

<layout>
    <data>
        <variable name="groupVisible" type="Boolean"/>
    </data>
    <View
        android:visibility="@{groupVisible ? View.VISIBLE : View.GONE}"/>
</layout>
Ernest Zamelczyk
  • 2,562
  • 2
  • 18
  • 32
  • sorry mate, I wasn't making myself clear, the question is now updated, those views belong to same logic group can't be located into same layout group. – ZhouX Oct 16 '18 at 12:59
1

Why don't you create an array:

val views = arrayOf(view1, view2, view3, view4, view5, view6, view7, view8, view9)

then:

fun show() {
    views.forEach {
        it.visibility = View.VISIBLE
        it.setOnClickListener{  }
    }
}

fun hide() {
    views.forEach { it.visibility = View.INVISIBLE }
}

Or without an array if the names of the views are surely like view1, view2, ...

for (i in 1..9) {
    val id = resources.getIdentifier("view$i", "id", context.getPackageName())
    val view = findViewById<View>(id)
    view.visibility = View.VISIBLE
    view.setOnClickListener{  }
}
forpas
  • 160,666
  • 10
  • 38
  • 76
  • This can do it for sure, thanks! however it is not what I saw in that post, which is something .. err. let's say kotlin-kind-of-magic.. – ZhouX Oct 16 '18 at 13:02
  • why would you want to create unnecessary loops when you can do it via Groups – Satyaraj Moily May 24 '21 at 15:32
1

You can define a function with three parameters and use vararg like following code:

fun changeVisiblityAndAddClickListeners(visibility: Int,
                                        listener: View.OnClickListener,
                                        vararg views: View) {
    for (view: View in views) {
        view.visibility = visibility
        if (visibility == View.VISIBLE) {
            view.setOnClickListener(listener)
        }
    }
}

Of course if you have too many views, this is not a effective solution. I just added this code snippet for an alternative way especially for problems with dynamic view set.

savepopulation
  • 11,736
  • 4
  • 55
  • 80
1

Create a list of views and loop on it

val views = listOf<View>(view1, view2, ...)
views.forEach {
    it.visibility = View.GONE
}

You can also create extension function of Iterable<View> to simplify any kind of action on listed views

fun Iterable<View>.visibility(visibility: Int) = this.forEach {
    it.visibility = visibility
}
//usage
views.visibility(View.GONE)

Maybe you want to locate all views from tags in XML. Take a look at this answer

Lionel Briand
  • 1,732
  • 2
  • 13
  • 21
0

If your views are not inside a view group you can use an extension function. You could create one to toggle the visibility of the views:

fun View.toggleVisibility() {
    if (visibility == View.VISIBLE) {
        visibility = View.GONE
    } else {
        visibility = View.VISIBLE
    }
}

And you can use it like this:

view.toggleVisibility()
André Sousa
  • 1,692
  • 1
  • 12
  • 23
  • thanks those views indeed are not under same layout group, but with your suggestion I still need to write dozen lines of xxxx.toggleVisibility() which is exactly what I would like to avoid.. – ZhouX Oct 16 '18 at 12:58
0

First in your xml layout, group your views by android:tag="group_1" attribute.

Then inside your activity use a for loop to implement whatever logic you need:

val root: ViewGroup = TODO("find your root layout")
for (i in 0 until root.childCount) {
    val v = root.getChildAt(i)
    when (v.tag) {
        "group_1" -> {
            TODO()
        }
        "group_2" -> {
            TODO()
        }
        else -> {
            TODO()
        }
    }
}
Mosius
  • 1,602
  • 23
  • 32
0

You can create a LinearLayout or any other ViewGroup containing your child Views and give it an ID in the XML file, then in your Activity or Fragment class define these functions:

    fun disableViewGroup(viewGroup: ViewGroup) {
        viewGroup.children.iterator().forEach {
            it.isEnabled = false
        }
    }

    fun enableViewGroup(viewGroup: ViewGroup) {
        viewGroup.children.iterator().forEach {
            it.isEnabled = true
        }
    }

And then in onCreate() or onStart() call it as following:

disableViewGroup(idOfViewGroup)

The children method returns a Sequence of children Views in the ViewGroup which you can iterate by forEach and apply whatever operation applicable to Views.

Hope it helps!

HumbleBee
  • 1,212
  • 13
  • 20