title: How to optimize a custom ImageView to be used in a GridView?
body: <p>I have developed this custom <code>ImageView</code> class to override some of the default behavior to fit my needs. Let me describe what this custom <code>ImageView</code> does...</p>
  
  <p>Let's say you have a bunch of icons to display in <code>GridView</code> both in the <code>drawable-mdpi</code> and <code>drawable-hdpi</code> folder, they are 48x48px and 72x72px in size, respectively. There are no icons available in the <code>drawable-xhdpi</code> folder. The <code>GridView</code> attributes are so that all the icons size will be in 48x48dp (this will translate to 48px, 72px and 96px for mpdi, hdpi and xhdpi densities, respectively).</p>
  
  <p>Since there are no icons in the <code>drawable-xhdpi</code> folder, when this app is ran on a device with such density, the icons will be pulled from the <code>drawable-hdpi</code> folder. <strong>And since they are only 72px and the xhdpi devices are expecting 96px images, the icons will be stretched to fill the remaining pixels.</strong></p>
  
  <p>This is the behavior my custom <code>ImageView</code> attempts to override. With my custom component, what will happen is that the images will simply not get stretched. For instance, in the example above using my class, each <code>ImageView</code> inside the <code>GridView</code> will still be 96x96px (because of the 48x48dp size defined) but the images used are from the <code>drawable-hdpi</code> folder which are 72x72px. <strong>What will happen is that these images from the <code>drawable-hdpi</code> folder will be placed in the center of the <code>ImageView</code> which is 96x96px in size without stretching the image to fit the whole view size.</strong></p>
  
  <p>If the above is confusing, let's try with a few pictures. The example below does not use <code>GridView</code>, I'm trying to simplify the idea behind my custom class. These are the source pictures I'm using for this example:</p>
  
  <p><img src="https://i.stack.imgur.com/ifSNH.png" alt="source-files"></p>
  
  <p>This is the result on <strong>HDPI</strong> device:</p>
  
  <p><img src="https://i.stack.imgur.com/4E7JD.png" alt="hdpi-demo"></p>
  
  <p>And this is the result on <strong>XHDPI</strong> device:</p>
  
  <p><img src="https://i.stack.imgur.com/CszZX.png" alt="xhdpi-demo"></p>
  
  <p>The code for the layout on the screenshots above is this:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_margin="10dp"
      android:orientation="vertical"&gt;
  
      &lt;TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Standard ImageView:"
          android:textAppearance="?android:attr/textAppearanceLarge"/&gt;
  
      &lt;ImageView
          android:layout_width="48dp"
          android:layout_height="48dp"
          android:layout_margin="10dp"
          android:scaleType="center"
          android:background="#FFEEEE"
          android:src="@drawable/ic_female"/&gt;
      &lt;ImageView
          android:layout_width="48dp"
          android:layout_height="48dp"
          android:layout_margin="10dp"
          android:scaleType="center"
          android:background="#FFEEEE"
          android:src="@drawable/ic_male"/&gt;
  
      &lt;TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Custom UnscaledImageView:"
          android:textAppearance="?android:attr/textAppearanceLarge"/&gt;
  
      &lt;com.sampleapp.widget.UnscaledImageView
          android:layout_width="48dp"
          android:layout_height="48dp"
          android:layout_margin="10dp"
          android:scaleType="center"
          android:background="#FFEEEE"
          android:src="@drawable/ic_female"/&gt;
      &lt;com.sampleapp.widget.UnscaledImageView
          android:layout_width="48dp"
          android:layout_height="48dp"
          android:layout_margin="10dp"
          android:scaleType="center"
          android:background="#FFEEEE"
          android:src="@drawable/ic_male"/&gt;
  
  &lt;/LinearLayout&gt;
  </code></pre>
  
  <p>Is it more clear now? This is what I want to do and this is working nicely, besides a small performance issue... Now let me post the code I'm using for such custom component:</p>
  
  <p><strong>attrs.xml:</strong></p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;resources&gt;
  
      &lt;declare-styleable name="UnscaledImageView"&gt;
          &lt;attr name="android:src" /&gt;
      &lt;/declare-styleable&gt;
  
  &lt;/resources&gt;
  </code></pre>
  
  <p><strong>UnscaledImageView.java:</strong></p>
  
  <pre><code>public class UnscaledImageView extends ImageView {
  
      private int mDeviceDensityDpi;
  
      public UnscaledImageView(Context context) {
          super(context);
  
          mDeviceDensityDpi = getResources().getDisplayMetrics().densityDpi;
      }
  
      public UnscaledImageView(Context context, AttributeSet attrs) {
          super(context, attrs);
  
          mDeviceDensityDpi = getResources().getDisplayMetrics().densityDpi;
  
          TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.UnscaledImageView);
          int resourceId = styledAttrs.getResourceId(R.styleable.UnscaledImageView_android_src, 0);
  
          if(resourceId != 0) {
              setUnscaledImageResource(resourceId);
          }
  
          styledAttrs.recycle();
      }
  
      public void setUnscaledImageResource(int resId) {
          setImageBitmap(decodeBitmapResource(resId));
      }
  
      @SuppressWarnings("deprecation")
      public void setUnscaledBackgroundResource(int resId) {
          BitmapDrawable drawable = new BitmapDrawable(null, decodeBitmapResource(resId));
          drawable.setTargetDensity(mDeviceDensityDpi);
          drawable.setGravity(Gravity.CENTER);
  
          setBackgroundDrawable(drawable);
      }
  
      private Bitmap decodeBitmapResource(int resId) {
          BitmapFactory.Options options = new BitmapFactory.Options();
          options.inDensity = mDeviceDensityDpi;
  
          return BitmapFactory.decodeResource(getResources(), resId, options);
      }
  
  }
  </code></pre>
  
  <p>So, this class will do it's thing if the <code>UnscaledImageView</code> view is used in XML layouts or directly initialized in code. I've also provided 2 methods so the image can be changed in code while keeping it from being stretched. As you can see, these methods only take resource ids, so far I haven't felt the need to use drawables or bitmaps directly.</p>
  
  <p>Now the real issue I'm having with this...</p>
  
  <p>If this class is used as single image view in some layout, no problem, it's only decoding one image. But if it's used in a <code>GridView</code> where there can be like 40 icons (I'm taking this value from what really happens on my app running on my xhdpi device) visible at the same time, scrolling the <code>GridView</code> will be very slow because the <code>decodeBitmapResource()</code> is calling <code>BitmapFactory.decodeResource()</code> for each and every image.</p>
  
  <p>This is my problem and that is my question. <strong>How can I optimize this?</strong> If possible, at all...</p>