97

How to compare two drawables, I am doing like this but not having any success

public void MyClick(View view)
{
 Drawable fDraw = view.getBackground();
 Drawable sDraw = getResources().getDrawable(R.drawable.twt_hover);

  if(fDraw.equals(sDraw))
  {
   //Not coming
  }
}
Lalit Poptani
  • 67,150
  • 23
  • 161
  • 242
Roshan Jha
  • 2,091
  • 1
  • 21
  • 30
  • http://stackoverflow.com/questions/27463737/compatibility-of-getcontext-and-getresource-between-5-0-and-lower – user3201520 Feb 08 '15 at 04:47
  • If you are writing tests with Robolectric and the goal is to determine if the correct drawable was loaded, see this answer: https://stackoverflow.com/questions/18008044/assert-imageview-was-loaded-with-specific-drawable-resource-id – yuval Jan 28 '21 at 01:42

15 Answers15

150

Update https://stackoverflow.com/a/36373569/1835650

getConstantState() works not well

There is another way to compare:

mRememberPwd.getDrawable().getConstantState().equals
            (getResources().getDrawable(R.drawable.login_checked).getConstantState());

mRemeberPwd is an ImageView in this example. If you're using a TextView, use getBackground().getConstantState instead.

TeeTracker
  • 7,064
  • 8
  • 40
  • 46
Mejonzhan
  • 2,374
  • 1
  • 20
  • 30
44

Relying on getConstantState() alone can result in false negatives.

The approach I've taken is to try comparing the ConstantState in the first instance, but fall back on a Bitmap comparison if that check fails.

This should work in all cases (including images which aren't resources) but note that it is memory hungry.

public static boolean areDrawablesIdentical(Drawable drawableA, Drawable drawableB) {
    Drawable.ConstantState stateA = drawableA.getConstantState();
    Drawable.ConstantState stateB = drawableB.getConstantState();
    // If the constant state is identical, they are using the same drawable resource.
    // However, the opposite is not necessarily true.
    return (stateA != null && stateB != null && stateA.equals(stateB))
            || getBitmap(drawableA).sameAs(getBitmap(drawableB));
}

public static Bitmap getBitmap(Drawable drawable) {
    Bitmap result;
    if (drawable instanceof BitmapDrawable) {
        result = ((BitmapDrawable) drawable).getBitmap();
    } else {
        int width = drawable.getIntrinsicWidth();
        int height = drawable.getIntrinsicHeight();
        // Some drawables have no intrinsic width - e.g. solid colours.
        if (width <= 0) {
            width = 1;
        }
        if (height <= 0) {
            height = 1;
        }

        result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(result);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
    }
    return result;
}
vaughandroid
  • 4,315
  • 1
  • 27
  • 33
  • 1
    This is 100% true and needs more upvotes! People, please test your code with known Drawables before immediately relying on `getConstantState()` comparison – avalancha Nov 09 '15 at 11:00
  • Good findings! But according to the documentation of BitmapDrawable.getBitmap(), getBitmap() may be null so you might want to check this as well – PhilLab Jun 16 '17 at 16:25
  • This answer is true and I have checked that after debugging several hours with the coding of only getConstantState(). – Raghav Satyadev Jul 11 '17 at 09:37
  • To be safe, `setBounds` and `draw` on a copy instead of the original https://stackoverflow.com/a/25462223/1916449 – arekolek Mar 26 '18 at 21:00
  • This doesn't work with tinted BitmapDrawables (see setTintMode / setTint / setTintList). The bitmaps can be identical byte for byte but with different tint properties. Because the Android SDK does not provide any getters for the tint properties, there may not be a way to make it work with tinted drawables. – Theo Jan 30 '19 at 23:15
  • The docs say "Calling Drawable#mutate() on a Drawable should typically create a new ConstantState for that Drawable." In the non-typical cases, you will get a false positive. – John Glen Jun 06 '22 at 00:24
15

My question was for just comparing two drawables, I tried but could not get any method that directly compare two drawables,however for my solution i changed drawable to bitmap and then comparing two bitmaps and that is working.

Bitmap bitmap = ((BitmapDrawable)fDraw).getBitmap();
Bitmap bitmap2 = ((BitmapDrawable)sDraw).getBitmap();

if(bitmap == bitmap2)
    {
        //Code blcok
    }
MKJParekh
  • 34,073
  • 11
  • 87
  • 98
Roshan Jha
  • 2,091
  • 1
  • 21
  • 30
  • thats what I was suggesting you, to compare drawable according to type of drawable. – jeet Feb 03 '12 at 09:04
  • 1
    You might also want to compare bitmaps like this: http://stackoverflow.com/a/7696320/317889 – HGPB Apr 12 '13 at 15:05
  • 3
    This is very heavy, so consider recycling Bitmaps or you will end up in a OutOfMemoryError! – paulgavrikov Nov 29 '13 at 00:27
  • 2
    Why can you compare Bitmaps with pointer equality (==)? I'd expect Bitmap.equals() to be needed. – Ellen Spertus Feb 28 '14 at 22:55
  • @espertus You are right. I used same in question for drawable objects dont know why turned to == for bitmap object in answer. Any ways thanks for pointing out this basic. – Roshan Jha Mar 01 '14 at 05:30
  • @espertus Can you tell, which would be prefer here as both == and .equals working here. – Roshan Jha Mar 01 '14 at 05:41
  • @espertus Because == seems cheaper than .equals – Roshan Jha Mar 01 '14 at 06:00
  • == tests that the same object is returned; .equals() generally detects that the two values have the same hash code, although it is sometimes overridden in implementations. They may be equivalent in this case -- I don't know -- but they shouldn't be assumed to be in every case. In any case, I'm glad you found something that works for the case you need. – Ellen Spertus Mar 01 '14 at 17:10
10

for SDK 21+

this works in SDK -21

mRememberPwd.getDrawable().getConstantState().equals
        (getResources().getDrawable(R.drawable.login_checked).getConstantState())

for SDK +21 android 5. set drawable id to imageview with tag

img.setTag(R.drawable.xxx);

and compare like this

if ((Integer) img.getTag() == R.drawable.xxx)
{
....your code
}

this solution is for who want to compare drawable id of imageview with id of drawable.xxx.

tohid
  • 119
  • 1
  • 7
4

perhaps try it in this way:

public void MyClick(View view)
{
 Drawable fDraw = view.getBackground();
 Drawable sDraw = getResources().getDrawable(R.drawable.twt_hover);

  if(fDraw.hashCode() == sDraw.hashCode())
  {
   //Not coming
  }
}

or prepare a method which takes two drawable arguments and return boolean. In that method you may convert drawable into bytes and compare,

public boolean compareDrawable(Drawable d1, Drawable d2){
    try{
        Bitmap bitmap1 = ((BitmapDrawable)d1).getBitmap();
        ByteArrayOutputStream stream1 = new ByteArrayOutputStream();
        bitmap1.compress(Bitmap.CompressFormat.JPEG, 100, stream1);
        stream1.flush();
        byte[] bitmapdata1 = stream1.toByteArray();
        stream1.close();

        Bitmap bitmap2 = ((BitmapDrawable)d2).getBitmap();
        ByteArrayOutputStream stream2 = new ByteArrayOutputStream();
        bitmap2.compress(Bitmap.CompressFormat.JPEG, 100, stream2);
        stream2.flush();
        byte[] bitmapdata2 = stream2.toByteArray();
        stream2.close();

        return bitmapdata1.equals(bitmapdata2);
    }
    catch (Exception e) {
        // TODO: handle exception
    }
    return false;
}
waqaslam
  • 67,549
  • 16
  • 165
  • 178
  • check the updated answer. if still doesn't work then check your drawables. Or try passing same drawables to verify the functionality of code – waqaslam Feb 03 '12 at 08:18
  • yes, after not getting success I am thinking same for converting drawables to bitmap and then to byte,let me try that one,thank you for your efforts – Roshan Jha Feb 03 '12 at 08:33
  • not working,did you test it? might be there is something wrong,cant we directly compare two drawables? – Roshan Jha Feb 03 '12 at 08:47
  • did you try it passing same drawable for `e.g R.drawable.abc` as both parameters? – waqaslam Feb 03 '12 at 08:49
  • hello waqas,check your method once again and then tell again whether it is working or not,might be possible that I am doing something wrong but that did not work,but then it changes definition of my question that how to compare two drawables.Changing drawable to bitmap and then bytes will then compare bitmap and bytes thats not my requirement.if we check drawable methods then there is method .equals(object) so I thought it should work directly,but it did not.Well you can check my answer below I am converting drawable to bitmap and then comparing two bitmaps and it is working. – Roshan Jha Feb 03 '12 at 08:58
3

The solution for Android 5:

 if(image.getDrawable().getConstantState().equals(image.getContext().getDrawable(R.drawable.something).getConstantState()))
Tiago Santos
  • 489
  • 5
  • 16
3

getDrawable(int) is Now Deprecated. Use getDrawable(context,R.drawable.yourimageid)

To Compare Two Backgrounds

Boolean Condition1=v.getBackground().getConstantState().equals(
ContextCompat.getDrawable(getApplicationContext(),R.drawable.***).getConstantState());
RAJESH KUMAR ARUMUGAM
  • 1,560
  • 21
  • 35
  • 2
    This worked like a charm to fix an odd bug on Android 5. In my code, the actual drawable was returned with `context.getResources().getDrawable(R.drawable.***)` on Android 6+ but wasn't on Android 5. With this little change I can compare background drawables across all Android versions flawlessly – Jose_GD Dec 10 '18 at 15:44
3

Ok, I think I've found the ultimate solution for this. Because of AppCompat and friends, the drawable provided is sometimes inflated in different forms so it's not enough to do getResources().getBitmap(R.drawable.my_awesome_drawable).

So, in order to get a drawable instance of the same type and form as provided by the view one can do this:

public static Drawable drawableFrom(View view, @DrawableRes int drawableId) {
    Context context = view.getContext();
    try {
        View dummyView = view.getClass().getConstructor(Context.class).newInstance(context);
        dummyView.setBackgroundResource(drawableId);
        return dummyView.getBackground();
    } catch (Exception e) {
      return ResourcesCompat.getDrawable(context.getResources(), drawableId, null);
    }
}

This is useful when doing tests. However, I would not recommend doing this in production. If you need to, extra caching would be desirable to avoid doing too much reflection.

For Expresso tests you can use this quite nicely:

onView(withDrawable(R.drawable.awesome_drawable))
  .check(matches(isDisplayed()));

or

onView(withId(R.id.view_id))
  .check(matches(withDrawable(R.drawable.awesome_drawable)));

Before you'll have to declare this helper class:

public class CustomMatchers {

  public static Matcher<View> withDrawable(@DrawableRes final int drawableId) {
     return new DrawableViewMatcher(drawableId);
  }
  private static class DrawableViewMatcher extends TypeSafeMatcher<View> {

     private final int expectedId;
     private String resourceName;

     private enum DrawableExtractionPolicy {
        IMAGE_VIEW {
          @Override
          Drawable findDrawable(View view) {
             return view instanceof ImageView ? ((ImageView) view).getDrawable() : null;
          }
        },
        TEXT_VIEW_COMPOUND {
          @Override
          Drawable findDrawable(View view) {
             return view instanceof TextView ? findFirstCompoundDrawable((TextView) view) : null;
          }
        },
        BACKGROUND {
          @Override
          Drawable findDrawable(View view) {
             return view.getBackground();
          }
        };

        @Nullable
        private static Drawable findFirstCompoundDrawable(TextView view) {
          for (Drawable drawable : view.getCompoundDrawables()) {
             if (drawable != null) {
                return drawable;
             }
          }
          return null;
        }

        abstract Drawable findDrawable(View view);

     }

     private DrawableViewMatcher(@DrawableRes int expectedId) {
        this.expectedId = expectedId;
     }

     @Override
     protected boolean matchesSafely(View view) {
        resourceName = resources(view).getResourceName(expectedId);
        return haveSameState(actualDrawable(view), expectedDrawable(view));
     }

     private boolean haveSameState(Drawable actual, Drawable expected) {
        return actual != null && expected != null && areEqual(expected.getConstantState(), actual.getConstantState());
     }

     private Drawable actualDrawable(View view) {
        for (DrawableExtractionPolicy policy : DrawableExtractionPolicy.values()) {
          Drawable drawable = policy.findDrawable(view);
          if (drawable != null) {
             return drawable;
          }
        }
        return null;
     }

     private boolean areEqual(Object first, Object second) {
        return first == null ? second == null : first.equals(second);
     }

     private Drawable expectedDrawable(View view) {
        return drawableFrom(view, expectedId);
     }

     private static Drawable drawableFrom(View view, @DrawableRes int drawableId) {
        Context context = view.getContext();
        try {
          View dummyView = view.getClass().getConstructor(Context.class).newInstance(context);
          dummyView.setBackgroundResource(drawableId);
          return dummyView.getBackground();
        } catch (Exception e) {
          return ResourcesCompat.getDrawable(context.getResources(), drawableId, null);
        }
     }

     @NonNull
     private Resources resources(View view) {
        return view.getContext().getResources();
     }

     @Override
     public void describeTo(Description description) {
        description.appendText("with drawable from resource id: ");
        description.appendValue(expectedId);
        if (resourceName != null) {
          description.appendValueList("[", "", "]", resourceName);
        }
     }
  }

}
pablisco
  • 14,027
  • 4
  • 48
  • 70
3

Compare 2 drawable:

drawable1.constantState == drawable2.constantState
            || drawable1.toBitmap().sameAs(drawable2.toBitmap())

If you can not find Drawable.toBitmap(...) here is it Drawable.kt

Benny
  • 2,233
  • 1
  • 22
  • 27
  • sdk 31, im using this `toBitmap().sameAs` to compare checkbox checked or unchecked based on two drawables.. – Nurkartiko Dec 05 '21 at 03:37
2

Use getTag() and setTag() for comparison

0

I already answered on the similar topic here: Get the ID of a drawable in ImageView. The approach is based on tagging a view with a specified resource id in the custom LayoutInflater. Whole process is automated by a simple library TagView.

As a result, you can compare two drawables just by their ids:

TagViewUtils.getTag(view, ViewTag.VIEW_BACKGROUND.id) == R.drawable.twt_hover
Bogdan Kornev
  • 101
  • 1
  • 5
0

Expanding on the answer from @vaughandroid the following Matcher works for a Vector Drawable that is tinted. You have to provide the tint that was used for the Drawable.

public static Matcher<View> compareVectorDrawables(final int imageId, final int tintId) {
        return new TypeSafeMatcher<View>() {

        @Override
        protected boolean matchesSafely(View target) {
            if (!(target instanceof ImageView)) {
                return false;
            }
            ImageView imageView = (ImageView) target;
            if (imageId < 0) {
                return imageView.getDrawable() == null;
            }
            Resources resources = target.getContext().getResources();
            Drawable expectedDrawable = resources.getDrawable(imageId, null);
            if (expectedDrawable == null) {
                return false;
            }

            Drawable imageDrawable = imageView.getDrawable();
            ColorFilter imageColorFilter = imageDrawable.getColorFilter();

            expectedDrawable.setColorFilter(imageColorFilter);
            expectedDrawable.setTintList(target.getResources()
                    .getColorStateList(tintId, null));

            boolean areSame = areDrawablesIdentical(imageDrawable, expectedDrawable);
            return areSame;
        }

        public boolean areDrawablesIdentical(Drawable drawableA, Drawable drawableB) {
            Drawable.ConstantState stateA = drawableA.getConstantState();
            Drawable.ConstantState stateB = drawableB.getConstantState();
            // If the constant state is identical, they are using the same drawable resource.
            // However, the opposite is not necessarily true.
            return (stateA != null && stateB != null && stateA.equals(stateB))
                    || getBitmap(drawableA).sameAs(getBitmap(drawableB));
        }

        public Bitmap getBitmap(Drawable drawable) {
            Bitmap result;
            if (drawable instanceof BitmapDrawable) {
                result = ((BitmapDrawable) drawable).getBitmap();
            } else {
                int width = drawable.getIntrinsicWidth();
                int height = drawable.getIntrinsicHeight();
                // Some drawables have no intrinsic width - e.g. solid colours.
                if (width <= 0) {
                    width = 1;
                }
                if (height <= 0) {
                    height = 1;
                }

                result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                Canvas canvas = new Canvas(result);
                drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
                drawable.draw(canvas);
            }
            return result;
        }

        @Override
        public void describeTo(Description description) {

        }
    };
}
0

a usefull method collected from GitHub copilot:

fun compareTwoDrawable(drawable1: Drawable, drawable2: Drawable): Boolean {
   val bitmap1 = (drawable1 as BitmapDrawable).bitmap
   val bitmap2 = (drawable2 as BitmapDrawable).bitmap
   return bitmap1.sameAs(bitmap2)
}
-1

if You want to directly compare two drawable then use following code

Drawable fDraw = getResources().getDrawable(R.drawable.twt_hover);

Drawable sDraw = getResources().getDrawable(R.drawable.twt_hover);

if (fDraw.getConstantState().equals(sDraw.getConstantState())) {
    //write your code.
} else {
    //write your code.
}
Addon.mahesh
  • 107
  • 5
-2

When you are using equals() method it is used to compare the contents. you should try == for comparing two objects.

public void MyClick(View view)
{
 Drawable fDraw = view.getBackground();
 Drawable sDraw = getResources().getDrawable(R.drawable.twt_hover);

  if( fDraw == sDraw )
  {
   // Coming
  }
}
Lucifer
  • 29,392
  • 25
  • 90
  • 143