5

I have an activity that has an ImageView defined inside a HorizontalScrollView. The image source is a 9-patch file that is constrained to stretch only the right edge to fill the screen. I have implemented a simple zoom feature which allows the user to double tap to zoom in and out, by resizing the bitmap and assigning the new bitmap to the view. My current problem is that when doubling tapping to zoom back out, the 9-patch is not applied when I assign the new resized bitmap to the view. In other words, instead of stretching just the right edge as defined in the 9-patch file, it stretched the entire image.

Here is my XML:

<HorizontalScrollView
            android:id="@+id/hScroll"
            android:fillViewport="true"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:fadingEdge="none" >

            <RelativeLayout
                android:id="@+id/rlayoutScrollMap"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent" >

                <ImageView
                    android:id="@+id/imgResultMap"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    android:scaleType="fitXY"
                    android:src="@drawable/map_base"/>

            </RelativeLayout>
        </horizontalScrollView>

Here is the relevant portion of my code, inside the onDoubleTap() call :

public boolean onDoubleTap(MotionEvent e)  
                {  
                    if (zoom == 1) {
                        zoom = 2; // zoom out
                    } else {
                        zoom = 1; // zoom in
                    }
                    Bitmap image = BitmapFactory.decodeResource(getResources(),R.drawable.map_base);            
                    Bitmap bmp = Bitmap.createScaledBitmap(image, image.getWidth() * zoom, image.getHeight() * zoom, false);
                    ImageView imgResultMap = (ImageView)findViewById(R.id.imgResultMap);
                    imgResultMap.setImageBitmap(bmp);

                    return false; 
                } 

EDIT: After doing some research, I have it figured out. Instead of just manipulating the bitmap, I need to also include the 9-patch chunk, which is not part of the bitmap image, to re-construct a new 9-patch drawable. See sample code below:

    ...
 else {
        // Zoom out
        zoom = 1;
        Bitmap mapBitmapScaled = mapBitmap; 
        // Load the 9-patch data chunk and apply to the view
        byte[] chunk = mapBitmap.getNinePatchChunk();
        NinePatchDrawable mapNinePatch = new NinePatchDrawable(getResources(), 
            mapBitmapScaled, chunk, new Rect(), null);
        imgResultMap.setImageDrawable(mapNinePatch);
 }

  ....
Kenny
  • 429
  • 6
  • 22

5 Answers5

10

EDIT: After doing some research, I have it figured out. Instead of just manipulating the bitmap, I need to also include the 9-patch chunk, which is not part of the bitmap image, to re-construct a new 9-patch drawable. See sample code below:

    ...
 else {
        // Zoom out
        zoom = 1;
        Bitmap mapBitmapScaled = mapBitmap; 
        // Load the 9-patch data chunk and apply to the view
        byte[] chunk = mapBitmap.getNinePatchChunk();
        NinePatchDrawable mapNinePatch = new NinePatchDrawable(getResources(), 
            mapBitmapScaled, chunk, new Rect(), null);
        imgResultMap.setImageDrawable(mapNinePatch);
 }

  ....

EDIT #2: For those who looks at my solution here, also take a look at Kai's suggestions below regarding memory management. Very useful information.

Kenny
  • 429
  • 6
  • 22
  • Great, I needed to make some manipulations on a 9patch resource, and without this snippet of code I really didn't know where to start! Thanks! – lorenzo-s Oct 01 '14 at 07:07
  • Try using setImageResource(9patchResourceID) instead of setImageBitmap or setImageBackground and it works :) – Shilpi Oct 16 '15 at 07:36
1

Why don't you just include two different scaled images and switch between them using imgResultMap.setImageResource(resId) when zooming? Also note that you are loading & creating Bitmaps in UIThread which is not a good way to provide smooth user experience, at least preload the bitmap only once during onCreate() and cache that.

Kai
  • 15,284
  • 6
  • 51
  • 82
  • This is a good point. However, I have about 35 elements that have the scaled versions created similar to what I showed above, and I am doing garbage collection right after the creation of each scaled version (using v.recycle()). My concern is that when I create the scaled version during onCreate() is there a potential that I will get OOM error right up front becuase it essentially needs to hold both versions in memory? – Kenny May 17 '12 at 16:52
  • You should be able to calculate the amount of memory held up per image by using img_width*img_height*4 to see how much memory those 35 elements would take, and then decide whether or not it makes sense for them to load both versions into memory at once. – Kai May 18 '12 at 02:03
  • It takes a total of ~2.4MB to load all these images in the activity so perhaps it is OK to create them up front. When I exit this activity, will garbage collection be automatically performed so that the memory will be freed for my other activities? Also, is it still realistic to assume that the current phones and tablets will allow only 24MB of heap memory for each app? I remember when testing one of my other activities in the app in the emulator with only 24MB heap, I did get OOM and I have to increase the heap size in order to continue with my testing. – Kenny May 18 '12 at 06:40
  • Right now probably older and lower-end phones would have their VM mem limit sets to 24MB, but you'll probably still want to support them. I suggest you use a LruCache that have different storage limit depending on the device's VM mem limit (http://developer.android.com/reference/android/util/LruCache.html). You can also add a wrapper around it to automatically create the missing bitmaps, this may cause object-creation trashing on lower mem devices depending how your program behaves, but still shouldn't be worse than what you had in the worst case. – Kai May 18 '12 at 16:35
  • Thanks for the tip Kai. Will look into the LruCache class you mentioned. – Kenny May 19 '12 at 01:15
0

Move this: ImageView imgResultMap = (ImageView)findViewById(R.id.imgResultMap); to a global intialized in the onCreate method. Otherwise the device has to search for the view and find it every time it is tapped.

and try calling imgResultMap.invalidate(); before you return from the onDoubleTap method (http://developer.android.com/reference/android/view/View.html#invalidate())

you786
  • 3,659
  • 5
  • 48
  • 74
0

9-patch images seem to only work when they're set as a View's background image. So:

  • instead of android:src, set android:background in your layout XML, and
  • call setBackgroundResource() when you want to change the image.

You can also use a plain View instead of an ImageView if you want; it doesn't really matter much.

mlc
  • 1,668
  • 2
  • 16
  • 30
0

Also another solution is to set image programmatically as ImageResource and set scaleType

imageView.setImageResource(R.drawable.favorite_driver_icon);
imageView.setScaleType(ScaleType.FIT_XY);
ConJoBa
  • 98
  • 7