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 static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import android.content.res.ColorStateList;
     22 import android.graphics.PorterDuff;
     23 import android.graphics.drawable.Drawable;
     24 import android.os.Build;
     25 import android.util.AttributeSet;
     26 import android.widget.ImageView;
     27 
     28 import androidx.annotation.NonNull;
     29 import androidx.annotation.RestrictTo;
     30 import androidx.appcompat.R;
     31 import androidx.appcompat.content.res.AppCompatResources;
     32 import androidx.core.widget.ImageViewCompat;
     33 
     34 /**
     35  * @hide
     36  */
     37 @RestrictTo(LIBRARY_GROUP)
     38 public class AppCompatImageHelper {
     39     private final ImageView mView;
     40 
     41     private TintInfo mInternalImageTint;
     42     private TintInfo mImageTint;
     43     private TintInfo mTmpInfo;
     44 
     45     public AppCompatImageHelper(ImageView view) {
     46         mView = view;
     47     }
     48 
     49     public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
     50         TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
     51                 R.styleable.AppCompatImageView, defStyleAttr, 0);
     52         try {
     53             Drawable drawable = mView.getDrawable();
     54             if (drawable == null) {
     55                 // If the view doesn't already have a drawable (from android:src), try loading
     56                 // it from srcCompat
     57                 final int id = a.getResourceId(R.styleable.AppCompatImageView_srcCompat, -1);
     58                 if (id != -1) {
     59                     drawable = AppCompatResources.getDrawable(mView.getContext(), id);
     60                     if (drawable != null) {
     61                         mView.setImageDrawable(drawable);
     62                     }
     63                 }
     64             }
     65 
     66             if (drawable != null) {
     67                 DrawableUtils.fixDrawable(drawable);
     68             }
     69 
     70             if (a.hasValue(R.styleable.AppCompatImageView_tint)) {
     71                 ImageViewCompat.setImageTintList(mView,
     72                         a.getColorStateList(R.styleable.AppCompatImageView_tint));
     73             }
     74             if (a.hasValue(R.styleable.AppCompatImageView_tintMode)) {
     75                 ImageViewCompat.setImageTintMode(mView,
     76                         DrawableUtils.parseTintMode(
     77                                 a.getInt(R.styleable.AppCompatImageView_tintMode, -1), null));
     78             }
     79         } finally {
     80             a.recycle();
     81         }
     82     }
     83 
     84     public void setImageResource(int resId) {
     85         if (resId != 0) {
     86             final Drawable d = AppCompatResources.getDrawable(mView.getContext(), resId);
     87             if (d != null) {
     88                 DrawableUtils.fixDrawable(d);
     89             }
     90             mView.setImageDrawable(d);
     91         } else {
     92             mView.setImageDrawable(null);
     93         }
     94 
     95         applySupportImageTint();
     96     }
     97 
     98     boolean hasOverlappingRendering() {
     99         final Drawable background = mView.getBackground();
    100         if (Build.VERSION.SDK_INT >= 21
    101                 && background instanceof android.graphics.drawable.RippleDrawable) {
    102             // RippleDrawable has an issue on L+ when used with an alpha animation.
    103             // This workaround should be disabled when the platform bug is fixed. See b/27715789
    104             return false;
    105         }
    106         return true;
    107     }
    108 
    109     void setSupportImageTintList(ColorStateList tint) {
    110         if (mImageTint == null) {
    111             mImageTint = new TintInfo();
    112         }
    113         mImageTint.mTintList = tint;
    114         mImageTint.mHasTintList = true;
    115         applySupportImageTint();
    116     }
    117 
    118     ColorStateList getSupportImageTintList() {
    119         return mImageTint != null ? mImageTint.mTintList : null;
    120     }
    121 
    122     void setSupportImageTintMode(PorterDuff.Mode tintMode) {
    123         if (mImageTint == null) {
    124             mImageTint = new TintInfo();
    125         }
    126         mImageTint.mTintMode = tintMode;
    127         mImageTint.mHasTintMode = true;
    128 
    129         applySupportImageTint();
    130     }
    131 
    132     PorterDuff.Mode getSupportImageTintMode() {
    133         return mImageTint != null ? mImageTint.mTintMode : null;
    134     }
    135 
    136     void applySupportImageTint() {
    137         final Drawable imageViewDrawable = mView.getDrawable();
    138         if (imageViewDrawable != null) {
    139             DrawableUtils.fixDrawable(imageViewDrawable);
    140         }
    141 
    142         if (imageViewDrawable != null) {
    143             if (shouldApplyFrameworkTintUsingColorFilter()
    144                     && applyFrameworkTintUsingColorFilter(imageViewDrawable)) {
    145                 // This needs to be called before the internal tints below so it takes
    146                 // effect on any widgets using the compat tint on API 21
    147                 return;
    148             }
    149 
    150             if (mImageTint != null) {
    151                 AppCompatDrawableManager.tintDrawable(imageViewDrawable, mImageTint,
    152                         mView.getDrawableState());
    153             } else if (mInternalImageTint != null) {
    154                 AppCompatDrawableManager.tintDrawable(imageViewDrawable, mInternalImageTint,
    155                         mView.getDrawableState());
    156             }
    157         }
    158     }
    159 
    160     void setInternalImageTint(ColorStateList tint) {
    161         if (tint != null) {
    162             if (mInternalImageTint == null) {
    163                 mInternalImageTint = new TintInfo();
    164             }
    165             mInternalImageTint.mTintList = tint;
    166             mInternalImageTint.mHasTintList = true;
    167         } else {
    168             mInternalImageTint = null;
    169         }
    170         applySupportImageTint();
    171     }
    172 
    173     private boolean shouldApplyFrameworkTintUsingColorFilter() {
    174         final int sdk = Build.VERSION.SDK_INT;
    175         if (sdk > 21) {
    176             // On API 22+, if we're using an internal compat image source tint, we're also
    177             // responsible for applying any custom tint set via the framework impl
    178             return mInternalImageTint != null;
    179         } else if (sdk == 21) {
    180             // GradientDrawable doesn't implement setTintList on API 21, and since there is
    181             // no nice way to unwrap DrawableContainers we have to blanket apply this
    182             // on API 21
    183             return true;
    184         } else {
    185             // API 19 and below doesn't have framework tint
    186             return false;
    187         }
    188     }
    189 
    190     /**
    191      * Applies the framework image source tint to a view, but using the compat method (ColorFilter)
    192      *
    193      * @return true if a tint was applied
    194      */
    195     private boolean applyFrameworkTintUsingColorFilter(@NonNull Drawable imageSource) {
    196         if (mTmpInfo == null) {
    197             mTmpInfo = new TintInfo();
    198         }
    199         final TintInfo info = mTmpInfo;
    200         info.clear();
    201 
    202         final ColorStateList tintList = ImageViewCompat.getImageTintList(mView);
    203         if (tintList != null) {
    204             info.mHasTintList = true;
    205             info.mTintList = tintList;
    206         }
    207         final PorterDuff.Mode mode = ImageViewCompat.getImageTintMode(mView);
    208         if (mode != null) {
    209             info.mHasTintMode = true;
    210             info.mTintMode = mode;
    211         }
    212 
    213         if (info.mHasTintList || info.mHasTintMode) {
    214             AppCompatDrawableManager.tintDrawable(imageSource, info, mView.getDrawableState());
    215             return true;
    216         }
    217 
    218         return false;
    219     }
    220 }
    221