1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.appcompat.widget;
18 
19 import android.content.res.ColorStateList;
20 import android.content.res.Resources;
21 import android.graphics.PorterDuff;
22 import android.graphics.drawable.Drawable;
23 import android.util.AttributeSet;
24 import android.widget.CompoundButton;
25 
26 import androidx.appcompat.R;
27 import androidx.appcompat.content.res.AppCompatResources;
28 import androidx.core.graphics.drawable.DrawableCompat;
29 import androidx.core.view.ViewCompat;
30 import androidx.core.widget.CompoundButtonCompat;
31 
32 import org.jspecify.annotations.NonNull;
33 import org.jspecify.annotations.Nullable;
34 
35 class AppCompatCompoundButtonHelper {
36     private final @NonNull CompoundButton mView;
37 
38     private ColorStateList mButtonTintList = null;
39     private PorterDuff.Mode mButtonTintMode = null;
40     private boolean mHasButtonTint = false;
41     private boolean mHasButtonTintMode = false;
42 
43     private boolean mSkipNextApply;
44 
AppCompatCompoundButtonHelper(@onNull CompoundButton view)45     AppCompatCompoundButtonHelper(@NonNull CompoundButton view) {
46         mView = view;
47     }
48 
loadFromAttributes(@ullable AttributeSet attrs, int defStyleAttr)49     void loadFromAttributes(@Nullable AttributeSet attrs, int defStyleAttr) {
50         TintTypedArray a =
51                 TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
52                         R.styleable.CompoundButton, defStyleAttr, 0);
53         ViewCompat.saveAttributeDataForStyleable(mView, mView.getContext(),
54                 R.styleable.CompoundButton, attrs, a.getWrappedTypeArray(), defStyleAttr, 0);
55         try {
56             boolean buttonDrawableLoaded = false;
57             if (a.hasValue(R.styleable.CompoundButton_buttonCompat)) {
58                 final int resourceId = a.getResourceId(R.styleable.CompoundButton_buttonCompat, 0);
59                 if (resourceId != 0) {
60                     try {
61                         mView.setButtonDrawable(
62                                 AppCompatResources.getDrawable(mView.getContext(), resourceId));
63                         buttonDrawableLoaded = true;
64                     } catch (Resources.NotFoundException nfe) {
65                         // Animated buttonCompat relies on AAPT2 features. If not found then swallow
66                         // this error and fall back to the regular drawable.
67                     }
68                 }
69             }
70             if (!buttonDrawableLoaded && a.hasValue(R.styleable.CompoundButton_android_button)) {
71                 final int resourceId = a.getResourceId(
72                         R.styleable.CompoundButton_android_button, 0);
73                 if (resourceId != 0) {
74                     mView.setButtonDrawable(
75                             AppCompatResources.getDrawable(mView.getContext(), resourceId));
76                 }
77             }
78             if (a.hasValue(R.styleable.CompoundButton_buttonTint)) {
79                 CompoundButtonCompat.setButtonTintList(mView,
80                         a.getColorStateList(R.styleable.CompoundButton_buttonTint));
81             }
82             if (a.hasValue(R.styleable.CompoundButton_buttonTintMode)) {
83                 CompoundButtonCompat.setButtonTintMode(mView,
84                         DrawableUtils.parseTintMode(
85                                 a.getInt(R.styleable.CompoundButton_buttonTintMode, -1),
86                                 null));
87             }
88         } finally {
89             a.recycle();
90         }
91     }
92 
setSupportButtonTintList(ColorStateList tint)93     void setSupportButtonTintList(ColorStateList tint) {
94         mButtonTintList = tint;
95         mHasButtonTint = true;
96 
97         applyButtonTint();
98     }
99 
getSupportButtonTintList()100     ColorStateList getSupportButtonTintList() {
101         return mButtonTintList;
102     }
103 
setSupportButtonTintMode(PorterDuff.@ullable Mode tintMode)104     void setSupportButtonTintMode(PorterDuff.@Nullable Mode tintMode) {
105         mButtonTintMode = tintMode;
106         mHasButtonTintMode = true;
107 
108         applyButtonTint();
109     }
110 
getSupportButtonTintMode()111     PorterDuff.Mode getSupportButtonTintMode() {
112         return mButtonTintMode;
113     }
114 
onSetButtonDrawable()115     void onSetButtonDrawable() {
116         if (mSkipNextApply) {
117             mSkipNextApply = false;
118             return;
119         }
120 
121         mSkipNextApply = true;
122         applyButtonTint();
123     }
124 
applyButtonTint()125     void applyButtonTint() {
126         Drawable buttonDrawable = CompoundButtonCompat.getButtonDrawable(mView);
127 
128         if (buttonDrawable != null && (mHasButtonTint || mHasButtonTintMode)) {
129             buttonDrawable = DrawableCompat.wrap(buttonDrawable);
130             buttonDrawable = buttonDrawable.mutate();
131             if (mHasButtonTint) {
132                 DrawableCompat.setTintList(buttonDrawable, mButtonTintList);
133             }
134             if (mHasButtonTintMode) {
135                 DrawableCompat.setTintMode(buttonDrawable, mButtonTintMode);
136             }
137             // The drawable (or one of its children) may not have been
138             // stateful before applying the tint, so let's try again.
139             if (buttonDrawable.isStateful()) {
140                 buttonDrawable.setState(mView.getDrawableState());
141             }
142             mView.setButtonDrawable(buttonDrawable);
143         }
144     }
145 
146 }
147