java – Have a disabled onClick?-ThrowExceptions

Exception or error:

I want to be able to respond to a click event on a disabled switch, is that possible?

I have a switch that is not enabled until the user fills in some information, so it looks like this:

enter image description here

I want to prompt the user to fill out the information if they click on the disabled switch with a dialog, like so:

 mySwitch.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            if (!userInfo.isFilled){
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("Fill out info first!")
                        .setMessage("You must first fill out info before turning on this featurel")
                        .setNeutralButton("Okay", null)
                        .show();
            }
        }
    });

However, the onClick() is not triggered when I click on the disabled switch, so how do I get when the user clicks on it?

How to solve:

You could place a transparent View on top of the Switch and toggle its enabled state opposite the Switch, and show the message when this overlaid View is clicked.

###

From the View.java source code,

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

the enabled flag ensures the UnhandledEvents are consumed however not passed along to the listeners,thereby bypassing all your possible code.So it is not possible to listen to events on a disabled view.

That said, your options are,

  1. Change the style to mimic that of a disabled view as mentioned here,and then add your required functionality.
  2. Add a overlay invisible view to perform your required functionality which you can set to Gone once the view should be enabled.
  3. Use something apart from enabled,(you could setClickable(false) and consume touch events)

###

You can set onTouchListener and react to boolean (e.g isToggleEnable) reference with respect to the user’s previous actions:

 mySwitch.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(!isToggleEnable){
                //Taost here
                }
                //If isToggleEnable = false on return OnClickListener won't be called
                return isToggleEnable; 
            }
        });

###

Try setting setFocusable(false) and setEnabled(true) on your switch. That way, click events will be fired while the switch still being “disabled”. Taken from this answer.

###

When it is disabled, setEnabled(false), these listeners won’t work.

Try this way: don’t disable it, use the setOnCheckedChangeListener and check against your is-entry-filled in there:

use setOnCheckedChangeListener

    switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (!isEntryFilled) {
                buttonView.setChecked(false);
                // your alert dialog
            } else {
            }
        }
    });

this will re-check it back to off and pop your alert, until isEntryFilled is met.

EDIT

OR instead of setEnabled(false), use setClickable(false) or android:clickable="false" since docs say setClickable() is tied to click-events.

and instead of OnClickListener, try OnTouchListener. It will register your on-down-touch (and ignore your on-up-touch), since a click consists of down+up.

    switch.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (!isEntryFilled) {
                buttonView.setChecked(false);
                // your alert dialog
            }
            return false;
        }
    });

then somewhere else, where you check for isEntryFilled, reactivate your switch with switch.setClickable(true)

###

mySwitch.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
                if (isClick()){
                   //Your Valid Code
                }else{
                  //Make our switch to false
                  new AlertDialog.Builder(MainActivity.this)
                           .setTitle("Fill out info first!")
                           .setMessage("You must first fill out info before turning on this featurel")
                            .setNeutralButton("Okay", null)
                            .show();
                   }
                }
});

    public Boolean isClick(){
         //check condition that user fill details or not
         //if yes then return true
         // else return false
    }

###

Let the Parent View intercept ClickEvents or TouchEvents, when its detected check if the receiving View is disabled, and do what you have to do.

Edit

it doesn’t work when disabled?

try these codes, Im use LinearLayout for easy aligment. but overall it should give you an example

this is a full example

XML

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="70dp"
android:background="#273746">   

<FrameLayout
    android:layout_width="match_parent"        
    android:id="@+id/ass"
    android:background="@drawable/abc_popup_background_mtrl_mult"
    android:layout_height="match_parent">
</FrameLayout>

MainActivity onCreate

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_entry_screen);
    FrameLayout fl = (FrameLayout)findViewById(R.id.ass);
    Test t = new Test(this);
    FrameLayout.LayoutParams lp = (LayoutParams) fl.getLayoutParams();
    lp.height = LayoutParams.MATCH_PARENT;
    lp.width = LayoutParams.MATCH_PARENT;
    t.setOrientation(LinearLayout.VERTICAL);
    t.setLayoutParams(lp);
    fl.addView(t);
    t.setBackgroundColor(Color.YELLOW);
    Button b = new Button(this);
    b.setText("patricia");
    t.addView(b);
     b = new Button(this);
    b.setText("monica");
    t.addView(b);

    b = new Button(this);
    b.setText("rebecca");
    t.addView(b);

}

Test.java

public class Test extends LinearLayout {

public Test(Context context) {
    super(context);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    StringBuilder sb = new StringBuilder();
    sb.append("intercept \n\r");
    int x = (int)event.getX(),
            y= (int)event.getY();

    for(int i =0; i< getChildCount(); i++){
        int[] pos = new int[]{getChildAt(i).getLeft(),getChildAt(i).getTop(),
                getChildAt(i).getMeasuredWidth(),
                getChildAt(i).getMeasuredHeight()};

        sb.append(getChildAt(i).getLeft()+", ");
        sb.append(getChildAt(i).getTop()+", ");
        sb.append(getChildAt(i).getMeasuredWidth()+", ");
        sb.append(getChildAt(i).getMeasuredHeight());
        sb.append("\n\r");
        sb.append(isInBounds(pos, x, y));
        sb.append("\n\r");
    }
    sb.append("x is ");
    sb.append(x);
    sb.append("y is ");
    sb.append(y);
    Toast.makeText(getContext(),sb.toString() , Toast.LENGTH_LONG).show();
    return super.onInterceptTouchEvent(event);
}

private boolean isInBounds(int[] dimen, int x, int y){
    return ((x >= dimen[0] && x < (dimen[0] + dimen[2]))
            && (y >= dimen[1] && y < (dimen[1] + dimen[3])));
}

}

Now The one you click will check out to be true, that is the child, now when it checks out to be true you can do something like this

View v = getchildAt(pos);
//its the one that is tapped or clicked
if(!v.isEnabled()){
    //this is the guy you want now, do what you want to do

for click event i am not try this, but you could just do View.performClick() or put your Dialog in the ViewGroup class and call it

actually you could use the View..getClipBounds() to save yourself from int array

###

Set the disable switches on click listener to change the listeners of the other switches. For example:

Switch s = (Switch) findViewById(R.id.SwitchID);

if (s != null) {
    s.setOnCheckedChangeListener(this);
}

/* ... */

public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    Toast.makeText(this, "The Switch is " + (isChecked ? "on" : "off"),
                   Toast.LENGTH_SHORT).show();
    if(isChecked) {
        //do stuff when Switch is ON
        //this is where you set your normal state OnClickListner
    } else {
            mySwitch.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        if (!userInfo.isFilled){
                            new AlertDialog.Builder(MainActivity.this)
                            .setTitle("Fill out info first!")
                            .setMessage("You must first fill out info before turning on this featurel")
                            .setNeutralButton("Okay", null)
                            .show();
                        }
                    }
             });
      }
}

###

I’m guessing you’ve disabled the switch using switch.setEnabled(false). If so, the onclick event will not trigger. If you still want to handle a click action when the switch is disabled, you can use .setOnTouchListener()…

You’re best bet however would be to use .setOnCheckedChangeListener() and keeping the switch enabled. Basically when onCheckChanged() gets called, you can popup your dialog if the switch value is on and when the user click ok, you default the switch back to off.

    mSwitched.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {

            if (checked && !userInfo.isFilled){
                new AlertDialog.Builder(Activity.this)
                        .setTitle("Fill out info first!")
                        .setMessage("You must first fill out info before turning on this featurel")
                        .setNeutralButton("Okay", new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {

                                mSwitched.setChecked(false);
                            }
                        })
                        .show();
            }
        }
    });

###

You can do this in a different way,Give a root layout to toggle button with same width and height of toggle button

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <!--Root layout to toggle button with same height and width 
        of toggle button-->
   <LinearLayout
       android:layout_width="wrap_content"
       android:id="@+id/linear"
       android:layout_height="wrap_content">
    <ToggleButton
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button"
         />
   </LinearLayout>

</RelativeLayout>

When you disable the button,make the button as not focasable and clickable .Then os will handover touch functionality to rootlayout.In the root layout click listner we can write the click logic when the button is not enabled

public class MainActivity extends AppCompatActivity {
    ToggleButton button;
    LinearLayout linearLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button= (ToggleButton) findViewById(R.id.button);
        linearLayout= (LinearLayout) findViewById(R.id.linear);
        //disabling button
        button.setEnabled(false);
        button.setClickable(false);
        button.setFocusableInTouchMode(false);
        button.setFocusable(false);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //write the logic here which will execute when button is enabled
            }
        });
        linearLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //write the logic here which will execute when button is disabled
            }
        });
    }

}

When you enable the button,make button to clickable and focausable.

//enabling button
  button.setEnabled(true);
  button.setClickable(true);
  button.setFocusableInTouchMode(true);
  button.setFocusable(true);

Leave a Reply

Your email address will not be published. Required fields are marked *