Is there an advantage in using setImageLevel() with LevelListDrawable?
I looked it up but there were only small examples.
I usually use setImageResource() a lot of times and I'd like to know if is better with the other method.
It depends on use case. If you should show different images on ImageView, using LeveListDrawable would make your code more decoupled and cleaner.
Let's say in your ImageView, you should show different emotions like facebook has and you'd need to show user which reaction they have chosen.
We could show proper emotion image every time using if or switch statement.
private void setProperImageForEmotion() {
int emotion = 1; // Let's imagine 1 is for like, ... , 6 is for angry
int resId = getCorrectDrawableSource(emotion);
}
private int getCorrectDrawableSource(int emotion) {
switch (emotion) {
case 2:
return R.drawable.love;
case 3:
return R.drawable.haha;
case 4:
return R.drawable.wow;
case 5:
return R.drawable.sad;
case 6:
return R.drawable.angry;
default:
return R.drawable.like;
}
}
Imagine that in future you have to add more emotions which makes you to add extra drawables to your switch statement. However, using LevelListDrawable will make your code much more cleaner and you do not have to add unrelated drawable ids to your Java or Kotlin code. Therefore, if we change above code to use LevelListDrawable, your code would look like this:
You need to create drawable file which will hold references to other drawable and you can define different levels:
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:maxLevel="1" android:drawable="#drawable/like" />
<item android:maxLevel="2" android:drawable="#drawable/love" />
<item android:maxLevel="3" android:drawable="#drawable/haha" />
<item android:maxLevel="4" android:drawable="#drawable/wow" />
<item android:maxLevel="5" android:drawable="#drawable/sad" />
<item android:maxLevel="6" android:drawable="#drawable/angry" />
</level-list>
Now in your Java or Kotlin code, you can set proper level:
private void setProperImageForEmotion() {
int emotion = 1; // Let's imagine 1 is for like, ... , 6 is for angry
imageView.setImageLevel(emotion);
}
As you can see, it simplifies the code. Also, you can use LevelListDrawable for setting icon in Notification and NotificationCompat.
Related
Android beginner here, so please bear with me...
I'm using a drawer where the menu items are added dynamically.Currently, this is what my code looks like:
val menu = nav_view.menu
menu.clear()
val selectedCatalogIsEmpty = selectedCatalogs.isEmpty()
for (catalog in catalogs){
val menuItem = menu.add(R.id.catalog_items, Menu.FIRST + catalog.catalogId, Menu.NONE, catalog.catalogName)
val switch = Switch(applicationContext)
menuItem.actionView = switch
if(selectedCatalogIsEmpty ||
selectedCatalogs.contains(catalog.catalogId) ) {
menuItem.isChecked = true
switch.isChecked = true
if(selectedCatalogIsEmpty){
selectedCatalogs.add(catalog.catalogId)
}
}
switch.setOnCheckedChangeListener { _, isChecked -> menuItem.isChecked = isChecked }
}
val menuItemSettings = menu.add(R.id.settings, Menu.NONE+ 5000, Menu.NONE, "Settings" )
Now, what i'd like to do is change the color of the thum when in the selected state. In order to achieve that, I've added the following to the styles.xml file:
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorPrimary</item>
<item name="colorControlNormal">#color/colorWhite</item>
<item name="colorControlActivated">#color/colorPrimary</item>
</style>
Unfortunately, I'm still getting the wrong color during runtime. Instead of the blue. I'm getting a greeny thumb:
It's clear that I've completely missed the point...I've run a couple of searches and people suggest using the SwitchCompat instead of the Switch. I've tried doing that, but I must also be missing something because I've ended up seing the text in really small caps (instead of the thumb I get with the Switch view).
Thanks.
Regards,
Luis
Ok, so after more than 3 hours, I've finally found my bug: I was using the applicationContext to initialize the Switch and application's theme isn't initialized: it's only used to apply a default theme for the remaining activities. So, updating the Switch instantiation to something like this solves the problem:
val switch = Switch(this#MainActivity) //kotlin ref to my activity
I need to force a repaint of the RatingBar control.
After many problems with the ratingbar and styles, I managed to get almost everything working.
I use it in a listview item. Due to how it works, one has to "fight" a little with its look and behavior. I ended up using a soluion I found on SO where one sets it to work as an indicator, but where oneself manually calculates what score the click on the rating bar corresponds to. The code always yield the correct result when steeping through the code, but the control painting of itself is wrong the first time. Here's my code in getView "part one":
final RatingBar rating = (RatingBar)view.findViewById(R.id.listitem_catalog_rating);
rating.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
float touchPositionX = event.getX();
float width = rating.getWidth();
float starsf = (touchPositionX / width);
starsf = starsf * param_final__data.score_max; // 5
int starsint = (int) starsf + param_final__data.score_min;
byte starsbyte = (byte) starsint;
param_final__data.score_cur = starsbyte;
starsf = starsint;
rating.setRating(starsf);
rating.setVisibility(View.INVISIBLE);
// force repaint and set visible - how?
}
else
if (event.getAction() == MotionEvent.ACTION_DOWN) {
param_final__view.setPressed(true);
}
else
if (event.getAction() == MotionEvent.ACTION_CANCEL) {
param_final__view.setPressed(false);
}
return true;
}
});
The problem is.When the ratingbar is initially shown, the first time when clicking anywhere on it will make it draw itself as if one always chosen the max score. However, if one hides the control and shows it again - it draws everything correctly. However, this is with "natural user interaction delay" - e.g. by clicking a button that switches the visibility state. If I try to force a repaint in code using invalidate or setvisibility instructions, nothing happens.
This is the code "part 2" where I initialize the ratingbar in getView when it is shown:
rating.setIsIndicator(true);
rating.setVisibility(View.VISIBLE);
rating.setStepSize(1);
//rating.setMax(data.score_max);
rating.setNumStars(data.score_max);
rating.setRating(data.score_cur);
And this is its XML:
<RatingBar
android:id="#+id/listitem_catalog_rating"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:numStars="1"
android:stepSize="1.0"
android:rating="1.0"
style="#style/MicRatingBar"
android:visibility="gone"
android:layout_marginTop="10dip"
android:layout_marginBottom="10dip"
/>
...
<style name="MicRatingBar" parent="#android:style/Widget.RatingBar">
<item name="android:progressDrawable">#drawable/micratingstars</item>
<item name="android:minHeight">34dip</item>
<item name="android:maxHeight">34dip</item>
</style>
...
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+android:id/background" android:drawable="#drawable/star_background" />
<item android:id="#+android:id/secondaryProgress" android:drawable="#drawable/star_secondaryprogress" />
<item android:id="#+android:id/progress" android:drawable="#drawable/star_progress" /> </layer-list>
For reference, these are some of the stackoverflows which got me as far as I am:
Capture RatingBar Click
Android RatingBar as Button when isIndicator = True
How to make a smaller RatingBar?
(but unfortunately does not solve my specific problem.)
I have tried lots of different combinations, and in my case, the code posted here is what got closest to the desired behavior and look. Just with the problem the score is drawn incorrectly on "first show".
I have tried to use e.g. invalidate, but I believe its internal flags makes it ignore he invalidate the request.
I think the problem lies in the order of these statements:
// Fine
rating.setIsIndicator(true);
// Fine
rating.setVisibility(View.VISIBLE);
// Problem - Although not documented, I think this should be
// called after `setNumStars(int)`
rating.setStepSize(1);
// Switch with the statement above
rating.setNumStars(data.score_max);
// Fine
rating.setRating(data.score_cur);
So, try this order:
rating.setIsIndicator(true);
rating.setVisibility(View.VISIBLE);
rating.setNumStars(data.score_max);
rating.setStepSize(1);
rating.setRating(data.score_cur);
As for // force repaint and set visible - how?, this shouldn't be required. setRating(float) should force an update. Just remove rating.setVisibility(View.INVISIBLE); from ACTION_UP part of your code. If the RatingBar still does not update, try rating.requestLayout(). But, please read further for a cleaner solution.
You said that int starsint = (int) starsf + param_final__data.score_min; is getting the correct value. And I am guessing that param_final__data.score_cur = starsbyte; updates your ListView data. Then why not just call notifyDataSetChanged(), and let the adapter update the view with the correct value? The change in the order of statements will be required though.
I've been trying to change the textcolor of my timepicker. But I can't find where the parent style is located. I've tried both
<style name="MyTimePicker" parent="#android:style/Widget.Holo.TimePicker">
<item name="android:textColor">#color/text</item>
</style>
and
<style name="MyTimePicker" parent="#android:style/Widget.TimePicker">
<item name="android:textColor">#color/text</item>
</style>
My minSdkVersion is 15. My targetSdkVersion is 20. I have rebuilded and cleaned my project.
I think I've been through every similar question on SO and none of them really have provided a solution for me. The only answer that might work is using some sort of library, but I'm not a big fan of that solution. Is the path to the parent something different from what I'm using, because I'm pretty sure I should be able to access it somehow?
Edit
This is how the theme is applied;
<TimePicker
style="#style/MyTimePicker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/timePicker"
android:layout_below="#+id/textView"
android:layout_centerHorizontal="true" />
On a note this is the error I receive (forgot to place it before):
Error:Error retrieving parent for item: No resource found that matches the given name '#android:style/Widget.Holo.TimePicker'.
Edit 2
A couple of the questions I've viewed to try to solve this:
How can I override TimePicker to change text color - I think this question gets as close to an answer, but I'm not entirely sure what I need to do? Do I need to import the android TimePicker style into my project?
https://stackoverflow.com/questions/24973586/set-textcolor-for-timepicker-in-customized-app-theme - No answer is given.
How can i change the textcolor of my timepicker and datepicker? - Tried the 0 votes answer, but it didn't work.
How to change the default color of DatePicker and TimePicker dialog in Android? - Again can't find the TimePicker in a similar way.
Android - How do I change the textColor in a TimePicker? - Again, can't find the actual TimePicker parent.
These are probably the best questions/answers to my problem, but none of them help me. It would be nice to get a definitive answer on this.
I have combined Paul Burke's Answer and Simon's Answer to succesfully edit the text colour of the TimePicker.
Here's how it is accomplished:
TimePicker time_picker; //Instantiated in onCreate()
Resources system;
private void set_timepicker_text_colour(){
system = Resources.getSystem();
int hour_numberpicker_id = system.getIdentifier("hour", "id", "android");
int minute_numberpicker_id = system.getIdentifier("minute", "id", "android");
int ampm_numberpicker_id = system.getIdentifier("amPm", "id", "android");
NumberPicker hour_numberpicker = (NumberPicker) time_picker.findViewById(hour_numberpicker_id);
NumberPicker minute_numberpicker = (NumberPicker) time_picker.findViewById(minute_numberpicker_id);
NumberPicker ampm_numberpicker = (NumberPicker) time_picker.findViewById(ampm_numberpicker_id);
set_numberpicker_text_colour(hour_numberpicker);
set_numberpicker_text_colour(minute_numberpicker);
set_numberpicker_text_colour(ampm_numberpicker);
}
private void set_numberpicker_text_colour(NumberPicker number_picker){
final int count = number_picker.getChildCount();
final int color = getResources().getColor(R.color.text);
for(int i = 0; i < count; i++){
View child = number_picker.getChildAt(i);
try{
Field wheelpaint_field = number_picker.getClass().getDeclaredField("mSelectorWheelPaint");
wheelpaint_field.setAccessible(true);
((Paint)wheelpaint_field.get(number_picker)).setColor(color);
((EditText)child).setTextColor(color);
number_picker.invalidate();
}
catch(NoSuchFieldException e){
Log.w("setNumberPickerTextColor", e);
}
catch(IllegalAccessException e){
Log.w("setNumberPickerTextColor", e);
}
catch(IllegalArgumentException e){
Log.w("setNumberPickerTextColor", e);
}
}
}
Please note that this answer might be outdated by now. I ran into this a while ago with something that might have been buggy (see my question for more details). Otherwise you should probably follow Vikram's answer.
Not sure why you would need to dive into Java Reflection API for this. Its a simple styling matter. The attribute that you need to override is: textColorPrimary.
<style name="AppTheme" parent="#android:style/Theme.Holo.Light">
....
<item name="android:textColorPrimary">#ff0000</item>
</style>
If you're using the TimePicker inside a Dialog, override android:textColorPrimary in the dialog's theme.
That's about it.
A TimePicker is really just two NumberPickers. Looking into the Widget.NumberPicker style and layout, you'll find the it uses
#style/TextAppearance.Large.Inverse.NumberPickerInputText
Unfortunately, TextAppearance.Large.Inverse.NumberPickerInputText doesn't use one of the attributes that you can set in your theme. So you have two options:
Copy the necessary classes to make your own version of NumberPicker and TimePicker. (You might be able to extract something from libraries like HoloEverywhere)
Use hacks.
If you want to go the second route, you can do this:
private int mNumberPickerInputId = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Resources system = Resources.getSystem();
// This is the internal id of the EditText used in NumberPicker (hack)
mNumberPickerInputId =
system.getIdentifier("numberpicker_input", "id", "android");
// just used for full example, use your TimePicker
TimePicker timePicker = new TimePicker(this);
setContentView(timePicker);
final int hourSpinnerId =
system.getIdentifier("hour", "id", "android");
View hourSpinner = timePicker.findViewById(hourSpinnerId);
if (hourSpinner != null) {
setNumberPickerTextColor(hourSpinner, Color.BLUE);
}
final int minSpinnerId =
system.getIdentifier("minute", "id", "android");
View minSpinner = timePicker.findViewById(minSpinnerId);
if (minSpinner != null) {
setNumberPickerTextColor(minSpinner, Color.BLUE);
}
final int amPmSpinnerId =
system.getIdentifier("amPm", "id", "android");
View amPmSpinner = timePicker.findViewById(amPmSpinnerId);
if (amPmSpinner != null) {
setNumberPickerTextColor(amPmSpinner, Color.BLUE);
}
}
private void setNumberPickerTextColor(View spinner, int color) {
TextView input = (TextView) spinner.findViewById(mNumberPickerInputId);
input.setTextColor(color);
}
EDIT
Upon further investigation, this hack doesn't really work well. It won't allow you to change the color of the NumberPicker above/below values. The color also resets after the use interacts with it. It seems that your only option will be to create your own copies of the necessary classes (option #1 above).
Adding to Vikram answer , Set the theme to timePicker do not set it as style
in styles.xml
<style name="timePickerOrange">
<item name="android:textColorPrimary">#color/orange</item> <!-- color for digits -->
<item name="android:textColor">#color/orange</item> <!-- color for colon -->
<item name="android:colorControlNormal">#color/orange</item> <!-- color for (horizontal) delimeters -->
</style>
and for timePicker
<TimePicker
android:theme="#style/timePickerOrange"
android:id="#+id/timePicker_defaultTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="#font/hero_regular"
android:timePickerMode="spinner"
app:fontFamily="#font/hero_regular" />
#Vikram is right
This is so simple.You can try this way
<style name="abirStyle" parent="#android:style/Theme.Holo.Light.Dialog.NoActionBar">
<item name="android:textColor">#color/colorPrimary</item>
<item name="android:background">#null</item>
<item name="android:textColorPrimary">#color/colorPrimary</item>
</style>
You can try to set android:textColorPrimaryInverse and android:textColorSecondaryInverse. That should do the job without using Reflection.
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:textColorPrimaryInverse">#android:color/black</item>
<item name="android:textColorSecondaryInverse">#color/colorLightGrey</item>
</style>
For those using the spinner, this is an easy way to change the colors and even provides the appropriate shadow effect of the previous and next numbers. Spinner requires a theme because most of the default xml attributes for colors only effect the clock mode.
styles.xml
<style name="timepicker">
<item name="android:textColorSecondary">#color/white </item>
<item name="android:textColorPrimary">#color/white </item>
</style>
xml layout file
<TimePicker
android:id="#+id/timePicker1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:theme="#style/timepicker"
android:layout_below="#id/line1"
android:layout_centerHorizontal="true"
android:padding="15dp"
android:timePickerMode="spinner" />
I want to inflate an item once and use it in a loop. I currently have a solution, but there is most likely a better way. Also, the program won't run unless there is view.removeView call, which makes sense, but seems hazardous if I ever want to add catBtn later in the app).
Existing code:
LinearLayout col1 = (LinearLayout)findViewById(R.id.col1);
for(int i = 0; i < 10; ++i) {
LinearLayout assets = (LinearLayout)this.getLayoutInflater().inflate(R.layout.assets, null);
Button btn = (Button)assets.findViewById(R.id.catBtn);//new Button(this);
assets.removeView(btn);
col1.addView(btn);
}
Existing layout.assets
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="1"
android:id="#+id/assets">
<ImageView android:focusable="true"
android:id="#+id/thumb"
android:background="#drawable/selectable"
android:layout_marginBottom="20dip"
android:src="#drawable/icon" android:layout_height="140dip" android:layout_width="250dip"/>
<Button android:id="#+id/catBtn"
android:layout_height="wrap_content"
android:background="#drawable/selectable"
android:text="Cat Button"
android:layout_width="120dip"
android:textSize="16dip"></Button>
</LinearLayout>
You could pass false as the last parameter to the inflate method
LayoutInflator.from(context).inflate(res, parent, false);
Which causes the inflated view to be attached to nothing. That way you don't have to remove anything. That gets rid of the assets.removeView() issue. But I think this still might be wasteful.
It looks like you just want some buttons:
<Button android:id="#+id/catBtn"
android:layout_height="wrap_content"
android:background="#drawable/selectable"
android:text="Cat Button"
android:layout_width="120dip"
android:textSize="16dip">
Let's extract that to a style:
<resources>
<declare-stylable android:name="awesome_button">
<attr android:name="awesomeButtonStyle" android:type="reference"/>
</declare-stylable>
<style android:name="AwesomeButton">
<item android:name="android:layout_height">wrap_content</item>
<item android:name="android:background">#drawable/selectable</item>
<item android:name="android:layout_width">120dp</item>
<item android:name="android:text">Cat Button</item>
<item android:name="android:textSize">16sp</item>
</style>
<style android:name="Theme.WithAwesomeButtons" parent="#android:style/Theme">
<item android:name="awesomeButtonStyle">#style/AwesomeButton</item>
</style>
<resources>
OK now we are rolling with style ;) (sorry couldn't resist). Now let's configure your Activity, inside the AndroidManifest.xml:
<activity android:name=".MyCatBtnActivity"
... Whatever else is in your activity
android:theme="#style/Theme.WithAwesomeButtons"/>
OK now within your loop:
for (int i=0; i<10; i++) {
// Let's get rid of the LayoutInflator (unless you want to use an xml layout
// in which case, make awesomeButton.xml and have it just have a button in it
// with attribute style="?awesomeButtonStyle").
Button button = new Button(this, null, R.attr.awesome_button.awesomeButtonStyle));
// Let's tag them with the integer counter so we can id them later
// You can set id, but there is a slight chance it will not be unique
// within the hierarchy. Later on you can either use col1.getChildView(index) to scan
// and look for these tags (or store them in a local array if col1 holds a lot of views)
// Then you can also evaluate the tag whenever you are referring to a button from
// within an OnClickListener or any View listener for that matter.
button.setTag(Integer.valueOf(i));
col1.add(button);
}
I think this is sort of what you are trying to achieve.
For instance, the default button has the following dependencies between its states and background images:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false" android:state_enabled="true"
android:drawable="#drawable/btn_default_normal" />
<item android:state_window_focused="false" android:state_enabled="false"
android:drawable="#drawable/btn_default_normal_disable" />
<item android:state_pressed="true"
android:drawable="#drawable/btn_default_pressed" />
<item android:state_focused="true" android:state_enabled="true"
android:drawable="#drawable/btn_default_selected" />
<item android:state_enabled="true"
android:drawable="#drawable/btn_default_normal" />
<item android:state_focused="true"
android:drawable="#drawable/btn_default_normal_disable_focused" />
<item
android:drawable="#drawable/btn_default_normal_disable" />
</selector>
How can I define my own custom state (smth like android:state_custom), so then I could use it to dynamically change my button visual appearance?
The solution indicated by #(Ted Hopp) works, but needs a little correction: in the selector, the item states need an "app:" prefix, otherwise the inflater won't recognise the namespace correctly, and will fail silently; at least this is what happens to me.
Allow me to report here the whole solution, with some more details:
First, create file "res/values/attrs.xml":
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="food">
<attr name="state_fried" format="boolean" />
<attr name="state_baked" format="boolean" />
</declare-styleable>
</resources>
Then define your custom class. For instance, it may be a class "FoodButton", derived from class "Button". You will have to implement a constructor; implement this one, which seems to be the one used by the inflater:
public FoodButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
On top of the derived class:
private static final int[] STATE_FRIED = {R.attr.state_fried};
private static final int[] STATE_BAKED = {R.attr.state_baked};
Also, your state variables:
private boolean mIsFried = false;
private boolean mIsBaked = false;
And a couple of setters:
public void setFried(boolean isFried) {mIsFried = isFried;}
public void setBaked(boolean isBaked) {mIsBaked = isBaked;}
Then override function "onCreateDrawableState":
#Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
if (mIsFried) {
mergeDrawableStates(drawableState, STATE_FRIED);
}
if (mIsBaked) {
mergeDrawableStates(drawableState, STATE_BAKED);
}
return drawableState;
}
Finally, the most delicate piece of this puzzle; the selector defining the StateListDrawable that you will use as a background for your widget. This is file "res/drawable/food_button.xml":
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.mydomain.mypackage">
<item
app:state_baked="true"
app:state_fried="false"
android:drawable="#drawable/item_baked" />
<item
app:state_baked="false"
app:state_fried="true"
android:drawable="#drawable/item_fried" />
<item
app:state_baked="true"
app:state_fried="true"
android:drawable="#drawable/item_overcooked" />
<item
app:state_baked="false"
app:state_fried="false"
android:drawable="#drawable/item_raw" />
</selector>
Notice the "app:" prefix, whereas with standard android states you would have used prefix "android:". The XML namespace is crucial for a correct interpretation by the inflater and depends on the type of project in which you are adding attributes. If it is an application, replace com.mydomain.mypackage with the actual package name of your application (application name excluded). If it is a library you must use "http://schemas.android.com/apk/res-auto" (and be using Tools R17 or later) or you will get runtime errors.
A couple of notes:
It seems you don't need to call the "refreshDrawableState" function, at least the solution works well as is, in my case
In order to use your custom class in a layout xml file, you will have to specify the fully qualified name (e.g. com.mydomain.mypackage.FoodButton)
You can as weel mix-up standard states (e.g. android:pressed, android:enabled, android:selected) with custom states, in order to represent more complicated state combinations
This thread shows how to add custom states to buttons and the like. (If you can't see the new Google groups in your browser, there's a copy of the thread here.)
Please do not forget to call refreshDrawableState within UI thread:
mHandler.post(new Runnable() {
#Override
public void run() {
refreshDrawableState();
}
});
It took lot of my time to figure out why my button is not changing its state even though everything looks right.