Home | History | Annotate | Download | only in widget
      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.graphics.PorterDuff;
     21 import android.graphics.drawable.Drawable;
     22 import android.os.Build;
     23 import android.util.AttributeSet;
     24 import android.view.View;
     25 
     26 import androidx.annotation.NonNull;
     27 import androidx.appcompat.R;
     28 import androidx.core.view.ViewCompat;
     29 
     30 class AppCompatBackgroundHelper {
     31 
     32     private final View mView;
     33     private final AppCompatDrawableManager mDrawableManager;
     34 
     35     private int mBackgroundResId = -1;
     36 
     37     private TintInfo mInternalBackgroundTint;
     38     private TintInfo mBackgroundTint;
     39     private TintInfo mTmpInfo;
     40 
     41     AppCompatBackgroundHelper(View view) {
     42         mView = view;
     43         mDrawableManager = AppCompatDrawableManager.get();
     44     }
     45 
     46     void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
     47         TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
     48                 R.styleable.ViewBackgroundHelper, defStyleAttr, 0);
     49         try {
     50             if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) {
     51                 mBackgroundResId = a.getResourceId(
     52                         R.styleable.ViewBackgroundHelper_android_background, -1);
     53                 ColorStateList tint = mDrawableManager
     54                         .getTintList(mView.getContext(), mBackgroundResId);
     55                 if (tint != null) {
     56                     setInternalBackgroundTint(tint);
     57                 }
     58             }
     59             if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTint)) {
     60                 ViewCompat.setBackgroundTintList(mView,
     61                         a.getColorStateList(R.styleable.ViewBackgroundHelper_backgroundTint));
     62             }
     63             if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTintMode)) {
     64                 ViewCompat.setBackgroundTintMode(mView,
     65                         DrawableUtils.parseTintMode(
     66                                 a.getInt(R.styleable.ViewBackgroundHelper_backgroundTintMode, -1),
     67                                 null));
     68             }
     69         } finally {
     70             a.recycle();
     71         }
     72     }
     73 
     74     void onSetBackgroundResource(int resId) {
     75         mBackgroundResId = resId;
     76         // Update the default background tint
     77         setInternalBackgroundTint(mDrawableManager != null
     78                 ? mDrawableManager.getTintList(mView.getContext(), resId)
     79                 : null);
     80         applySupportBackgroundTint();
     81     }
     82 
     83     void onSetBackgroundDrawable(Drawable background) {
     84         mBackgroundResId = -1;
     85         // We don't know that this drawable is, so we need to clear the default background tint
     86         setInternalBackgroundTint(null);
     87         applySupportBackgroundTint();
     88     }
     89 
     90     void setSupportBackgroundTintList(ColorStateList tint) {
     91         if (mBackgroundTint == null) {
     92             mBackgroundTint = new TintInfo();
     93         }
     94         mBackgroundTint.mTintList = tint;
     95         mBackgroundTint.mHasTintList = true;
     96         applySupportBackgroundTint();
     97     }
     98 
     99     ColorStateList getSupportBackgroundTintList() {
    100         return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
    101     }
    102 
    103     void setSupportBackgroundTintMode(PorterDuff.Mode tintMode) {
    104         if (mBackgroundTint == null) {
    105             mBackgroundTint = new TintInfo();
    106         }
    107         mBackgroundTint.mTintMode = tintMode;
    108         mBackgroundTint.mHasTintMode = true;
    109 
    110         applySupportBackgroundTint();
    111     }
    112 
    113     PorterDuff.Mode getSupportBackgroundTintMode() {
    114         return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
    115     }
    116 
    117     void applySupportBackgroundTint() {
    118         final Drawable background = mView.getBackground();
    119         if (background != null) {
    120             if (shouldApplyFrameworkTintUsingColorFilter()
    121                     && applyFrameworkTintUsingColorFilter(background)) {
    122                 // This needs to be called before the internal tints below so it takes
    123                 // effect on any widgets using the compat tint on API 21 (EditText)
    124                 return;
    125             }
    126 
    127             if (mBackgroundTint != null) {
    128                 AppCompatDrawableManager.tintDrawable(background, mBackgroundTint,
    129                         mView.getDrawableState());
    130             } else if (mInternalBackgroundTint != null) {
    131                 AppCompatDrawableManager.tintDrawable(background, mInternalBackgroundTint,
    132                         mView.getDrawableState());
    133             }
    134         }
    135     }
    136 
    137     void setInternalBackgroundTint(ColorStateList tint) {
    138         if (tint != null) {
    139             if (mInternalBackgroundTint == null) {
    140                 mInternalBackgroundTint = new TintInfo();
    141             }
    142             mInternalBackgroundTint.mTintList = tint;
    143             mInternalBackgroundTint.mHasTintList = true;
    144         } else {
    145             mInternalBackgroundTint = null;
    146         }
    147         applySupportBackgroundTint();
    148     }
    149 
    150     private boolean shouldApplyFrameworkTintUsingColorFilter() {
    151         final int sdk = Build.VERSION.SDK_INT;
    152         if (sdk > 21) {
    153             // On API 22+, if we're using an internal compat background tint, we're also
    154             // responsible for applying any custom tint set via the framework impl
    155             return mInternalBackgroundTint != null;
    156         } else if (sdk == 21) {
    157             // GradientDrawable doesn't implement setTintList on API 21, and since there is
    158             // no nice way to unwrap DrawableContainers we have to blanket apply this
    159             // on API 21
    160             return true;
    161         } else {
    162             // API 19 and below doesn't have framework tint
    163             return false;
    164         }
    165     }
    166 
    167     /**
    168      * Applies the framework background tint to a view, but using the compat method (ColorFilter)
    169      *
    170      * @return true if a tint was applied
    171      */
    172     private boolean applyFrameworkTintUsingColorFilter(@NonNull Drawable background) {
    173         if (mTmpInfo == null) {
    174             mTmpInfo = new TintInfo();
    175         }
    176         final TintInfo info = mTmpInfo;
    177         info.clear();
    178 
    179         final ColorStateList tintList = ViewCompat.getBackgroundTintList(mView);
    180         if (tintList != null) {
    181             info.mHasTintList = true;
    182             info.mTintList = tintList;
    183         }
    184         final PorterDuff.Mode mode = ViewCompat.getBackgroundTintMode(mView);
    185         if (mode != null) {
    186             info.mHasTintMode = true;
    187             info.mTintMode = mode;
    188         }
    189 
    190         if (info.mHasTintList || info.mHasTintMode) {
    191             AppCompatDrawableManager.tintDrawable(background, info, mView.getDrawableState());
    192             return true;
    193         }
    194 
    195         return false;
    196     }
    197 }
    198