Monday, July 30, 2012

Introducing Google Fiber: The Next Chapter of the Internet



Google Fiber starts with Internet speeds 100 times faster than what most Americans have today. In this video, we see the evolution of the Internet represented in three different stages. It started with Dial-Up, grew with Broadband -- and with Google Fiber, the possibilities are endless. Pre-register now at http://www.google.com/fiber

About the Video:
The cars and model installation were built by hand, and filmed at Agua Dolce airport in Santa Clarita, CA. The installation now resides in the Fiber Space in Kansas City. For more info about the Fiber Space, please visit http://google.com/fiber/fiberspace


Apply LruCache on GridView

The example "Caching Bitmaps with LruCache" provide a example to Apply LruCache on Gallery. This exercise is GridView version using LruCache.

Reference: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html#memory-cache

GridView with LruCache


To using android.util.LruCache in the code, targetSdkVersion of "13" is needed to be specified in AndroidManifest.xml.

The layout file, refer to the post "GridView loading photos from SD Card".

package com.example.androidgridview;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {

    public class ImageAdapter extends BaseAdapter {
     
     private Context mContext;
     ArrayList<String> itemList = new ArrayList<String>();
     
     public ImageAdapter(Context c) {
      mContext = c; 
     }
     
     void add(String path){
      itemList.add(path); 
     }

  @Override
  public int getCount() {
   return itemList.size();
  }

  @Override
  public Object getItem(int arg0) {
   // TODO Auto-generated method stub
   return null;
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   ImageView imageView;
         if (convertView == null) {  // if it's not recycled, initialize some attributes
             imageView = new ImageView(mContext);
             imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
             imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
             imageView.setPadding(8, 8, 8, 8);
         } else {
             imageView = (ImageView) convertView;
         }

         //Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220, 220);
         
         // Use the path as the key to LruCache
         final String imageKey = itemList.get(position);
           final Bitmap bm = getBitmapFromMemCache(imageKey);
           
         if (bm == null){
          BitmapWorkerTask task = new BitmapWorkerTask(imageView);
          task.execute(imageKey);
         };

         imageView.setImageBitmap(bm);
         return imageView;
  }
  
  public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth, int reqHeight) {
   
   Bitmap bm = null;
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(path, options);
       
   // Calculate inSampleSize
   options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
       
   // Decode bitmap with inSampleSize set
   options.inJustDecodeBounds = false;
   bm = BitmapFactory.decodeFile(path, options); 
       
   return bm;   
  }
  
  public int calculateInSampleSize(
    
   BitmapFactory.Options options, int reqWidth, int reqHeight) {
   // Raw height and width of image
   final int height = options.outHeight;
   final int width = options.outWidth;
   int inSampleSize = 1;
   
   if (height > reqHeight || width > reqWidth) {
    if (width > height) {
     inSampleSize = Math.round((float)height / (float)reqHeight);    
    } else {
     inSampleSize = Math.round((float)width / (float)reqWidth);    
    }   
   }
   
   return inSampleSize;    
  }
  
  class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap>{
   
   private final WeakReference<ImageView> imageViewReference;
   
   public BitmapWorkerTask(ImageView imageView) {
    // Use a WeakReference to ensure the ImageView can be garbage collected
    imageViewReference = new WeakReference<ImageView>(imageView); 
   }
   
   @Override
   protected Bitmap doInBackground(String... params) {
    final Bitmap bitmap = decodeSampledBitmapFromUri(params[0], 200, 200);
    addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
    return bitmap;
   }

   @Override
   protected void onPostExecute(Bitmap bitmap) {
    if (imageViewReference != null && bitmap != null) {
     final ImageView imageView = (ImageView)imageViewReference.get();
     if (imageView != null) {
      imageView.setImageBitmap(bitmap); 
     } 
    } 
   }
  }

 }
    
    ImageAdapter myImageAdapter;
    
    private LruCache<String, Bitmap> mMemoryCache;

 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        GridView gridview = (GridView) findViewById(R.id.gridview);
        myImageAdapter = new ImageAdapter(this);
        gridview.setAdapter(myImageAdapter);
        
        String ExternalStorageDirectoryPath = Environment
          .getExternalStorageDirectory()
          .getAbsolutePath();
        
        String targetPath = ExternalStorageDirectoryPath + "/test/";
        
        Toast.makeText(getApplicationContext(), targetPath, Toast.LENGTH_LONG).show();
        File targetDirector = new File(targetPath);
        
        File[] files = targetDirector.listFiles();
        for (File file : files){
         myImageAdapter.add(file.getAbsolutePath());
        } 
        
        // Get memory class of this device, exceeding this amount will throw an
        // OutOfMemory exception.
        final int memClass 
         = ((ActivityManager)getSystemService(Context.ACTIVITY_SERVICE))
          .getMemoryClass();
        
        // Use 1/8th of the available memory for this memory cache.
        final int cacheSize = 1024 * 1024 * memClass / 8;
        
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
         
         @Override
         protected int sizeOf(String key, Bitmap bitmap) {
          // The cache size will be measured in bytes rather than number of items.
          return bitmap.getByteCount(); 
         }
        };
    }
 
 public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
  if (getBitmapFromMemCache(key) == null) {
   mMemoryCache.put(key, bitmap); 
  } 
 }
 
 public Bitmap getBitmapFromMemCache(String key) {
  return (Bitmap) mMemoryCache.get(key); 
 }

}


Download the files.


Sunday, July 29, 2012

Factory images of Jelly Bean for Galaxy Nexus and Nexus S released

Factory Images for Nexus Devices updated with Factory images of Jelly Bean for Galaxy Nexus and Nexus S.

Factory Images for Nexus Devices


You will find these files useful if you have used the Android Open-Source Project, flashed custom builds on your device, and wish to return that device to its factory state.

Gallery-like single column GridView

by changing android:numColumns="1", the last exercise of "GridView" can be modified to a Gallery-like single column GridView.

Gallery-like single column GridView


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="horizontal">
    <GridView
        android:id="@+id/gridview"
        android:layout_width="130dp" 
        android:layout_height="fill_parent"
        android:columnWidth="90dp"
        android:numColumns="1"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:stretchMode="columnWidth"
        android:gravity="center"
        android:background="@android:color/background_dark"/>
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent" 
        android:orientation="vertical"
        android:background="@android:color/background_light">
 </LinearLayout>
</LinearLayout>


GridView loading photos from SD Card

Previous exercises described how to implement "Gallery-like HorizontalScrollView" and "Vertical Gallery-like ScrollView". Alternatively, it can be displayed in GridView.

GridView


Add a <GridView> in layout.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">

    <GridView
        android:id="@+id/gridview"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent"
        android:columnWidth="90dp"
        android:numColumns="auto_fit"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:stretchMode="columnWidth"
        android:gravity="center"/>

</LinearLayout>


Main code:
package com.example.androidgridview;

import java.io.File;
import java.util.ArrayList;

import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {

    public class ImageAdapter extends BaseAdapter {
     
     private Context mContext;
     ArrayList<String> itemList = new ArrayList<String>();
     
     public ImageAdapter(Context c) {
      mContext = c; 
     }
     
     void add(String path){
      itemList.add(path); 
     }

  @Override
  public int getCount() {
   return itemList.size();
  }

  @Override
  public Object getItem(int arg0) {
   // TODO Auto-generated method stub
   return null;
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   ImageView imageView;
         if (convertView == null) {  // if it's not recycled, initialize some attributes
             imageView = new ImageView(mContext);
             imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
             imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
             imageView.setPadding(8, 8, 8, 8);
         } else {
             imageView = (ImageView) convertView;
         }

         Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220, 220);

         imageView.setImageBitmap(bm);
         return imageView;
  }
  
  public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth, int reqHeight) {
   
   Bitmap bm = null;
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(path, options);
       
   // Calculate inSampleSize
   options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
       
   // Decode bitmap with inSampleSize set
   options.inJustDecodeBounds = false;
   bm = BitmapFactory.decodeFile(path, options); 
       
   return bm;   
  }
  
  public int calculateInSampleSize(
    
   BitmapFactory.Options options, int reqWidth, int reqHeight) {
   // Raw height and width of image
   final int height = options.outHeight;
   final int width = options.outWidth;
   int inSampleSize = 1;
   
   if (height > reqHeight || width > reqWidth) {
    if (width > height) {
     inSampleSize = Math.round((float)height / (float)reqHeight);    
    } else {
     inSampleSize = Math.round((float)width / (float)reqWidth);    
    }   
   }
   
   return inSampleSize;    
  }

 }
    
    ImageAdapter myImageAdapter;

 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        GridView gridview = (GridView) findViewById(R.id.gridview);
        myImageAdapter = new ImageAdapter(this);
        gridview.setAdapter(myImageAdapter);
        
        String ExternalStorageDirectoryPath = Environment
          .getExternalStorageDirectory()
          .getAbsolutePath();
        
        String targetPath = ExternalStorageDirectoryPath + "/test/";
        
        Toast.makeText(getApplicationContext(), targetPath, Toast.LENGTH_LONG).show();
        File targetDirector = new File(targetPath);
        
        File[] files = targetDirector.listFiles();
        for (File file : files){
         myImageAdapter.add(file.getAbsolutePath());
        } 
    }

}


Download the files.

Next:
- Gallery-like single column GridView
- Retrieve old activity state for configuration change by overriding onRetainNonConfigurationInstance() method
- Implement OnItemClickListener for GridView

Suggested:
Scale bitmap Efficiently

Saturday, July 21, 2012

Vertical Gallery-like ScrollView

With the custom LinearLayout (MyHorizontalLayout.java) in last exercise "Implement custom LinearLayout for Gallery-like HorizontalScrollView", it can be implement vertical Gallery-like ScrollView also.

Vertical Gallery-like ScrollView


Keep both MainActivity.java and MyHorizontalLayout.java of last exercise no change.

Modify the layout.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">
    
    <ScrollView
        android:layout_width="wrap_content"
        android:layout_height="fill_parent" >
        <com.example.androidhorizontalscrollviewgallery.MyHorizontalLayout
            android:id="@+id/mygallery"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            />
    </ScrollView>

</LinearLayout>


Related:
- GridView loading photos from SD Card


Friday, July 20, 2012

Implement custom LinearLayout for Gallery-like HorizontalScrollView

Last exercise explain the basic to "implement Gallery-like HorizontalScrollView". In this post, we are going to implement our custom LinearLayout for Gallery-like HorizontalScrollView.

Custom LinearLayout for Gallery-like HorizontalScrollView


MyHorizontalLayout.java, our custom LinearLayout for Gallery-like HorizontalScrollView.
package com.example.androidhorizontalscrollviewgallery;
import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class MyHorizontalLayout extends LinearLayout {
 
 Context myContext;
 ArrayList<String> itemList = new ArrayList<String>();

 public MyHorizontalLayout(Context context) {
  super(context);
  myContext = context;
 }

 public MyHorizontalLayout(Context context, AttributeSet attrs) {
  super(context, attrs);
  myContext = context;
 }

 public MyHorizontalLayout(Context context, AttributeSet attrs,
   int defStyle) {
  super(context, attrs, defStyle);
  myContext = context;
 }
 
 void add(String path){
  int newIdx = itemList.size();
  itemList.add(path);
  addView(getImageView(newIdx));
 }
 
 ImageView getImageView(int i){
  Bitmap bm = null;
  if (i < itemList.size()){
   bm = decodeSampledBitmapFromUri(itemList.get(i), 220, 220);
  }
  
  ImageView imageView = new ImageView(myContext);
     imageView.setLayoutParams(new LayoutParams(220, 220));
     imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
     imageView.setImageBitmap(bm);

  return imageView;
 }
 
 public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth, int reqHeight) {
     Bitmap bm = null;
     
     // First decode with inJustDecodeBounds=true to check dimensions
     final BitmapFactory.Options options = new BitmapFactory.Options();
     options.inJustDecodeBounds = true;
     BitmapFactory.decodeFile(path, options);
     
     // Calculate inSampleSize
     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
     
     // Decode bitmap with inSampleSize set
     options.inJustDecodeBounds = false;
     bm = BitmapFactory.decodeFile(path, options); 
     
     return bm;  
    }
    
    public int calculateInSampleSize(
      
     BitmapFactory.Options options, int reqWidth, int reqHeight) {
     // Raw height and width of image
     final int height = options.outHeight;
     final int width = options.outWidth;
     int inSampleSize = 1;
        
     if (height > reqHeight || width > reqWidth) {
      if (width > height) {
       inSampleSize = Math.round((float)height / (float)reqHeight);   
      } else {
       inSampleSize = Math.round((float)width / (float)reqWidth);   
      }   
     }
     
     return inSampleSize;   
    }
 
}


Modify layout to include MyHorizontalLayout.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">

    <HorizontalScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
        <com.example.androidhorizontalscrollviewgallery.MyHorizontalLayout
            android:id="@+id/mygallery"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            />
    </HorizontalScrollView>

</LinearLayout>


See how simple is the main code:
package com.example.androidhorizontalscrollviewgallery;

import java.io.File;

import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.widget.Toast;

public class MainActivity extends Activity {

 MyHorizontalLayout myHorizontalLayout;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        myHorizontalLayout = (MyHorizontalLayout)findViewById(R.id.mygallery);
        
        String ExternalStorageDirectoryPath = Environment
          .getExternalStorageDirectory()
          .getAbsolutePath();
        
        String targetPath = ExternalStorageDirectoryPath + "/test/";
        
        Toast.makeText(getApplicationContext(), targetPath, Toast.LENGTH_LONG).show();
        File targetDirector = new File(targetPath);
                 
        File[] files = targetDirector.listFiles();
        for (File file : files){
         myHorizontalLayout.add(file.getAbsolutePath());
        }    
    }
    
}


Download the files.

Next:
- Vertical Gallery-like ScrollView
- Handle onClick for our custom LinearLayout for Gallery-like HorizontalScrollView

Related:
- GridView loading photos from SD Card


Implement Gallery-like HorizontalScrollView

As mentioned in the post "Implement Android Gallery widget" - android.widget.Gallery is no longer supported. Other horizontally scrolling widgets include HorizontalScrollView and ViewPager from the support library.

It's a example to implement Gallery-like view using HorizontalScrollView. Please note that in this example, the bitmaps in HorizontalScrollView will not be removed even not in screen. So if too much bitmaps loaded, error of java.lang.OutOfMemoryError will be thrown!

Gallery-like HorizontalScrollView


Add HorizontalScrollView in layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">

    <HorizontalScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
        <LinearLayout
            android:id="@+id/mygallery"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            />
    </HorizontalScrollView>

</LinearLayout>


Main Java code:
package com.example.androidhorizontalscrollviewgallery;

import java.io.File;

import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;

public class MainActivity extends Activity {

 LinearLayout myGallery;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        myGallery = (LinearLayout)findViewById(R.id.mygallery);
        
        String ExternalStorageDirectoryPath = Environment
          .getExternalStorageDirectory()
          .getAbsolutePath();
        
        String targetPath = ExternalStorageDirectoryPath + "/test/";
        
        Toast.makeText(getApplicationContext(), targetPath, Toast.LENGTH_LONG).show();
        File targetDirector = new File(targetPath);
                 
        File[] files = targetDirector.listFiles();
        for (File file : files){
         myGallery.addView(insertPhoto(file.getAbsolutePath()));
        }    
    }

    View insertPhoto(String path){
     Bitmap bm = decodeSampledBitmapFromUri(path, 220, 220);
     
     LinearLayout layout = new LinearLayout(getApplicationContext());
     layout.setLayoutParams(new LayoutParams(250, 250));
     layout.setGravity(Gravity.CENTER);
     
     ImageView imageView = new ImageView(getApplicationContext());
     imageView.setLayoutParams(new LayoutParams(220, 220));
     imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
     imageView.setImageBitmap(bm);
     
     layout.addView(imageView);
     return layout;
    }
    
    public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth, int reqHeight) {
     Bitmap bm = null;
     
     // First decode with inJustDecodeBounds=true to check dimensions
     final BitmapFactory.Options options = new BitmapFactory.Options();
     options.inJustDecodeBounds = true;
     BitmapFactory.decodeFile(path, options);
     
     // Calculate inSampleSize
     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
     
     // Decode bitmap with inSampleSize set
     options.inJustDecodeBounds = false;
     bm = BitmapFactory.decodeFile(path, options); 
     
     return bm;  
    }
    
    public int calculateInSampleSize(
      
     BitmapFactory.Options options, int reqWidth, int reqHeight) {
     // Raw height and width of image
     final int height = options.outHeight;
     final int width = options.outWidth;
     int inSampleSize = 1;
        
     if (height > reqHeight || width > reqWidth) {
      if (width > height) {
       inSampleSize = Math.round((float)height / (float)reqHeight);   
      } else {
       inSampleSize = Math.round((float)width / (float)reqWidth);   
      }   
     }
     
     return inSampleSize;   
    }
    
}


Download the files.

Next:
- Implement custom LinearLayout for Gallery-like HorizontalScrollView
- Handle onClick for our custom LinearLayout for Gallery-like HorizontalScrollView


Remark@2015-11-13:
It's a old example. With current Material Design on Android, it's suggested to consider using " RecyclerView + CardView".

Coresponding "Gallery-like RecyclerView + CardView example".


Thursday, July 19, 2012

Dialog Animation using windowAnimations

In this exercise, we are going to apply slide-in and slide-out animation on dialog, using windowAnimations.


Create/modify /res/values/styles.xml to add animation style of DialogAnimation, using build-in animation of slide_in_left and slide_out_right.
<resources>

    <style name="AppTheme" parent="android:Theme.Light" />

    <style name="DialogAnimation">
        <item name="android:windowEnterAnimation">@android:anim/slide_in_left</item>
        <item name="android:windowExitAnimation">@android:anim/slide_out_right</item>
    </style>
</resources>


Implement our dialog layout, /res/layout/dialoglayout.xml.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" 
    android:orientation="vertical">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"/>
    <Button
        android:id="@+id/dismiss"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="dismiss"/>

</LinearLayout>


MainActivity.java
package com.example.animationdialog;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.app.Activity;
import android.app.Dialog;

public class MainActivity extends Activity {

 Button btnOpenDialog;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnOpenDialog = (Button)findViewById(R.id.opendialog);
        btnOpenDialog.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    openDialog();
   }});
    }

    private void openDialog(){
     final Dialog dialog = new Dialog(MainActivity.this);
     dialog.setTitle("Animation Dialog");
     dialog.setContentView(R.layout.dialoglayout);
     dialog.getWindow().getAttributes().windowAnimations = R.style.DialogAnimation;
     Button btnDismiss = (Button)dialog.getWindow().findViewById(R.id.dismiss);
     
     btnDismiss.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    dialog.dismiss();
   }});
     
     dialog.show();
    }
    
}


Main layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/opendialog"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:padding="@dimen/padding_medium"
        android:text="Open Dialog"
        tools:context=".MainActivity" />

</RelativeLayout>

Download the files.


Android SDK Tools Revision 20.0.1, NDK revision 8b, and ADT 20.0.1 released

Android SDK Tools Revision 20.0.1, full SDK for Android 4.1, released. You can now develop and publish applications against API level 16 using new Jelly Bean APIs. The new update can be downloaded through SDK Manager. Also updated are NDK revision 8b and ADT Plugin 20.0.1.

Update SDK on Eclipse:

Please note that note that the SDK Tools r20.0.1 is designed for use with ADT 20.0.1 and later. To update ADT in Eclipse, click Help -> Check for updates, to update ADT.

After updated ADT, click Window -> Android SDK Manager to install updated components.



Tuesday, July 17, 2012

Implement GestureDetector/OnGestureListener to detect Fling, and ObjectAnimator for ImageView

In this exercise, I will implement GestureDetector with OnGestureListener for a ImageView of icon. Once the user fling on the icon, it will be moved (animated using ObjectAnimator).



package com.example.androidanimatefling;

import android.os.Bundle;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.util.DisplayMetrics;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.GestureDetector.OnGestureListener;
import android.view.View.OnTouchListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends Activity {
 
 TextView info;
 ImageView flingObj;
 FrameLayout mainScreen;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        info = (TextView)findViewById(R.id.info);
        flingObj = (ImageView)findViewById(R.id.flingobject);
        mainScreen = (FrameLayout)findViewById(R.id.mainscreen);
        
        final GestureDetector myGesture = new GestureDetector(this, new MyOnGestureListener());
        
        flingObj.setOnTouchListener(new OnTouchListener(){

   @Override
   public boolean onTouch(View v, MotionEvent event) {
    return myGesture.onTouchEvent(event);
   }});
        
        flingObj.setClickable(true);
        
    }

    class MyOnGestureListener implements OnGestureListener{
     
     int MIN_DIST = 100;

  @Override
  public boolean onDown(MotionEvent arg0) {
   // TODO Auto-generated method stub
   return false;
  }

  @Override
  public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
    float velocityY) {
   float e1X = e1.getX();
   float e1Y = e1.getY();
   float e2X = e2.getX();
   float e2Y = e2.getY();
   float distX = e2X - e1X;
   float distY = e2Y - e1Y;
   
   info.setText(
     "e1X   e1Y : " + String.valueOf(e1X) + " : " + String.valueOf(e1Y) + "\n" +
     "e2X   e2Y : " + String.valueOf(e2X) + " : " + String.valueOf(e2Y) + "\n" +
     "velocityX : " + String.valueOf(velocityX) + "\n" +
     "velocityY : " + String.valueOf(velocityY));
   
   //Get the Y OFfset
   DisplayMetrics displayMetrics = new DisplayMetrics();
   getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
   int offsetY = displayMetrics.heightPixels - mainScreen.getMeasuredHeight();
   
   int[] location = new int[2];
   flingObj.getLocationOnScreen(location);
   float orgX = location[0];
   float orgY = location[1] - offsetY;
   
   float stopX = orgX + distX;
   float stopY = orgY + distY;
   
   if (distX > MIN_DIST) {
    //Fling Right
    ObjectAnimator flingAnimator = ObjectAnimator.ofFloat(flingObj, "translationX", orgX, stopX);
          flingAnimator.setDuration(1000);
          flingAnimator.start();
   }else if(distX < - MIN_DIST){
    //Fling Left
    ObjectAnimator flingAnimator = ObjectAnimator.ofFloat(flingObj, "translationX", orgX, stopX);
          flingAnimator.setDuration(1000);
          flingAnimator.start();
   }else if (distY > MIN_DIST) {
    //Fling Down
    ObjectAnimator flingAnimator = ObjectAnimator.ofFloat(flingObj, "translationY", orgY, stopY);
          flingAnimator.setDuration(1000);
          flingAnimator.start();
   }else if(distY < - MIN_DIST){
    //Fling Up
    ObjectAnimator flingAnimator = ObjectAnimator.ofFloat(flingObj, "translationY", orgY, stopY);
          flingAnimator.setDuration(1000);
          flingAnimator.start();
   }
   
   return true;
  }

  @Override
  public void onLongPress(MotionEvent e) {
   // TODO Auto-generated method stub
   
  }

  @Override
  public boolean onScroll(MotionEvent e1, MotionEvent e2,
    float distanceX, float distanceY) {
   // TODO Auto-generated method stub
   return false;
  }

  @Override
  public void onShowPress(MotionEvent e) {
   // TODO Auto-generated method stub
   
  }

  @Override
  public boolean onSingleTapUp(MotionEvent e) {
   // TODO Auto-generated method stub
   return false;
  }};

}


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/mainscreen">
    
    <TextView 
        android:id="@+id/info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <ImageView
        android:id="@+id/flingobject"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="@dimen/padding_medium"
        android:src="@drawable/ic_launcher"
        tools:context=".MainActivity" />

</FrameLayout>


In order to use android.animation.ObjectAnimator, minSdkVersion="11" is needed.

Download the files.

Error of getLocationInWindow() and getLocationOnScreen()

The View class provide the methods to computes the coordinates:

In my experience, the returned x location is correct, but the y location is always error with a fixed offset. The offset various depends on devices and configuration.

To correct it, we can get the offset using the code, after view displayed:
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int offsetY = displayMetrics.heightPixels - mainScreen.getMeasuredHeight();


offset of getLocationInWindow() and getLocationOnScreen()


package com.example.androidoffsetgetlocation;

import android.os.Bundle;
import android.app.Activity;
import android.util.DisplayMetrics;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends Activity {
 
 LinearLayout mainScreen;
 ImageView object;
 TextView textOnCreate, textOnWindowFocusChanged;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mainScreen = (LinearLayout)findViewById(R.id.mainscreen);
        object = (ImageView)findViewById(R.id.object);
        textOnCreate = (TextView)findViewById(R.id.textview1);
        textOnWindowFocusChanged = (TextView)findViewById(R.id.textview2);
  
  readLocation(textOnCreate, "onCreate()");
    }

 @Override
 public void onWindowFocusChanged(boolean hasFocus) {
  // TODO Auto-generated method stub
  super.onWindowFocusChanged(hasFocus);
  
  readLocation(textOnWindowFocusChanged, "onWindowFocusChanged()");
 }
 
 private void readLocation(TextView tv, String status){
  DisplayMetrics displayMetrics = new DisplayMetrics();
  getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
  int offsetX = displayMetrics.widthPixels - mainScreen.getMeasuredWidth();
  int offsetY = displayMetrics.heightPixels - mainScreen.getMeasuredHeight();
        
        int[] locationInWindow = new int[2];
  object.getLocationInWindow(locationInWindow);
  int[] locationOnScreen = new int[2];
  object.getLocationOnScreen(locationOnScreen);
  
  tv.setText(
    "\n" + status +"\n"
    + "getLocationInWindow() - " + locationInWindow[0] + " : " + locationInWindow[1] + "\n"
    + "getLocationOnScreen() - " + locationOnScreen[0] + " : " + locationOnScreen[1] + "\n"
    + "Offset x: y - " + offsetX + " : " + offsetY);
  
 }

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical"
    android:id="@+id/mainscreen">
     
    <ImageView
        android:id="@+id/object"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"/>
    <TextView
        android:id="@+id/textview1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/textview2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>



Monday, July 16, 2012

Complete list of all new features of Android 4.1 Jelly Bean

Android 4.1, Jelly Bean, is the fastest and smoothest version of Android yet. Jelly Bean improves on the simplicity and beauty of Android 4.0, and introduces a new Google search experience on Android.

  • Everything in Jelly Bean feels fast, fluid, and smooth. Moving between home screens and switching between apps is effortless, like turning pages in a book.
  • Jelly Bean features improved performance throughout the system, including faster orientation changes, faster responses when switching between recent apps, and smoother and more consistent rendering across the system through vsync and triple buffering.
  • Jelly Bean has more reactive and uniform touch responses, and makes your device even more responsive by boosting your device's CPU instantly when you touch the screen, and turns it down when you don't need it to improve battery life.


To read what's new in Android 4.1 Jelly Bean, visit: http://www.android.com/about/jelly-bean/.

Implement grouped CheckBox on Action Menu

Example of Action Menu with grouped CheckBox.

Action Menu with grouped CheckBox


Create /res/menu/activity_main.xml to define action menu.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/menu_settings"
        android:title="@string/menu_settings"
        android:orderInCategory="100"
        android:showAsAction="never" />
    <group android:checkableBehavior="single">
        <item android:id="@+id/selecta"
              android:title="Selection A" android:checked="true"/>
        <item android:id="@+id/selectb"
              android:title="Selection B" />
        <item android:id="@+id/selectc"
              android:title="Selection C" />
    </group>
</menu>


The checked status will not be updated automatically. We can change it in onOptionsItemSelected() callback method.
package com.example.androidactionbar;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  switch (item.getItemId()) {
        case R.id.selecta:
         item.setChecked(true);
         Toast.makeText(getApplicationContext(), 
           "A Selected", 
           Toast.LENGTH_LONG).show();
         return true;
        case R.id.selectb:
         item.setChecked(true);
         Toast.makeText(getApplicationContext(), 
           "B Selected", 
           Toast.LENGTH_LONG).show();
         return true;
        case R.id.selectc:
         item.setChecked(true);
         Toast.makeText(getApplicationContext(), 
           "C Selected", 
           Toast.LENGTH_LONG).show();
         return true;
        default:
            return super.onOptionsItemSelected(item);    
  }
 }

}



Sunday, July 15, 2012

LruCache with different size

Last exercise show Gallery with cached bitmaps using LruCache.

There is no specific size or formula that suits all applications, it's up to you to analyze your usage and come up with a suitable solution. A cache that is too small causes additional overhead with no benefit, a cache that is too large can once again cause java.lang.OutOfMemory exceptions and leave the rest of your app little memory to work with.

In this exercise, there are two Gallery widgets on screen, both with cached bitmaps using LruCache. The upper one with smaller cache size and the lower one with bigger cache size. There are more than 200 photos in /test/ directory.  The upper small cache size gallery always re-load the cache. The lower one with bigger size of cache can keep bitmaps in cache, but when the cached memory get large, it crash the app!.



package com.example.androidgallery;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class MainActivity extends Activity {

    public abstract class CacheGalleryBaseAdapter extends BaseAdapter {

     ArrayList<String> GalleryFileList;
     Context context;
     
     CacheGalleryBaseAdapter(Context cont){
      context = cont;
      GalleryFileList = new ArrayList<String>();    
  }
     
     abstract Bitmap abstractGetBitmapFromMemCache(String key);
     abstract BitmapWorkerTask abstractStartBitmapWorkerTask(ImageView imageView);
     
  @Override
  public int getCount() {
   return GalleryFileList.size();
  }

  @Override
  public Object getItem(int position) {
   return GalleryFileList.get(position);
  }

  @Override
  public long getItemId(int position) {
   return position;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {

   ImageView imageView = new ImageView(context);
   
   //---
   // Use the path as the key to LruCache
   final String imageKey = GalleryFileList.get(position);

      final Bitmap bm = abstractGetBitmapFromMemCache(imageKey);
      if (bm == null){
       BitmapWorkerTask task = abstractStartBitmapWorkerTask(imageView);
       task.execute(imageKey);
      }
   
   LinearLayout layout = new LinearLayout(context);
   layout.setLayoutParams(new Gallery.LayoutParams(250, 250));
   layout.setGravity(Gravity.CENTER);

   imageView.setLayoutParams(new Gallery.LayoutParams(200, 200));
   imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
   imageView.setImageBitmap(bm);
   
   layout.addView(imageView);
   return layout;

  }
  
  public void add(String newitem){
   GalleryFileList.add(newitem);
  }

 }
    
    public class CacheGalleryBaseAdapter_SmallSize extends CacheGalleryBaseAdapter{

     CacheGalleryBaseAdapter_SmallSize(Context cont) {
   super(cont);
  }

  @Override
  Bitmap abstractGetBitmapFromMemCache(String key) {
   return getBitmapFromMemCache_SmallSize(key);
  }

  @Override
  BitmapWorkerTask abstractStartBitmapWorkerTask(ImageView imageView) {
   return (new BitmapWorkerTask_SmallSize(imageView));
  }
     
    }
    
    public class CacheGalleryBaseAdapter_BigSize extends CacheGalleryBaseAdapter{

     CacheGalleryBaseAdapter_BigSize(Context cont) {
   super(cont);
  }

  @Override
  Bitmap abstractGetBitmapFromMemCache(String key) {
   return getBitmapFromMemCache_BigSize(key);
  }

  @Override
  BitmapWorkerTask abstractStartBitmapWorkerTask(ImageView imageView) {
   return(new BitmapWorkerTask_BigSize(imageView));
  }
     
    }

    CacheGalleryBaseAdapter_SmallSize myFastGallery1BaseAdapter;
    CacheGalleryBaseAdapter_BigSize myFastGallery2BaseAdapter;
    
    Gallery myFastGallery1, myFastGallery2;
    
    private LruCache<String, Bitmap> mMemoryCache_SmallSize;
    private LruCache<String, Bitmap> mMemoryCache_BigSize;
    
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myFastGallery1 = (Gallery)findViewById(R.id.fastgallery1);
        myFastGallery2 = (Gallery)findViewById(R.id.fastgallery2);
        
        myFastGallery1BaseAdapter = new CacheGalleryBaseAdapter_SmallSize(this);
        myFastGallery2BaseAdapter = new CacheGalleryBaseAdapter_BigSize(this);
        
        String ExternalStorageDirectoryPath = Environment
    .getExternalStorageDirectory()
    .getAbsolutePath();
        
        String targetPath = ExternalStorageDirectoryPath + "/test/";
        
        File targetDirector = new File(targetPath);
        
        File[] files = targetDirector.listFiles();
        for (File file : files){
         myFastGallery1BaseAdapter.add(file.getPath());
         myFastGallery2BaseAdapter.add(file.getPath());
  }
        
        myFastGallery1.setAdapter(myFastGallery1BaseAdapter);
        myFastGallery2.setAdapter(myFastGallery2BaseAdapter);
        
        // Get memory class of this device, exceeding this amount will throw an
        // OutOfMemory exception.
        final int memClass = ((ActivityManager)getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();

        final int cacheSize_SmallSize = 1024 * 1024 * memClass / 12;
        mMemoryCache_SmallSize = new LruCache_customSize(cacheSize_SmallSize);
        
        final int cacheSize_BigSize = 1024 * 1024 * memClass;
        mMemoryCache_BigSize = new LruCache_customSize(cacheSize_BigSize);

    }
 
 class LruCache_customSize extends LruCache<String, Bitmap>{

  public LruCache_customSize(int maxSize) {
   super(maxSize);
   // TODO Auto-generated constructor stub
  }
  
  protected int sizeOf(String key, Bitmap bitmap) {
      // The cache size will be measured in bytes rather than number of items.
      return bitmap.getByteCount(); 
     }
  
 }
 
 abstract class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap>{
  
  private final WeakReference<ImageView> imageViewReference;
  
  public BitmapWorkerTask(ImageView imageView) {
         // Use a WeakReference to ensure the ImageView can be garbage collected
         imageViewReference = new WeakReference<ImageView>(imageView);
     }
  
  @Override
     protected void onPostExecute(Bitmap bitmap) {
         if (imageViewReference != null && bitmap != null) {
             final ImageView imageView = (ImageView)imageViewReference.get();
             if (imageView != null) {
                 imageView.setImageBitmap(bitmap);
             }
         }
     }
  
 }
 
 public Bitmap getBitmapFromMemCache_SmallSize(String key) {
  return (Bitmap) mMemoryCache_SmallSize.get(key);  
 }
 
 public Bitmap getBitmapFromMemCache_BigSize(String key) {
  return (Bitmap) mMemoryCache_BigSize.get(key);  
 }
 
 class BitmapWorkerTask_SmallSize extends BitmapWorkerTask{

  public BitmapWorkerTask_SmallSize(ImageView imageView) {
   super(imageView);
   // TODO Auto-generated constructor stub
  }
  
  @Override
  protected Bitmap doInBackground(String... params) {
   final Bitmap bitmap = decodeSampledBitmapFromUri(params[0], 200, 200);

   if( getBitmapFromMemCache_SmallSize(String.valueOf(params[0])) == null){
    mMemoryCache_SmallSize.put(String.valueOf(params[0]), bitmap); 
   }
         
         return bitmap;
  }
  
 }
 
 class BitmapWorkerTask_BigSize extends BitmapWorkerTask{

  public BitmapWorkerTask_BigSize(ImageView imageView) {
   super(imageView);
   // TODO Auto-generated constructor stub
  }
  
  @Override
  protected Bitmap doInBackground(String... params) {
   final Bitmap bitmap = decodeSampledBitmapFromUri(params[0], 200, 200);

   if( getBitmapFromMemCache_BigSize(String.valueOf(params[0])) == null){
    mMemoryCache_BigSize.put(String.valueOf(params[0]), bitmap); 
   }
         
         return bitmap;
  }
  
 }
    
    public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth, int reqHeight) {
     Bitmap bm = null;
     
     // First decode with inJustDecodeBounds=true to check dimensions
     final BitmapFactory.Options options = new BitmapFactory.Options();
     options.inJustDecodeBounds = true;
     BitmapFactory.decodeFile(path, options);
      
     // Calculate inSampleSize
     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
      
     // Decode bitmap with inSampleSize set
     options.inJustDecodeBounds = false;
     bm = BitmapFactory.decodeFile(path, options); 
     
     return bm; 
    }
    
    public int calculateInSampleSize(
      BitmapFactory.Options options, int reqWidth, int reqHeight) {
     // Raw height and width of image
     final int height = options.outHeight;
     final int width = options.outWidth;
     int inSampleSize = 1;
     
     if (height > reqHeight || width > reqWidth) {
      if (width > height) {
       inSampleSize = Math.round((float)height / (float)reqHeight);  
      } else {
       inSampleSize = Math.round((float)width / (float)reqWidth);  
      }  
     }
     
     return inSampleSize;  
    }

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">
    
    <TextView 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:text="Gallery with Small Cache Size" />
    <Gallery
        android:id="@+id/fastgallery1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
    
    <TextView 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:text="Gallery with Big Cache Size" />
    <Gallery
        android:id="@+id/fastgallery2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>


modify AndroidManifest.xml to have minSdkVersion and targetSdkVersion of "13".

Download the files.


Saturday, July 14, 2012

Caching Bitmaps with LruCache

In the last exercise of "Implement Android Gallery widget with scale-down bitmap", the bitmap will be loaded and scaled every-time getView() of GalleryBaseAdapter called. The side-effect is the gallery widget stop a moment whenever a new a new item come-out.

The LruCache class (also available in the Support Library for use back to API Level 4) is particularly well suited to the task of caching bitmaps, keeping recently referenced objects in a strong referenced LinkedHashMap and evicting the least recently used member before the cache exceeds its designated size. ~ reference: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html#memory-cache.

In this exercise, two Gallery widget were implemented. In the video shown below, the upper one is original gallery, the lower one is gallery with cached bitmaps. It can be noted that the cached gallery need more time to be loaded in first loading, but swipe much more smooth.



Main activity.
package com.example.androidgallery;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class MainActivity extends Activity {

    public class GalleryBaseAdapter extends BaseAdapter {

     ArrayList<String> GalleryFileList;
     Context context;
     
     GalleryBaseAdapter(Context cont){
      context = cont;
      GalleryFileList = new ArrayList<String>();    
  }
     
  @Override
  public int getCount() {
   return GalleryFileList.size();
  }

  @Override
  public Object getItem(int position) {
   return GalleryFileList.get(position);
  }

  @Override
  public long getItemId(int position) {
   return position;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   
   Bitmap bm = decodeSampledBitmapFromUri(GalleryFileList.get(position), 200, 200);
   LinearLayout layout = new LinearLayout(context);
   layout.setLayoutParams(new Gallery.LayoutParams(250, 250));
   layout.setGravity(Gravity.CENTER);
   
   ImageView imageView = new ImageView(context);
   imageView.setLayoutParams(new Gallery.LayoutParams(200, 200));
   imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
   imageView.setImageBitmap(bm);
   
   layout.addView(imageView);
   return layout; 
  }
  
  public void add(String newitem){
   GalleryFileList.add(newitem);
  }

 }
    
    public class CacheGalleryBaseAdapter extends GalleryBaseAdapter{

  CacheGalleryBaseAdapter(Context cont) {
   super(cont);
  }
  
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {

   ImageView imageView = new ImageView(context);
   
   //---
   // Use the path as the key to LruCache
   final String imageKey = GalleryFileList.get(position);

      final Bitmap bm = getBitmapFromMemCache(imageKey);
      if (bm == null){
       BitmapWorkerTask task = new BitmapWorkerTask(imageView);
       task.execute(imageKey);
      }
   
   LinearLayout layout = new LinearLayout(context);
   layout.setLayoutParams(new Gallery.LayoutParams(250, 250));
   layout.setGravity(Gravity.CENTER);

   imageView.setLayoutParams(new Gallery.LayoutParams(200, 200));
   imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
   imageView.setImageBitmap(bm);
   
   layout.addView(imageView);
   return layout;

  }
     
    }
    
    GalleryBaseAdapter myGalleryBaseAdapter;
    CacheGalleryBaseAdapter myCacheGalleryBaseAdapter;
    Gallery myPhotoGallery, myFastGallery;
    
    private LruCache<String, Bitmap> mMemoryCache;
    
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myPhotoGallery = (Gallery)findViewById(R.id.photogallery);
        myFastGallery = (Gallery)findViewById(R.id.fastgallery);
        
        myGalleryBaseAdapter = new GalleryBaseAdapter(this);
        myCacheGalleryBaseAdapter = new CacheGalleryBaseAdapter(this);
        
        String ExternalStorageDirectoryPath = Environment
    .getExternalStorageDirectory()
    .getAbsolutePath();
        
        String targetPath = ExternalStorageDirectoryPath + "/test/";
        
        File targetDirector = new File(targetPath);
        
        File[] files = targetDirector.listFiles();
        for (File file : files){
         myGalleryBaseAdapter.add(file.getPath());
         myCacheGalleryBaseAdapter.add(file.getPath());
  }
        
        myPhotoGallery.setAdapter(myGalleryBaseAdapter);
        myFastGallery.setAdapter(myCacheGalleryBaseAdapter);
        
        // Get memory class of this device, exceeding this amount will throw an
        // OutOfMemory exception.
        final int memClass = ((ActivityManager)getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();

        // Use 1/8th of the available memory for this memory cache.
        final int cacheSize = 1024 * 1024 * memClass / 8;

        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

         protected int sizeOf(String key, Bitmap bitmap) {
          // The cache size will be measured in bytes rather than number of items.
          return bitmap.getByteCount(); 
         }
         
        };
    }
 
 class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap>{
  
  private final WeakReference<ImageView> imageViewReference;
  
  public BitmapWorkerTask(ImageView imageView) {
         // Use a WeakReference to ensure the ImageView can be garbage collected
         imageViewReference = new WeakReference<ImageView>(imageView);
     }

  @Override
  protected Bitmap doInBackground(String... params) {
   final Bitmap bitmap = decodeSampledBitmapFromUri(params[0], 200, 200);
         addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
         return bitmap;
  }
  
  @Override
     protected void onPostExecute(Bitmap bitmap) {
         if (imageViewReference != null && bitmap != null) {
             final ImageView imageView = (ImageView)imageViewReference.get();
             if (imageView != null) {
                 imageView.setImageBitmap(bitmap);
             }
         }
     }
  
 }
 
 public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
  if (getBitmapFromMemCache(key) == null) {
   mMemoryCache.put(key, bitmap); 
  } 
 }

 public Bitmap getBitmapFromMemCache(String key) {
  return (Bitmap) mMemoryCache.get(key); 
 }
    
    public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth, int reqHeight) {
     Bitmap bm = null;
     
     // First decode with inJustDecodeBounds=true to check dimensions
     final BitmapFactory.Options options = new BitmapFactory.Options();
     options.inJustDecodeBounds = true;
     BitmapFactory.decodeFile(path, options);
      
     // Calculate inSampleSize
     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
      
     // Decode bitmap with inSampleSize set
     options.inJustDecodeBounds = false;
     bm = BitmapFactory.decodeFile(path, options); 
     
     return bm; 
    }
    
    public int calculateInSampleSize(
      BitmapFactory.Options options, int reqWidth, int reqHeight) {
     // Raw height and width of image
     final int height = options.outHeight;
     final int width = options.outWidth;
     int inSampleSize = 1;
     
     if (height > reqHeight || width > reqWidth) {
      if (width > height) {
       inSampleSize = Math.round((float)height / (float)reqHeight);  
      } else {
       inSampleSize = Math.round((float)width / (float)reqWidth);  
      }  
     }
     
     return inSampleSize;  
    }

}


Modify the layout to have two gallery.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">
    
    <TextView 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:text="Gallery without Cache" />
    <Gallery
        android:id="@+id/photogallery"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
    
    <TextView 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:text="Gallery with Cache" />
    <Gallery
        android:id="@+id/fastgallery"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>


android.util.LruCache is needed in the code, modify AndroidManifest.xml to have minSdkVersion and targetSdkVersion of "13".

Download the files.


Please note that this approach have it's own trade-off: if you request cacheSize too much, it will make your app causing java.lang.OutOfMemory exceptions, if too small, it will cause additional overhead. In my own openion, if you have predictable images (or resources) needed for caching, it's a good choice.

Read more: LruCache with different size

Related:
- Apply LruCache on GridView


Implement Android Gallery widget with scale-down bitmap

In the exercise of "Android Gallery widget", the bitmap are loaded in Gallery widget without re-size. If you load with big picture, error of java.lang.OutOfMemoryError will be thrown.

Last post "Scale bitmap Efficiently" explain how to scale-down bitmap. It will be apply in the Gallery.

Android Gallery widget with scale-down bitmap


package com.example.androidgallery;

import java.io.File;
import java.util.ArrayList;

import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;

public class MainActivity extends Activity {

    public class GalleryBaseAdapter extends BaseAdapter {

     ArrayList<String> GalleryFileList;
     Context context;
     
     GalleryBaseAdapter(Context cont){
      context = cont;
      GalleryFileList = new ArrayList<String>();    
  }
     
  @Override
  public int getCount() {
   return GalleryFileList.size();
  }

  @Override
  public Object getItem(int position) {
   return GalleryFileList.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return position;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   //Bitmap bm = BitmapFactory.decodeFile(GalleryFileList.get(position));
   Bitmap bm = decodeSampledBitmapFromUri(GalleryFileList.get(position), 200, 200);
   
   LinearLayout layout = new LinearLayout(context);
   layout.setLayoutParams(new Gallery.LayoutParams(250, 250));
   layout.setGravity(Gravity.CENTER);

   ImageView imageView = new ImageView(context);
   imageView.setLayoutParams(new Gallery.LayoutParams(200, 200));
   imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
   imageView.setImageBitmap(bm);
   
   layout.addView(imageView);
   return layout;

  }
  
  public void add(String newitem){
   GalleryFileList.add(newitem);
  }

 }
    
    GalleryBaseAdapter myGalleryBaseAdapter;
    Gallery myPhotoGallery;
    
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myPhotoGallery = (Gallery)findViewById(R.id.photogallery);
        
        myGalleryBaseAdapter = new GalleryBaseAdapter(this);
        
        String ExternalStorageDirectoryPath = Environment
    .getExternalStorageDirectory()
    .getAbsolutePath();
        
        String targetPath = ExternalStorageDirectoryPath + "/test/";
        
        Toast.makeText(getApplicationContext(), targetPath, Toast.LENGTH_LONG).show();
        File targetDirector = new File(targetPath);
        
        File[] files = targetDirector.listFiles();
        for (File file : files){
         myGalleryBaseAdapter.add(file.getPath());
  }
        
        myPhotoGallery.setAdapter(myGalleryBaseAdapter);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
    
    public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth, int reqHeight) {
     Bitmap bm = null;
     
     // First decode with inJustDecodeBounds=true to check dimensions
     final BitmapFactory.Options options = new BitmapFactory.Options();
     options.inJustDecodeBounds = true;
     BitmapFactory.decodeFile(path, options);
      
     // Calculate inSampleSize
     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
      
     // Decode bitmap with inSampleSize set
     options.inJustDecodeBounds = false;
     bm = BitmapFactory.decodeFile(path, options); 
     
     return bm; 
    }
    
    public int calculateInSampleSize(
      BitmapFactory.Options options, int reqWidth, int reqHeight) {
     // Raw height and width of image
     final int height = options.outHeight;
     final int width = options.outWidth;
     int inSampleSize = 1;
     
     if (height > reqHeight || width > reqWidth) {
      if (width > height) {
       inSampleSize = Math.round((float)height / (float)reqHeight);  
      } else {
       inSampleSize = Math.round((float)width / (float)reqWidth);  
      }  
     }
     
     return inSampleSize;  
    }

}


The layout refer "Android Gallery widget".

Download the files.

Related:
- Caching Bitmaps with LruCache


Scale bitmap Efficiently

Remark: I have a exercise "Display Gallery selected image using BitmapFactory" before. The original idea is to select image using Android build-in app Gallery, and display on a ImageView, FOR SMALL IMAGE. Recently found that it cannot display large image, such as original photos from camera ~ but I don't know why no error reported!!!

It may be due to the Bitmap is too large for the ImageView. To scale-down the bitmap before display, Android Developer Site have a good article to describe "Loading Large Bitmaps Efficiently".


The old exercise is modified with scale-down bitmap and re-posted here:

with scale-down bitmap


Main Code:
package com.example.androidselectimage;

import java.io.FileNotFoundException;

import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
 
 TextView textTargetUri;
 ImageView targetImage;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button buttonLoadImage = (Button)findViewById(R.id.loadimage);
        textTargetUri = (TextView)findViewById(R.id.targeturi);
        targetImage = (ImageView)findViewById(R.id.targetimage);
     
        buttonLoadImage.setOnClickListener(new Button.OnClickListener(){
         
         @Override
         public void onClick(View arg0) {
          // TODO Auto-generated method stub
          Intent intent = new Intent(Intent.ACTION_PICK,
            android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
          startActivityForResult(intent, 0);
         }});
        
    }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  // TODO Auto-generated method stub
  super.onActivityResult(requestCode, resultCode, data);
  
  if (resultCode == RESULT_OK){
   Uri targetUri = data.getData();
   textTargetUri.setText(targetUri.toString());
   
   Toast.makeText(getApplicationContext(), 
     "ImageView: " + targetImage.getWidth() + " x " + targetImage.getHeight(), 
     Toast.LENGTH_LONG).show();
   
   Bitmap bitmap;
   bitmap = decodeSampledBitmapFromUri(
     targetUri,
     targetImage.getWidth(), targetImage.getHeight());
      
   if(bitmap == null){
    Toast.makeText(getApplicationContext(), "the image data could not be decoded", Toast.LENGTH_LONG).show();
    
   }else{
    Toast.makeText(getApplicationContext(), 
      "Decoded Bitmap: " + bitmap.getWidth() + " x " + bitmap.getHeight(), 
      Toast.LENGTH_LONG).show();
    targetImage.setImageBitmap(bitmap);
   } 
  }
 }
 
 /*
  *  How to "Loading Large Bitmaps Efficiently"?
  *  Refer: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
  */
 
 public Bitmap decodeSampledBitmapFromUri(Uri uri, int reqWidth, int reqHeight) {

  Bitmap bm = null;
  
  try{
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options);

      // Calculate inSampleSize
      options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

      // Decode bitmap with inSampleSize set
      options.inJustDecodeBounds = false;
      bm = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options);
  } catch (FileNotFoundException e) {
   e.printStackTrace();
   Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_LONG).show();
  }
  
     return bm;
 }
    
 public int calculateInSampleSize(
   BitmapFactory.Options options, int reqWidth, int reqHeight) {
  // Raw height and width of image
  final int height = options.outHeight;
  final int width = options.outWidth;
  int inSampleSize = 1;
  
  if (height > reqHeight || width > reqWidth) {
   if (width > height) {
    inSampleSize = Math.round((float)height / (float)reqHeight); 
   } else {
    inSampleSize = Math.round((float)width / (float)reqWidth); 
   } 
  }
  return inSampleSize; 
 }
    
}


Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello_world" />
<Button
    android:id="@+id/loadimage"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Load Image" />
<TextView
    android:id="@+id/targeturi"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
<ImageView
    android:id="@+id/targetimage"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:scaleType="centerCrop" />
</LinearLayout>


Download the files.

Friday, July 13, 2012

Implement Android Gallery widget

Please note: android.widget.Gallery is no longer supported. Other horizontally scrolling widgets include HorizontalScrollView and ViewPager from the support library. (Refer Implement Gallery-like HorizontalScrollView)

~ But in some case, I still can't find a suitable alternative, so I still post about Gallery here.

In this exercise, the image files in "test/" folder under SD Card will be loaded in Gallery.

Android Gallery widget


package com.example.androidgallery;

import java.io.File;
import java.util.ArrayList;

import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;

public class MainActivity extends Activity {

    public class GalleryBaseAdapter extends BaseAdapter {

     ArrayList<String> GalleryFileList;
     Context context;
     
     GalleryBaseAdapter(Context cont){
      context = cont;
      GalleryFileList = new ArrayList<String>();    
  }
     
  @Override
  public int getCount() {
   return GalleryFileList.size();
  }

  @Override
  public Object getItem(int position) {
   return GalleryFileList.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return position;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   Bitmap bm = BitmapFactory.decodeFile(GalleryFileList.get(position));
   
   LinearLayout layout = new LinearLayout(context);
   layout.setLayoutParams(new Gallery.LayoutParams(250, 250));
   layout.setGravity(Gravity.CENTER);

   ImageView imageView = new ImageView(context);
   imageView.setLayoutParams(new Gallery.LayoutParams(200, 200));
   imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
   imageView.setImageBitmap(bm);
   
   layout.addView(imageView);
   return layout;

  }
  
  public void add(String newitem){
   GalleryFileList.add(newitem);
  }

 }
    
    GalleryBaseAdapter myGalleryBaseAdapter;
    Gallery myPhotoGallery;
    
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myPhotoGallery = (Gallery)findViewById(R.id.photogallery);
        
        myGalleryBaseAdapter = new GalleryBaseAdapter(this);
        
        String ExternalStorageDirectoryPath = Environment
    .getExternalStorageDirectory()
    .getAbsolutePath();
        
        String targetPath = ExternalStorageDirectoryPath + "/test/";
        
        Toast.makeText(getApplicationContext(), targetPath, Toast.LENGTH_LONG).show();
        File targetDirector = new File(targetPath);
        
        File[] files = targetDirector.listFiles();
        for (File file : files){
         myGalleryBaseAdapter.add(file.getPath());
  }
        
        myPhotoGallery.setAdapter(myGalleryBaseAdapter);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    
}


layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Gallery
        android:id="@+id/photogallery"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>


Download the files.

Next:
- Implement Android Gallery widget with scale-down bitmap

Related:
- Implement Gallery-like HorizontalScrollView.


Thursday, July 12, 2012

Android Accessory Development Kit (ADK) for 2012

The Android Accessory Development Kit (ADK) for 2012 is the latest reference implementation of an Android Open Accessory device, designed to help Android hardware accessory builders and software developers create accessories for Android.



The ADK 2012 is based on the Arduino open source electronics prototyping platform and is an open hardware design. The hardware design files and firmware source code are included with the ADK software download. The ADK contains two main physical hardware components:
  1. Main processing board containing the microprocessor, USB connections, power connector and input/output pins. This board can be removed and used separately from the rest of the hardware.
  2. Shield containing sensors, LEDs, input controls, audio amplifier and speaker output, contained in a custom, polygon box enclosure.
The main hardware features of the ADK are as follows:
  • An ARM 32-bit Cortex M3 micro-processor
  • Separate USB connections for an Android device and computer connection for programming and debugging
  • Sensors for light, color, proximity, temperature, humidity, barometric pressure, and acceleration
  • Micro SD Card slot
  • Bluetooth support
The ADK comes preloaded with an alarm clock firmware program that you can use immediately. A companion Android application, ADK 2012, is available on Google Play. The source code for both the Android application and the ADK firmware (an Arduino sketch) can be downloaded from this page.
The ADK 2012 also comes with additional parts to help you develop accessories with it, including:
  • AC power adapter
  • USB A to Micro USB B connector cable
  • Micro USB B to Micro USB AB connector (small, rectangular plug)
  • Micro SD Card, preinstalled in the ADK SD Card socket


Google I/O 2012 - ADK 2.0
Introducing the new APIs and capabilities in ADK 2.0, with demos.