Android - Listen to a disabled button - android

How can I respond to an event based on clicking a disabled Button.
I have a requirement that I have to present Dialog, when a disabled Button is clicked but the listener I have assigned does not fire even when I setClickable(false)
Am an android noob, sorry.

You can for example use #setActivated() method instead. Disabling a view will ignore all events.
https://developer.android.com/reference/android/view/View.html#setActivated(boolean).
Then you can customize text and background styles with android:state_activate attribute if you need:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_activated="false"
android:color="#color/a_color" />
<item android:state_activated="true"
android:color="#color/another_color" />
</selector>

A disabled button cannot listen to any event, but you can customize your own button by extending Button class to make your own definition of disabling

You can override onTouchEvent and create a listener like this :
class MyButton #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.materialButtonStyle) : MaterialButton(context, attrs, defStyleAttr) {
private var onDisableClickListener: OnClickListener? = null
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (!isEnabled && event?.action == MotionEvent.ACTION_DOWN) {
onDisableClickListener?.onClick(this)
}
return super.onTouchEvent(event)
}
fun setOnDisableClickListener(l: OnClickListener?) {
onDisableClickListener = l
}
}
In your activity :
button.setOnDisableClickListener {
Toast.makeText(this), "The button is disabled", Toast.LENGTH_SHORT).show()
}
button.setOnClickListener {
Toast.makeText(this), "The button is enabled", Toast.LENGTH_SHORT).show()
}

Instead of disabling it, keep it enabled but use a flag to control your "inner state"

I solved this issue by using a flag to keep my button's state.
private boolean isMyButtonEnabled = false;
public void onMyButtonClick(View v) {
if(isMyButtonEnabled){
..
}
}

you can add android:allowClickWhenDisabled attribute to your button in xml like this:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:allowClickWhenDisabled="true"/>

I looked for it but got nothing to listen the EditText block. So I find another way to activate it. If there is a near button or area that you already listen, you can enable SetOnLongClickListener to activate the block. It will be a secret but you can tell the users.
button.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
// TODO Auto-generated method stub
editText.setEnabled(true)
return true;
}
});

I'm about to tackle this by using the selected state, which is generally available for use in widgets, and can be used in state list drawables. A simple search for usage of isSelected turns up results in ListView, GridView, TextView and TabLayout. And the documentation states
Views are typically
* selected in the context of an AdapterView like ListView or GridView;
* the selected view is the view that is highlighted.

You should use activated state to enable or disable button . It is clickable or as someone point use selected or checked state. Each of these state has a different meaning so use it carefully

create in res/color/color_state.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#767C7F" android:state_activated="true" />
<item android:color="#CBCBCB" android:state_activated="false" />
<item android:color="#CBCBCB" />
</selector>
set textColor by:
android:textColor="#color/color_state"
set event click to change state color:
binding.format1.setOnClickListener {
binding.format1.isActivated = true
binding.format2.isActivated = false
binding.format3.isActivated = false
}

Related

How to set click listener to this Button

I cant get this to work I want the sign out Button on this preferences screen to have a ClickListener
This is how it looks like:
Here´s the code and the buttonView is always NULL:
class PreferencesFragment : PreferenceFragmentCompat() {
lateinit var activity: Context
private var prefs: SharedPreferences = BleApplication.getInstance().getDefaultSharedPreferences()
override fun onAttach(context: Context?) {
super.onAttach(context)
activity = requireActivity()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val buttonView = view.findViewById<View>(R.id.btn_sign_out)
if (buttonView != null) {
buttonView.setOnClickListener {
Toast.makeText(getActivity(), "You clicked me.", Toast.LENGTH_SHORT).show()
}
}
// Hide the divider
/* setDivider(ColorDrawable(Color.TRANSPARENT))
setDividerHeight(0)*/
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.app_prefs)
}
}
I also tried the kotlinx.android.synthetic but same problem there
Here´s the xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:layout="#layout/pref_category_text"
android:title="#string/pref_category_remote_battery_title">
<SwitchPreferenceCompat
android:key="#string/pref_key_category_remote_battery_switch"
android:title="#string/pref_category_remote_battery_switch_title"
android:summary="#string/pref_category_remote_battery_switch_summ"/>
</PreferenceCategory>
<PreferenceCategory
android:layout="#layout/pref_category_text"
android:title="#string/pref_category_sign_out_title">
<Preference
android:key="#string/pref_key_category_signed_out"
android:widgetLayout="#layout/pref_category_sign_out_button"
android:title="#string/pref_category_sign_out_button_title"
android:summary="#string/pref_category_sign_out_buttom_summ"/>
</PreferenceCategory>
</PreferenceScreen>
Here is the "#layout/pref_category_sign_out_button" layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="#+id/btn_sign_out"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/buttonshape"
android:text="#string/pref_category_sign_out_title" />
</LinearLayout>
Since your Fragment extends from PreferenceFragmentCompat, you should not try to set a View.OnClickListener but override PreferenceFragmentCompat.onPreferenceTreeClick() instead. According to the documentation, this method is ...
Called when a preference in the tree rooted at this PreferenceScreen has been clicked.
Code example in Java:
#Override
onPreferenceTreeClick(Preference preference){
if(preference.getKey().equals(getContext().getString(R.string.pref_key_category_signed_out))){
// user clicked signout "button"
// take appropriate actions
// return "true" to indicate you handled the click
return true;
}
return false;
}
Code example in Kotlin (I hope I can trust Android Studio :P)
override fun onPreferenceTreeClick(preferenceScreen: PreferenceScreen, preference: Preference): Boolean {
return if (preference.key == context.getString(R.string.pref_key_category_signed_out)) {
// user clicked signout "button"
// take appropriate actions
// return "true" to indicate you handled the click
true
} else false
}
This will enable you to catch click events for the Preference but not for the Button.
In order to do that as well, one can use a custom Preference and override onBindViewHolder(PreferenceViewHolder). Since PreferenceViewHolder - similar to RecyclerView.ViewHolder - has a field itemView which contains the inflated layout, here is a good opportunity to set our own View.OnClickListener.
SignOutPreference extends from TwoStatePreference (in the com.android.support:preference-v7 library) because replacing the CheckBox widget with your custom Button requires only to set the android:widgetLayout attribute, just like you do in your code snippet:
<PreferenceCategory
android:layout="#layout/pref_category_text"
android:title="#string/pref_category_sign_out_title">
<your.package.name.SignOutPreference
android:key="#string/pref_key_category_signed_out"
android:widgetLayout="#layout/pref_category_sign_out_button"
android:title="#string/pref_category_sign_out_button_title"
android:summary="#string/pref_category_sign_out_buttom_summ"/>
</PreferenceCategory>
SignOutPreference.java
public class SignOutPreference extends TwoStatePreference {
public SignOutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SignOutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SignOutPreference(Context context) {
super(context);
}
#Override
public void onBindViewHolder(final PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
Button button = holder.itemView.findViewById(R.id.btn_sign_out);
if(button != null){
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(holder.itemView.getContext(), "CLICKED!", Toast.LENGTH_SHORT).show();
}
});
}
}
}
As #0X0nosugar mentioned you can use the onPreferenceTreeClicked method to handle all clicks in a convenient way like this:
#Override
onPreferenceTreeClick(Preference preference){
if ((preference.getKey().equals(getContext().getString(R.string.pref_key_category_signed_out))){
// user clicked signout "button"
// take appropriate actions
// return "true" to indicate you handled the click
return true;
}
return false;
}
The problem when using a custom button via widgetLayout is that when the button click is not a Preference click, so the handler doesn't catch it. One way to circumvent this problem is to just disable the built-in click for the button, like this:
<Button
android:id="#+id/btn_sign_out"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/buttonshape"
android:text="#string/pref_category_sign_out_title"
android:clickable="false"/>
See the last line. This way you don't need to create an extra class just for the button and you can easily access whatever methods and variables you have in your PreferencesFragment class.
I'm sure there's a better way to somehow trigger the Preference click from the button click, but at least this works as pretty well.
I was looking for a simple answer and found a way. Just set the onClick attribute for the button in the xml file, and implement the method in the parent activity of the preference fragment. (It's important to implement it in the Activity, not in the Preference Fragment. Or else it will give you crashes)
I wanted to make my onClick method to work only when the button is touched(clicked), and not respond to clicks in the area outside of the button. So far this is the only way that works just like I wanted it to.
My code is in Kotlin, but the logic is simple so it won't be hard to write it in java.
This is my button.xml used for the preference's widgetLayout. Look at how I set the android:onClick= attribute.
<?xml version="1.0" encoding="utf-8"?>
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/btn"
android:text="reset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="#style/rippleEffect" //my custom ripple effect
android:onClick="onClickMethod">
</Button>
Then I implemented the onClickMethod in the parent activity of the preference fragment. This callback method should be public, and have View as input parameter.
(For more info read this -> How exactly does the android:onClick XML attribute differ from setOnClickListener? )
fun onClickMethod(view: View) {
//do something
}
Below is my preference.xml.
<Preference
android:key="pref_key"
android:title="Reset"
android:summary="summary.."
app:iconSpaceReserved="false"
android:widgetLayout="#layout/button"/>
Also I tried to set the ClickListener programmatically, but the only way that worked without errors was when I set the button's ClickListener inside the PreferenceClickListener. This only worked half way, since I need to touch(click) the preference item first to init the button's ClickListener.
val view = findPreference<androidx.preference.Preference>("pref_key")
var isFirst = true
view?.setOnPreferenceClickListener {
if (isFirst) {
btn.setOnClickListener {
Toast.makeText(requireContext(), "button clicked!", Toast.LENGTH_SHORT).show()
}
isFirst = false
it.summary = "unlocked"
} else {
isFirst = true
it.summary = "locked - tap to unlock"
}
true
}
Anyway this answer is working well for me, but I'm still looking for a way to use my preference key since this method does not fully uses the preference attribute, but just as a layout. But for now I hope this is helpful for those who want to use buttons in android preference.

Change button background color as if pressed, but without the user pressing it

On Android, a Button changes its background color when pressed.
How can we tell a button that it is pressed (without firing the onClick-action), so that it changes color, without the user pressing it? (for example triggered by a swipe action)
It should change color briefly, and then change back.
There a quite a few questions concerning keeping the pressed state. This question asks, how to set the button_pressed state briefly, as if clicked, but without a real click.
Button.setPressed(true) has not given a color change, neither has Button.performClick().
First, create the effect when button is hovered, clicked etc in XML. Put this style in your drawable.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Pressed button -->
<item android:drawable="#color/dark_green"
android:state_focused="true"
android:state_pressed="false"
/>
<item android:drawable="#color/dark_green"
android:state_focused="true"
android:state_pressed="true"
/>
<item android:drawable="#color/dark_green"
android:state_focused="false"
android:state_pressed="true"/>
<!-- Normal button -->
<item android:drawable="#color/green"
android:state_focused="false"
android:state_pressed="false"/>
</selector>
Then in your XML, initiates the style by using:
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#drawable/the_style_in_drawable"
android:text="click"/>
By putting the style in your XML, you don't have to initiate the style when button on click. Android will detect the button state and do the work for you. Just remember to put the state in selector.
To change a button state without anything else is done via
btn1.getBackground().setState(new int[]{android.R.attr.state_pressed});
To reset to ordinary, you use
btn1.getBackground().setState(new int[]{android.R.attr.state_enabled});
A Button's states can be found out via
btn1.getBackground().getState();
which resturns an int[]. You can compare its values to android.R.attr to find out which states are set.
Example Code
private void simulateClick(final ImageButton button,
final long clickDuration) {
button.getBackground().setState(new int[]{android.R.attr.state_pressed});
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(clickDuration);
} catch ( InterruptedException e ) {
// not bad if interrupted: sleeps a bit faster (can happen?)
}
Count.this.runOnUiThread(new Runnable() {
public void run() {
button.getBackground().setState(new int[]{android.R.attr.state_enabled});
}
});
}}).start();
}
Explanation
Each View has a Drawable as background image. A Drawable can be of different subtypes, here it is a StateListDrawable, as defined per XML. (See #Lynx's answer as an example of a XML defined drawable).
This Drawable can be told which state it is to assume (via setState) and does the layout itself.
AsyncTask for button color change illusion:
private class ChangeButtonColorMomentarily extends AsyncTask<String, Void, String> {
#Override
protected void onPreExecute() {
btn1.setBackgroundDrawable(new ColorDrawable(Color.rgb(50, 50, 50)));//pressed state
}
#Override
protected String doInBackground(String... params) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
return "";
}
#Override
protected void onPostExecute(String result) {
btn1.setBackgroundDrawable(new ColorDrawable(Color.rgb(200, 200, 200)));//normal state
}
}
Also take note that if your API 16 above use setBackground() instead.
For changing the color of button at that time, you can use setOnTouchListener as:
button.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
//Button Pressed
}
if(event.getAction() == MotionEvent.ACTION_UP){
//finger was lifted
}
return false;
}
});

Showing Cursor inside EditText when focus is not on EditText

I am working on Android Smart TV application:
In a view there is a custom keyboard and an EditText.
When application launches focus goes to the keyboard.
Desired:
When the user types with keyboard (clicking with a remote) the cursor should also blink inside the editText.
How can I show this effect inside the EditText?
This happens if you set a background for the field. If you want to solve this, set the cursorDrawable to #null.
You should add textCursorDrawable with cursorVisible.
Reference to a drawable that will be drawn under the insertion cursor.
android:cursorVisible="true"
android:textCursorDrawable="#null"
You could try something like this:
editText.setText(text);
editText.setPressed(true);
editText.setSelection(editText.getText().length()); // moves the cursor to the end of the text
However, there are 2 problems with this approach:
The cursor will not blink. The logic for the blinking is in the Editor class and cannot be overridden. It requires that the EditText is focused, and only 1 View can be focused at once within a Window - in your case that will be one of the keyboard buttons.
/**
* #return True when the TextView isFocused and has a valid zero-length selection (cursor).
*/
private boolean shouldBlink() {
if (!isCursorVisible() || !mTextView.isFocused()) return false;
...
}
The cursor will not always be visible. The blinking of the cursor is based on the System time - it is visible for half a second, and hidden for the next half a second. The cursor will only be visible if the code I suggested above is called at a point in time when the cursor would be visible according to the System time.
This is why the native keyboard/IME works the way it does. It is a separate Window that allows the EditText to maintain focus and have the blinking cursor functionality, while the user is tapping on Views in a different Window (the keyboard/IME).
That being said, there is a workaround for the problems above - make sure to set shouldBlink to false when you no longer need it though, it's a guaranteed memory leak or crash otherwise:
private void blink() {
if (shouldBlink) {
editText.setText(editText.getText());
editText.setPressed(true);
editText.setSelection(editText.getText().length());
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (shouldBlink) {
blink();
}
}
}, 500);
}
}
You can do this..I hope/think that u have a layout for the buttons u have created, by this u can set a Focus Listener for that layout and inside the onFocusChange method you can check if(layout.hasFocus()) and do this...
For example if your editText is named as et, u can set this to it:
et.setActivated(true);
et.setPressed(true);
I have a small example code for you having two edit text
et2.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if(et2.hasFocus()){
//et1.setCursorVisible(true);
et1.setActivated(true);
et1.setPressed(true);
}
}
});
In your layout xml file add the following line in your edit text:
<requestFocus/>
This will place the cursor in your editText widget.
Hope it helps.
simply add
editText.requestFocus();
There is a couple of ways doing it:
1) XML
android:cursorVisible="true"
2) Java
mEditText.setOnClickListener(editTextClickListener);
OnClickListener editTextClickListener = new OnClickListener() {
public void onClick(View v) {
if (v.getId() == mEditText.getId()) {
mEditText.setCursorVisible(true);
}
}
};
or
if (mEditText.hasFocus()){
mEditText.setCursorVisible(true);
}
I know this is necro, but this was much better than the solutions above. Just extend EditText and add:
#Override
public boolean isCursorVisible() {
return true;
}
#Override
public boolean isFocused() {
return true;
}
And in your XML:
<com.foo.MyEditText
...
android:focusable="false"
android:focusableInTouchMode="false"
android:clickable="false"
android:cursorVisible="true"/>
Now the EditText thinks it is focused and the cursor is visible, but it actually can't be focused.
private void setFocusCursor(){
mBinding.replyConversationsFooter.footerEditText.setFocusable(true);
`mBinding.replyConversationsFooter.footerEditText.setFocusableInTouchMode(true);`
`mBinding.replyConversationsFooter.footerEditText.requestFocus();`
}
Just call this function in oncreateView() and there you go.
We can only set one and only focus on a window.So doing this will help you solve your problem.
You can use the following code in your Activity:
//Get the EditText using
EditText et = (EditText)findViewById(R.id.editText);
//Set setCursorVisible to true
et.setCursorVisible(true);
You can explicitly put caret to last position in text:
int pos = editText.getText().length();
editText.setSelection(pos);
This will always focus on first character on edittext.
android:focusable="true"
Tested on API 27, 28 emulator.
Remove a background attribute, add focusable:
<EditText
...
android:focusable="true"
android:focusableInTouchMode="true"
/>
In code write: edit.requestFocus(); Though an underline will be visible.
In order to remove an underline, see https://stackoverflow.com/a/52052087/2914140:
edit.getBackground().mutate().setColorFilter(ContextCompat.getColor(getContext(), R.color.white), PorterDuff.Mode.SRC_ATOP);
To change a color of the cursor see https://stackoverflow.com/a/49462015/2914140:
add android:textCursorDrawable="#drawable/shape_cursor", while shape_cursor is:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="1dp"
android:height="25dp"
/>
<solid android:color="#color/gray" />
</shape>
It works on API 27, 28 emulator, but on a real device (API 21) cursor disappears. I tried many variants from here and there, but nothing helped.
Then I noticed that when EditText contains at least one symbol, it shows cursor. I added a TextWatcher to add a space when nothing entered.
private lateinit var someText: String
...
edit.requestFocus()
edit.setText(" ")
edit.addTextChangedListener(YourTextWatcher())
private inner class YourTextWatcher : TextWatcher {
override fun afterTextChanged(s: Editable?) {
someText = s.toString().trim()
if (someText.isEmpty()) {
// To not fall into infinite loop.
if (s?.length != 1) {
edit.setText(" ")
}
} else {
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}
Also you can add paddings in order to tap inside EditText, if it is small.
I did like this:
var msgEditText = dialog.findViewById(R.id.msg1textView) as EditText
msgEditText.isActivated = true
msgEditText.isPressed = true
msgEditText.requestFocus()
msgEditText.setSelection(view.getText().length)

Android: Button state changed event

Some XML attributes of buttons (such as background, textColor, etc) can be defined with color or drawable state List like this:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:color="#ffff0000"/>
<item android:state_focused="true"
android:color="#ff0000ff"/>
<item android:state_enabled="true"
android:color="#ff00ffff"/>
<item android:color="#ff000000"/>
</selector>
When view state changes (pressed/unpressed, for example), corresponding color is changed automatically.
How can I prograqmmatically handle some kind of stateChangedEvent to perform more complicated layout change, than just changing a color (for example, change font size or set another text)?
For focus changes and touch events you can register listeners by setOnFocusChangeListener and setOnTouchListener. And changes about disabled/enabled states you can perform directly after changing your button state.
// use the selector method to pass your button and image
// you can use color also
b1=(Button)findViewById(R.id.button1);
// b2=(Button)findViewById(R.id.button2);
selector(b1, R.drawable.image_1_2, R.drawable.image_1);
// selector(b2, R.drawable.image_2_2, R.drawable.image_2);
}
public void selector(Button b,int pressed_image,int normal_image )
{
StateListDrawable states = new StateListDrawable();
states.addState(new int[] {android.R.attr.state_pressed},
getResources().getDrawable(pressed_image));
states.addState(new int[] { },
getResources().getDrawable(normal_image));
b.setBackgroundDrawable(states);
}
Just override View.setPressed:
#Override
public void setPressed(boolean pressed) {
super.setPressed(pressed);
...
}
Handler onTouch(View v, MotionEvent event) for specific views and perform actions in MotionEvent.DOWN / Up according to your requirement.
You have to take a reference to the View Object (button) via findViewById(<object_id>) and than use the appropriate methods from the API.
For example:
private Button aButton;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
aButton = (Button) findViewById(R.id.your_button_id);
//example 1
aButton.setVisibility(SomeExpressionEvaluation ? View.GONE : View.VISIBLE);
//example 2
if (SomeExpressionEvaluation) {
aButton.setText("Some Text");
}
and so on, just take a look at the API, especially inherited methods from the View class.
I must say you can use a (touch listner) this how u use a touch listner
image=(ImageView)findViewById(R.id.image);
find ur image first
Add a touch Listner to ur image
image.setOnTouchListener(image_onTouch);
//Add a touch method which is by name image_onTouch
OnTouchListener image_onTouch=new OnTouchListener(){
#Override
public boolean onTouch(View arg0,MotionEvent arg1){
int iAction=arg1.getAction();
if(iAction==0){
image.setImageResource(R.drawable.image1);
}
else{
image.setImageResource(R.drawable.image2);
}
return false;
}
};
// image 1 is ur image which u want 2 click and image 2 is the image when you touch that image you have to make an another image in which background color do u wanna show and use it in the code
I had also this kind of problem in past. I solved by putting this XML file in separate drawable folder in Res instaed of drawable-mdpi or else. And make sure that you have to give this Xml as your button's background.

Disable an ImageButton

This looks easy, but I'm not able to disable an ImageButton. It continues to receive click events, and its appearance don't change like a standard Button would.
There are some similar questions on SO, but they don't help me.
Even with a very simple layout like this :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ImageButton
android:id="#+id/btn_call"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:clickable="false"
android:enabled="false"
android:src="#android:drawable/sym_action_call" />
</LinearLayout>
The button is still enabled and I can click it.
What's strange is that if I change the ImageButton to a simple Button, then it works as expected. The button becomes disabled and unclickable. I don't understand. Does anyone have an idea?
Here's the code I use to disable an ImageButton and make it look grayed out:
/**
* Sets the specified image buttonto the given state, while modifying or
* "graying-out" the icon as well
*
* #param enabled The state of the menu item
* #param item The menu item to modify
* #param iconResId The icon ID
*/
public static void setImageButtonEnabled(Context ctxt, boolean enabled, ImageButton item,
int iconResId) {
item.setEnabled(enabled);
Drawable originalIcon = ctxt.getResources().getDrawable(iconResId);
Drawable icon = enabled ? originalIcon : convertDrawableToGrayScale(originalIcon);
item.setImageDrawable(icon);
}
/**
* Mutates and applies a filter that converts the given drawable to a Gray
* image. This method may be used to simulate the color of disable icons in
* Honeycomb's ActionBar.
*
* #return a mutated version of the given drawable with a color filter
* applied.
*/
public static Drawable convertDrawableToGrayScale(Drawable drawable) {
if (drawable == null) {
return null;
}
Drawable res = drawable.mutate();
res.setColorFilter(Color.GRAY, Mode.SRC_IN);
return res;
}
Simply call setImageButtonEnabled(); the only downside is you need the image's resource ID in here because it's not possible to revert a transformed icon back into the original.
ImageButton has different inheritance chain meaning it does not extend Button:
ImageButton < ImageView < View
It continues to receive click events
Here is what happens when you set a click listener for the View:
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
So if you set a listener the android:clickable="false" changes to android:clickable="true".
and its appearance don't change like a standard Button would
You should supply a drawable state list to the view so it could set an appropriate image based on android:enabled. Do you have this? Or you have the only image for your button?
EDIT: You can find info on StateListDrawable here. android:state_enabled is what you need to use in the list in order to tell the OS what image to use for that state.
EDIT2: Since you really need to add a listener you can make a check inside of the listener if (!isEnabled()) { return; } else { /* process the event */ }.
if you want to disable an image button,on click event, set the the property "setEnabled" to false
Ex: imgButton.setEnabled(false);
Make sure there is no view with same id in your view hierarchy and you do not add any click listener to that view.
Taking advantage of the Oleg Vaskevich's answer. Can be made an answer for Kotlin.
Make a Extension Function for ImageButton, this way:
/**
* Sets the specified image buttonto the given state, while modifying or
* "graying-out" the icon as well
*
* #param enabled The state of the menu item
* #param iconResId The icon ID
*/
fun ImageButton.setButtonEnabled(enabled: Boolean, iconResId: Int) {
isEnabled = enabled
val originalIcon = context.resources.getDrawable(iconResId)
val icon = if (enabled) originalIcon else convertDrawableToGrayScale(originalIcon)
setImageDrawable(icon)
}
And you get a little less reliant on providing Context
I managed to build a solution inspired by Oleg Vaskevich's answer, but without the need to pass drawable resource ID to setEnabled().
Here is Kotlin code, inside of utility module:
fun Drawable.deepCopy(): Drawable =
constantState?.newDrawable()?.mutate() ?:
throw RuntimeException("Called on null Drawable!")
fun Drawable.toGrayscale(): Drawable =
deepCopy().apply { setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_IN) }
fun ImageButton.setAndShowEnabled(enabled: Boolean) {
if (enabled == isEnabled)
return
isEnabled = enabled
if (enabled) {
setImageDrawable(tag as Drawable)
}
else {
if (tag == null)
tag = drawable
setImageDrawable(drawable.toGrayscale())
}
}
It can be used like this:
val button: ImageButton = findViewById(...)
// ...
button.setAndShowEnabled(false)
// launch async operation
GlobalScope.launch {
// do work here
// unblock button
button.setAndShowEnabled(true)
}
As other answers have said, you cannot disable an ImageButton in the layout XML as you can a Button, but you can disable both the same way at runtime:
In Java:
button.setEnabled(false); // setEnabled(boolean) on TextView
imgButton.setEnabled(false); // setEnabled(boolean) on View
In both cases the button is disabled -- no click events get to its onClickListener.
You can also change the icon color of the disabled ImageButton the same way you change the text color on a disabled Button, assuming the icon is tintable.
In the layout XML:
<Button
...
android:textColor="#drawable/button_color_selector" />
<ImageButton
...
android:tint="#drawable/button_color_selector" />
Now setEnable(boolean) on the Button or ImageButton changes the text or icon color according to the states in your button_color_selector.xml
To improve on Ivan's 2018 answer: This is a much simpler method (Kotlin):
fun ImageButton.setAndShowEnabled(enabled: Boolean) {
val filter = PorterDuffColorFilter(GRAY, PorterDuff.Mode.SCREEN)
if (enabled == this.isEnabled)
return
this.isEnabled = enabled
if (enabled) {
drawable.colorFilter = null // Removes the filter.
} else {
drawable.mutate() // Repeated calls are no-ops.
drawable.colorFilter = filter
}
}

Categories

Resources