如何禁用布局中的所有视图?

例如:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/backbutton"
android:text="Back"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/my_layout"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/my_text_view"
android:text="First Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/my_edit_view"
android:width="100px"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<View .../>
<View .../>
...
<View .../>
</LinearLayout>


</LinearLayout>

有没有办法禁用(setEnable (false)) LinearLayoutmy_layout中的所有元素?

126862 次浏览

Another way is to call setEnabled() on each child (for example if you want to do some extra check on child before disabling)

LinearLayout layout = (LinearLayout) findViewById(R.id.my_layout);
for (int i = 0; i < layout.getChildCount(); i++) {
View child = layout.getChildAt(i);
child.setEnabled(false);
}

this one is recursive for ViewGroups

private void disableEnableControls(boolean enable, ViewGroup vg){
for (int i = 0; i < vg.getChildCount(); i++){
View child = vg.getChildAt(i);
child.setEnabled(enable);
if (child instanceof ViewGroup){
disableEnableControls(enable, (ViewGroup)child);
}
}
}

Let's change tütü's code

private void disableEnableControls(boolean enable, ViewGroup vg){
for (int i = 0; i < vg.getChildCount(); i++){
View child = vg.getChildAt(i);
if (child instanceof ViewGroup){
disableEnableControls(enable, (ViewGroup)child);
} else {
child.setEnabled(enable);
}
}
}

I think, there is no point in just making viewgroup disable. If you want to do it, there is another way I have used for exactly the same purpose. Create view as a sibling of your groupview :

<View
android:visibility="gone"
android:id="@+id/reservation_second_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:background="#66ffffff"
android:clickable="false" />

and at run-time, make it visible. Note: your groupview's parent layout should be either relative or frame layout. Hope this will help.

tutu's answer is on the right track, but his recursion is a little awkward. I think this is cleaner:

private static void setViewAndChildrenEnabled(View view, boolean enabled) {
view.setEnabled(enabled);
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
setViewAndChildrenEnabled(child, enabled);
}
}
}

to disable a view, you must call the method setEnabled with false for argument. ex:

Button btn = ...
btn.setEnabled(false);

Set

android:descendantFocusability="blocksDescendants"

for yor ViewGroup view. All descendants will not take focus.

If some desperate developer scrolls down here, I have another option to do it. Which also disables scrolling as far as I experimented with it. The idea is to use View element like this one in a RelativeLayout, under all your UI elements.

<View
android:id="@+id/shade"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/primaryShadow"
android:visibility="gone"/>

So it is set to be "gone" before some condition. And then you set it's visibility to VISIBLE when you want to disable your UI. Also you have to implement OnClickListener for this View. This onClickListener will catch click event and won't pass it to the underlying elements.

Use below recursive function to make your child views visible or gone. First argument is your parent view and second argument decides if you want childs of parent view visible or gone. true = visible false = gone

private void layoutElemanlarininGorunumunuDegistir(View view, boolean gorunur_mu_olsun) {
ViewGroup view_group;
try {
view_group = (ViewGroup) view;
Sabitler.konsolaYazdir(TAG, "View ViewGroup imiş!" + view.getId());
} catch (ClassCastException e) {
Sabitler.konsolaYazdir(TAG, "View ViewGroup değilmiş!" + view.getId());
return;
}


int view_eleman_sayisi = view_group.getChildCount();
for (int i = 0; i < view_eleman_sayisi; i++) {
View view_group_eleman = view_group.getChildAt(i);
if (gorunur_mu_olsun) {
view_group_eleman.setVisibility(View.VISIBLE);
} else {
view_group_eleman.setVisibility(View.GONE);
}
layoutElemanlarininGorunumunuDegistir(view_group_eleman, gorunur_mu_olsun);
}
}

Although not quite the same as disabling views within a layout, it is worth mentioning that you can prevent all children from receiving touches (without having to recurse the layout hierarchy) by overriding the ViewGroup#onInterceptTouchEvent(MotionEvent) method:

public class InterceptTouchEventFrameLayout extends FrameLayout {


private boolean interceptTouchEvents;


// ...


public void setInterceptTouchEvents(boolean interceptTouchEvents) {
this.interceptTouchEvents = interceptTouchEvents;
}


@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return interceptTouchEvents || super.onInterceptTouchEvent(ev);
}


}

Then you can prevent children from receiving touch events:

InterceptTouchEventFrameLayout layout = (InterceptTouchEventFrameLayout) findViewById(R.id.layout);
layout.setInterceptTouchEvents(true);

If you have a click listener set on layout, it will still be triggered.

Actully what work for me is:

getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);

and to undo it:

getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
  private void disableLL(ViewGroup layout){
for (int i = 0; i < layout.getChildCount(); i++) {
View child = layout.getChildAt(i);
child.setEnabled(false);
child.setClickable(false);
if (child instanceof ViewGroup)
disableLL((ViewGroup) child);
}
}

and call method like this :

RelativeLayout rl_root = (RelativeLayout) findViewById(R.id.rl_root);
disableLL(rl_root);

If you want to disable a set of, or say a particular kind of view.Let's say you want to disable a fixed number of buttons with some particular text of or no text then you can use array of that type and loop through the array elements while disabling the buttons using setEnabled(false) property You can do it on a function call like this:

public void disable(){
for(int i=0;i<9;i++){
if(bt[i].getText().equals("")){//Button Text condition
bt[i].setEnabled(false);
}
}
}

I improved the tütü response to properly disable EditText and RadioButton componentes. Besides, I'm sharing a way that I found to change the view visibility and add transparency in the disabled views.

private static void disableEnableControls(ViewGroup view, boolean enable){
for (int i = 0; i < view.getChildCount(); i++) {
View child = view.getChildAt(i);
child.setEnabled(enable);
if (child instanceof ViewGroup){
disableEnableControls((ViewGroup)child, enable);
}
else if (child instanceof EditText) {
EditText editText = (EditText) child;
editText.setEnabled(enable);
editText.setFocusable(enable);
editText.setFocusableInTouchMode(enable);
}
else if (child instanceof RadioButton) {
RadioButton radioButton = (RadioButton) child;
radioButton.setEnabled(enable);
radioButton.setFocusable(enable);
radioButton.setFocusableInTouchMode(enable);
}
}
}


public static void setLayoutEnabled(ViewGroup view, boolean enable) {
disableEnableControls(view, enable);
view.setEnabled(enable);
view.setAlpha(enable? 1f: 0.3f);
}


public static void setLayoutEnabled(ViewGroup view, boolean enable, boolean visibility) {
disableEnableControls(view, enable);
view.setEnabled(enable);
view.setAlpha(enable? 1f: 0.3f);
view.setVisibility(visibility? View.VISIBLE: View.GONE);
}

This is a pretty delayed answer.But it might help someone. Many answers mentioned above seem to be good. But if your layout.xml has nested viewgroups. Then above answers may not provide full result. Hence i have posted my opinion as a snippet. With the code below one can disable all views (Including Nested ViewGroups).

NOTE: Try avoiding nested ViewGroups as they are not recommended.

 private void setEnableView(boolean b) {
LinearLayout layout = (LinearLayout)findViewById(R.id.parent_container);
ArrayList<ViewGroup> arrVg = new ArrayList<>();
for (int i = 0; i < layout.getChildCount(); i++) {
View child = layout.getChildAt(i);
if (child instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) child;
arrVg.add(vg);
}
child.setEnabled(b);
}


for (int j=0;j< arrVg.size();j++){
ViewGroup vg = arrVg.get(j);
for (int k = 0; k < vg.getChildCount(); k++) {
vg.getChildAt(k).setEnabled(b);
}
}
}

Details

  • Android studio 3.1.4
  • Kotlin 1.2.70
  • checked in minSdkVersion 19

Solution

fun View.forEachChildView(closure: (View) -> Unit) {
closure(this)
val groupView = this as? ViewGroup ?: return
val size = groupView.childCount - 1
for (i in 0..size) {
groupView.getChildAt(i).forEachChildView(closure)
}
}

Usage

val layout = LinearLayout(context!!)
layout.forEachChildView {  it.isEnabled = false  }


val view = View(context!!)
view.forEachChildView {  it.isEnabled = false  }


val fragment = Fragment.instantiate(context, "fragment_id")
fragment.view?.forEachChildView {  it.isEnabled = false  }

If you're interested in disabling views in a specific ViewGroup then you can use the interesting, perhaps slightly obscure duplicateParentState. A view state is a set of boolean attributes such as pressed, enabled, activated, and others. Just use this on each child you want to sync to parent ViewGroup:

android:duplicateParentState="true"

Note that it duplicates the entire state and not just the enabled state. This may be what you want! Of course, this approach is best if you're loading layout XML.

In Kotlin, you can use isDuplicateParentStateEnabled = true before the View is added to the ViewGroup.

As documented in the setDuplicateParentStateEnabled method, if the child view has additional states (like checked state for a checkbox), these won't be affected by the parent.

The xml analogue is android:duplicateParentState="true".

I personally use something like this (vertical tree traversal using recursion)

fun ViewGroup.deepForEach(function: View.() -> Unit) {
this.forEach { child ->
child.function()
if (child is ViewGroup) {
child.deepForEach(function)
}
}
}

usage :

   viewGroup.deepForEach { isEnabled = false }

For me RelativeLayout or any other layout at the end of the xml file with width and height set to match_parent with attribute focusable and clickable set to true.

 <RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true">


<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />


</RelativeLayout>

My two cents function without recursion

    package io.chord.ui.utils


import android.view.View
import android.view.ViewGroup
import androidx.core.view.forEach


class ViewUtils
{
companion object
{
fun setViewState(view: View, state: Boolean)
{
var depth = 0
val views: MutableMap<Int, MutableList<View>> = mutableMapOf()
views[depth] = mutableListOf(view)


while(true)
{
val currentViews = views[depth]
val nextViews = mutableListOf<View>()


currentViews!!.forEach { view ->
if(view is ViewGroup)
{
view.forEach { children ->
nextViews.add(children)
}
}
}


if(nextViews.size == 0)
{
break
}


depth++


views[depth] = nextViews
}


views.flatMap {
it.value
}.forEach {
it.isEnabled = state
}
}
}
}

The easiest way is creating a <View in your xml, with match_parent for height and width, make sure the view is above every other views, then when you want to prevent clicks, make it visible add an onClickListener to that view with null as parameter.

Example:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/backbutton"
android:text="Back"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/my_layout"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/my_text_view"
android:text="First Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/my_edit_view"
android:width="100px"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
        

<View
android:id="@+id/disable_layout_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
</LinearLayout>


</LinearLayout>

Then in your code:

val disableLayoutView = rootView.find<View>(R.id.disable_layout_view)
disableLayoutView.visibility = View.VISIBLE
disableLayoutView.setOnClickListener(null)

I like to have a control over the root view so I added the includeSelf flag to deepForEach

fun ViewGroup.deepForEach(includeSelf: Boolean = true, function: View.() -> Unit) {
if (includeSelf) function()
forEach {
it.apply {
function()
(this as? ViewGroup)?.apply { deepForEach(includeSelf, function) }
}
}
}

usage :

   viewGroup.deepForEach (includeSelf: true) { isEnabled = false }
You can have whichever children that you want to have the same state as the parents include android:duplicateParentState="true".  Then if you disable the parent, whatever children you have that set on will follow suit.

This will allow you to dynamically control all states at once from the parent view.

<LinearLayout
android:id="@+id/some_parent_something"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/backbutton"
android:text="Back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"/>
<LinearLayout
android:id="@+id/my_layout"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:duplicateParentState="true">
<TextView
android:id="@+id/my_text_view"
android:text="First Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true" />
<EditText
android:id="@+id/my_edit_view"
android:width="100px"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true" />
<View .../>
<View .../>
...
<View .../>
</LinearLayout>


</LinearLayout>

Many answers are good here. But if child views may be change during initialization (e.g. in the case of RecyclerView) you can use the Handler:

fun ViewGroup.setEnabledDeep(enabled: Boolean) {
Handler(Looper.getMainLooper()).post {
isEnabled = enabled
for (i: Int in 0..childCount) {
val child: View = getChildAt(i) ?: continue
child.isEnabled = enabled
if (child is ViewGroup) {
child.setEnabledDeep(enabled)
}
}
}
}

Then:

recyclerView.setEnabledDeep(false)