May 26, 2012

Auto resize bitmap to fit View

Last article demonstrate Reduce bitmap by setting inSampleSize of BitmapFactory.Options manually. In this article we are going to calculate the most optimize inSampleSize value to fit within a area. In our case, the ImageView is place inside a LinearLayout with "fill_parent" on both layout_width and layout_height. We try to get the best inSampleSize to create bitmap most fit in the LinearLayout.

Auto resize to fit View


In order to calulate the resize ratio, we need the expected width and height of the parent LinearLayout. BUT we cannot direct get it in onCreate() call-back method. Because when onCreate() is called, the layout not yet drawn and both getWidth() and getHeight() return 0. In the code, I display the getWidth() and getHeight() in onCreate(), onStart() and onResume() for your reference. So...how can I know when the layout is drawn? it's android.view.ViewTreeObserver.

android.view.ViewTreeObserver is used to register listeners that can be notified of global changes in the view tree. Such global events include, but are not limited to, layout of the whole tree, beginning of the drawing pass, touch mode change.... A ViewTreeObserver should never be instantiated by applications as it is provided by the views hierarchy. Refer to getViewTreeObserver() for more information.


onGlobalLayout() of ViewTreeObserver.OnGlobalLayoutListener is Callback method to be invoked when the global layout state or the visibility of views within the view tree changes.

Such that we can register our OnGlobalLayoutListener to get parent LinearLayout width and height, to perform the auto-resize.

(I also tried to do it in onPreDraw() of ViewTreeObserver.onPreDrawListener. But it seem that to be called too many times!)

Please note that inSampleSize is int value, that means we cannot get a 100% fit value. That's why I place HorizontalScrollView and ScrollView outside the ImageView, you can still scroll it. If you want a visually full-fit ImageView, simple remove HorizontalScrollView and ScrollView.

package com.AndroidResizeBigPicture;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidResizeBigPictureActivity extends Activity {
 

 TextView resizeLevel;
 ImageView imageView;
 LinearLayout imageParent;
 
 Bitmap bitmap_Source;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

     resizeLevel = (TextView)findViewById(R.id.resizelevel);
     imageView = (ImageView)findViewById(R.id.imageview);
     imageParent = (LinearLayout)findViewById(R.id.imageparent);

     ToastInfo("onCreate");
     
     ViewTreeObserver viewTreeObserver = imageParent.getViewTreeObserver();
     viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener);
     //viewTreeObserver.addOnPreDrawListener(onPreDrawListener);
    }
    
    OnGlobalLayoutListener onGlobalLayoutListener
    = new OnGlobalLayoutListener(){

  @Override
  public void onGlobalLayout() {
   /*
    * Callback method to be invoked when the global layout state 
    * or the visibility of views within the view tree changes
    */
   ToastInfo("onGlobalLayout");
   imageView.setImageBitmap(
     resize(imageParent.getWidth(), imageParent.getHeight()));
  }
     
    };
    
    OnPreDrawListener onPreDrawListener
    = new OnPreDrawListener(){

  @Override
  public boolean onPreDraw() {
   /*
    * Callback method to be invoked when the view tree is about 
    * to be drawn. At this point, all views in the tree have been 
    * measured and given a frame. Clients can use this to adjust 
    * their scroll bounds or even to request a new layout before 
    * drawing occurs.
    */
   
   ToastInfo("onPreDraw");
   imageView.setImageBitmap(
     resize(imageParent.getWidth(), imageParent.getHeight()));
   
   return true;
  }};
  
    private void ToastInfo(String caller){
     Toast.makeText(getBaseContext(), 
       caller + " - " +
       imageParent.getWidth() + " : " + imageParent.getHeight(), 
       Toast.LENGTH_LONG).show();
    }

 @Override
 protected void onStart() {
  // TODO Auto-generated method stub
  super.onStart();
  ToastInfo("onStart");
 }
 
 @Override
 protected void onResume() {
  // TODO Auto-generated method stub
  super.onResume();
  ToastInfo("onResume");
 }

 private Bitmap resize(int width, int height){
  
  BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
        bmpFactoryOptions.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeResource(
          getResources(), R.drawable.testpicture, bmpFactoryOptions);
        
        int heightRatio = (int)Math.ceil(bmpFactoryOptions.outHeight/(float)height);
        int widthRatio = (int)Math.ceil(bmpFactoryOptions.outWidth/(float)width);
        
        if (heightRatio > 1 || widthRatio > 1)
        {
         if (heightRatio > widthRatio)
         {
          bmpFactoryOptions.inSampleSize = heightRatio;
          resizeLevel.setText("inSampleSize= " + heightRatio);
         } else {
          bmpFactoryOptions.inSampleSize = widthRatio; 
          resizeLevel.setText("inSampleSize= " + widthRatio);
         } 
        }else{
         resizeLevel.setText("inSampleSize= N/A");
        }
         
        bmpFactoryOptions.inJustDecodeBounds = false;
        bitmap = BitmapFactory.decodeResource(getResources(), 
          R.drawable.testpicture, bmpFactoryOptions);
        return bitmap;
 }
  
}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
 
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <TextView
        android:id="@+id/resizelevel"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content" />
    <LinearLayout
        android:id="@+id/imageparent"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
        <HorizontalScrollView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
       <ScrollView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content">
          <ImageView
              android:id="@+id/imageview"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:scaleType="center" />
         </ScrollView>
        </HorizontalScrollView>
    </LinearLayout>
 
</LinearLayout>


Notice: You have to place your testpicture.jpg in /res/drawable/ folder.


No comments:

Post a Comment

Infolinks In Text Ads