Wednesday, December 22, 2010

How to compile Launcher / Calendar or any packages/apps from AOSP in eclipse...!!

How to compile the launcher or any app from packages/apps from eclipse

File->New > Android Project-> Create project from existing workspace ->
-> Point to any of the application in AOSP/packages/apps/AlarmClock (or) Calendar any app.

Now you would be facing many compilation errors for most of the apis.
Currently, You would be working with Android SDK.So it would be linking against android.jar provided from SDK version, in which these apis wont be available.


To make it compile in eclipse, you need to do following steps.



1 - when 'make' is made at root of AOSP, certain jar files will be created in /out directory.

/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes-full-debug.jar

2 - In eclipse, right click your project , select properties -> JAVA Build path ->Libraries tab.
You would have option as 'Add Library' in the side.
Click it. Then select 'User Library'-> 'user libraries'.

then click 'New' and give a name to it.
Then select the newly entered name and select 'Add jars' options in the side. Choose the path of the classes-debug.jar.

3 - Back to Java Build path window. Go to 'Order and export' make the newly added library to be at the top.



Done...!! Now you are ready to compile and debug as well...!!



Note :
'classes-full-debug.jar' file is nothing but collection of .classes files required for compilation. But when you want to run the apk build using this method ( eclipse ) on the emulator, we need the corresponding dex files to be present. DEX files are nothing but dalvik compiled files, which android OS requires for running your application. So you need to push the framework.jar created from compiling 'make' @ root of AOSP to the emulator /system/framework.

go to /out/target/product/generic/system/framework/framework.jar

adb -s emulator-5554 remount
adb -s emulator-5554 push framework.jar /system/framework
adb -s emulator-5554 shell reboot.



For reference eclipse screen shots:











Monday, December 20, 2010

Button press / Button states (images) handling in custom View

In this post, i am going to tell you all how to implement,generate button press events in a custom view.

http://developer.android.com/reference/android/widget/Button.html

"You can replace the button's background image with a state list drawable. A state list drawable is a drawable resource defined in XML that changes its image based on the current state of the button. Once you've defined a state list drawable in XML, you can apply it to your Button with the android:background attribute."

How do we simulate these states, without the state list drawable resource...:)

1 - First i am taking two buttons, and its corresponding images for different states.

2 - So ultimately we need to draw different button images when pressed on the Button Image and when press is released from it. So we need to change the (int) button_states with either of the above constants on touch events.

final int state_pressed = 1;
final int state_normal = 2;
final int state_enabled = 3;
final int state_disabled = 4;

3 - And in onDraw() method, based on the button_state value we pick up the corresponding bitmap and draw it.

4 - We change the button_state value based on touch events. So we need to override
onTouchEvent() api to receive the button press events.When we get

   
ACTION_DOWN - set button_state = state_pressed
ACTION_MOVE - set button_state = state_pressed // Means user keeps pressing the button.
ACTION_UP - set button_state = state_normal


5 - Since we have more than one buttons on the screen, we need to keep track of each of button positions on the screen with the 'Region' variables.And keep button_state1,button_state2 for the corresponding buttons on the screen.

6 - When the onTouchEvent() is invoked for any press events on the screen, check is the co-ordinates falls in which 'Region' of the button, then change the corresponding button_state values. Call invalidate() on each touch events, which calls the 'onDraw()' wherein based on the button_state we draw diff button images, bringing the user the illusion button is pressed.

Note: Since bitmaps are costly in terms of memory, we shouldnt be keep creating bitmaps for each draw() call for switching the button image. You program will crash.
So we need to 'recycle()' the old bitmap and create a new one and assign to it.




switch(buttonState1)
{
case state_pressed:
button2.recycle();
button2 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.forward_pressed);
break;
case state_normal:
button2.recycle();
button2 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.forward_default);
break;
case state_enabled:
button2.recycle();
button2 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.forward_default);
break;
case state_disabled:
//button2.recycle();
//button2 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.back_pressed);
break;
}


Here is the full code example what i have tried out.

kept these images in drawable directory,
back_default.png, back_pressed.png, back_disabled.png
forward_default.png, forward_pressed.png.




buttonpress.java
------------------


package com.android.buttonpress;


import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;

public class buttonpress extends Activity {


public class DisplayView extends View
{
int positionX = 5;
int positionY = 15;
Paint mPaint;
final int state_pressed = 1;
final int state_normal = 2;
final int state_enabled = 3;
final int state_disabled = 4;
int buttonState;
int buttonState1;
Region region;
Region region1;
Rect buttonRect;
Rect buttonRect1;
Bitmap button1;
Bitmap button2;
Context mContext;
DisplayView(Context context)
{
super(context);
mContext = context;
mPaint = new Paint();
mPaint.setTextSize(25);
mPaint.setColor(0xFF0000FF);
mPaint.setTextSize(16);
buttonState1 = state_normal;
buttonState = state_normal;
buttonRect = new Rect(10,30,210,110);
buttonRect1 = new Rect(10,150,210,230);
region = new Region(buttonRect);
region1 = new Region(buttonRect1);
button1 = BitmapFactory.decodeResource(context.getResources(), R.drawable.back_default);
button2 = BitmapFactory.decodeResource(context.getResources(), R.drawable.forward_default);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN: {
if(region.contains((int)event.getX(), (int)event.getY()) == true)
{
buttonState = state_pressed;
}
else if(region1.contains((int)event.getX(), (int)event.getY()) == true ){
buttonState = state_disabled;
buttonState1 = state_pressed;
}
invalidate();
break;
}
case MotionEvent.ACTION_UP: {
if(region.contains((int)event.getX(), (int)event.getY())== true)
{
buttonState = state_normal;
}
else if(region1.contains((int)event.getX(), (int)event.getY()) == true){
buttonState = state_normal;
buttonState1 = state_normal;
}
invalidate();
break;
}
}
return true;
}
@Override
public void onDraw(Canvas canvas)
{

canvas.drawText("Custom button press", positionX, positionY, mPaint);
switch(buttonState)
{
case state_pressed:
button1.recycle();
button1 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.back_pressed);
break;
case state_normal:
button1.recycle();
button1 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.back_default);
break;
case state_enabled:
button1.recycle();
button1 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.back_default);
break;
case state_disabled:
button1.recycle();
button1 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.back_disabled);
break;
}
canvas.drawBitmap(button1,null,buttonRect,mPaint);

switch(buttonState1)
{
case state_pressed:
button2.recycle();
button2 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.forward_pressed);
break;
case state_normal:
button2.recycle();
button2 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.forward_default);
break;
case state_enabled:
button2.recycle();
button2 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.forward_default);
break;
case state_disabled:
//button2.recycle();
//button2 = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.back_pressed);
break;
}
canvas.drawBitmap(button2,null,buttonRect1,mPaint);
}
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DisplayView view = new DisplayView(this);
setContentView(view);
}
}

Sunday, December 12, 2010

Custom Fade in - Fade out animation in a custom view

I was in a situation to implement a fade in - fade out animations between two pictures
in a custom view.

I have an array of bitmaps passed to my custom view. I need to do animation between the images.

1st Image - Shown first. 2nd Image - is Lying behind it.

Required sequence of image display is like below




1st Image - Fades out
2nd Image - Fades in // Both actions parallely

As soon as 2nd image is seen, bring the 3rd image and draw it behind the 2nd image.

2nd Image - Fades out
3rd Image - Fades In // Both actions parallely.

Considering 4 images are in the array.

4th Image - Fades out
1st Image - Fades In. // So this loop continues with number of images in the array.



Lets see how we can achieve this...!!

1 - First thing to know is, using 'Paint' instance we can set alpha value for the paint. Which means,



if alpha -> 0, then the content drawn using this paint will not be seen at all.
Complete transparency will be provided.

if alpha -> 255, then the content will be shown without transparency. i.e literally means, u cannot see contents if any drawn behind it.

if alpha -> 100, then paritally you can see some content(if any) behind the current content.



Using this setAlpha() api of paint, fade -in & fade -out of two images can be applied simultaneously.

2 - We need to do the animation repeatedly over a peroid of time. So we need either a thread or runnable. Since thread is costly, runnables are preferred. In this case we need two threads.



1 - Perform / Decide, when to change the pictures. ( ex. every 5 sec)
2 - Change the alpha values of foreground / backgroud images to bring the fade out - fade in effect. ( ex. 500 milli seconds each alpha value persists )



3 - We need to create two paints, one for foreground image and another for background image.
i.e Paint mForeGroundPaint;
Paint mBackGroundPaint;

4 - Load the bunch of images you need to shuffle across in a ArrayList.

5 - Have a global picCount variable to keep track of current count of images in the arrayList.

6 - Important stuff to remember. Since we are using runnables, which is different from main UI thread, we need to have a handler to send a message to main thread and call the drawing of foreground / background images. Else you will see a crash.

7 - Once the picCount reaches > ArrayList count, make it to zero and shuffle the foreground / background images accordingly.

8 - This runnables will keep running as long the instance of 'customView' life.So make sure when you want to stop it, remove the Runnable from the Handler.

mHandler.removeCallbacks(mAnimationTask);



fadeinout.java
-----------------

  

package com.android.fadein;

import java.util.ArrayList;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Bitmap.Config;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;

public class fadeinout extends Activity {

public class customview extends View
{
Paint mPaint;
Paint mForeGroundPaint;
Paint mBackGroundPaint;
Rect rect;
ArrayList attachmentList;
int picCount;
int foregrndalpha = 255;
int backgrndalpha = 0;
Runnable mAnimationTask;
H mHandler = new H();
Bitmap contentBitmap;
Canvas Backgroundcanvas;
Bitmap mAttachmentbackGround;
Bitmap mAttachmentforeGround;

Context mContext;
public customview(Context context)
{
super(context);
mContext = context;
mPaint = new Paint();
mPaint.setTextSize(25);
// set Color- blue
mPaint.setColor(0xFF0000FF);
mPaint.setTextSize(16);
mForeGroundPaint = new Paint();
mBackGroundPaint = new Paint();

attachmentList = new ArrayList();
attachmentList.add(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.bear));
attachmentList.add(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.tiger));
attachmentList.add(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.bird));
attachmentList.add(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.fish));

/* assign the background, foreground images */
picCount = 0;
mAttachmentforeGround = attachmentList.get(picCount);
mAttachmentbackGround = attachmentList.get(picCount+1);

contentBitmap = Bitmap.createBitmap( 300,300, Config.ARGB_8888);
Backgroundcanvas = new Canvas(contentBitmap);

foregrndalpha = 255;
rect = new Rect();
rect.left = 10;
rect.top = 30;
rect.right = 310;
rect.bottom = 330;

populateAttachmentBitmap();
fadeInfadeOutImages();
}

/* Periodically changes the mForeGroundPaint alpha value to bring transparency so that it looks like
* forground image is fading out and background image is fading in
*/

public void fadeInfadeOutImages()
{
mAnimationTask = new Runnable()
{

public void run()
{
if(foregrndalpha > 0 )
{
if (foregrndalpha == 255) { // 155
foregrndalpha -= 100;
backgrndalpha = 100;
} else { //100
foregrndalpha -= 155;
backgrndalpha = 255;
}

mForeGroundPaint.setAlpha(foregrndalpha);
mBackGroundPaint.setAlpha(backgrndalpha);
mHandler.sendEmptyMessage(0);
mHandler.postDelayed(mAnimationTask, 500);
}
else
{
foregrndalpha = 255;
backgrndalpha = 0;
/* Once the alpha reaches zero, its time to change the background, foreground images */
if(++picCount < attachmentList.size())
{
mAttachmentforeGround = mAttachmentbackGround;
mAttachmentbackGround = attachmentList.get(picCount);
}
else
{
picCount = 0;
mAttachmentforeGround = mAttachmentbackGround;
mAttachmentbackGround = attachmentList.get(picCount);
}

mForeGroundPaint.setAlpha(foregrndalpha);
mBackGroundPaint.setAlpha(backgrndalpha);
mHandler.sendEmptyMessage(0);
mHandler.postDelayed(mAnimationTask, 3000);
}

}
};
mHandler.postDelayed(mAnimationTask, 5000);

}

/* All email animation changes to the UI thread must be sent
* via this handler to GridElementView
*/

class H extends Handler
{
public void handleMessage(Message m)
{
if(m.what == 0)
{
System.out.println("Handle message");
populateAttachmentBitmap();
invalidate();

}

}
}

public void populateAttachmentBitmap () {

Backgroundcanvas.drawBitmap(mAttachmentbackGround, null, rect, mBackGroundPaint);
Backgroundcanvas.drawBitmap(mAttachmentforeGround, null, rect, mForeGroundPaint);

}

@Override
public void onDraw(Canvas canvas)
{
canvas.drawText("Welcome to custom fadein - fadeout image animation", 10,20, mPaint);
canvas.drawBitmap(contentBitmap, null,rect, mPaint);
}

}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
customview view = new customview(this);
setContentView(view);
}
}

Monday, December 6, 2010

Custom GridView in a dialog...!!

Everybody's aware that inside a dialog, a custom view can be set.

In this post, i am going to discuss how do we set a custom gridview as the custom view for dialog.

My requirement was to display a grid of options for the user to pick up a categories with a icons shown in a 3x3 matrix. User can choose a one by touching it. And the dialog had a title and at the right end a close button to dismiss the dialog.

I will take through step by steps.

1 - Dialog

onCreateDialog(int id) --> Is the function which will be called by framework,when an activity has implmented and showDialog(int id ) is called on activity's instance.
So we go ahead and implement this onCreateDialog(int id) function.

2 - Layouts

The view can be split into two parts.

- LinearLayout vertical orientation holding two elements.

- RelativeLayout - Title and Exit icons at the right extreme.
- GridView - Display 3x3 icons& text as a view.

CategoryDialog.xml
----------------------



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_root"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
>
<RelativeLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="3dip" >
<ImageView android:id="@+id/close"
android:layout_width="30dip"
android:layout_height="30dip"
android:layout_alignParentRight="true"
android:layout_marginRight="3dp"
android:src="@drawable/exit"
/>
<TextView android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="3dp"
android:textColor="#FFF"
android:textSize="20dip"
android:text="Choose Categories"/>
</RelativeLayout>

<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:columnWidth="90dp"
android:numColumns="3"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:stretchMode="columnWidth"
android:gravity="center"/>
</LinearLayout>




3 - Creating Dialog

Lets use the AlertDialog and set the categoryDialog.xml layout to its setView() api to show the content. what we are exepecting.

Use inflater to create a view by inflating R.layout.categorydialog



AlertDialog.Builder builder;
Context mContext = this;
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.categorydialog,(ViewGroup) findViewById(R.id.layout_root));
builder = new AlertDialog.Builder(mContext);
builder.setView(layout);
dialog = builder.create();
return dialog;




4- Creating GridView.

View layout = inflater.inflate(R.layout.categorydialog,(ViewGroup) findViewById(R.id.layout_root));
GridView gridview = (GridView)layout.findViewById(R.id.gridview);
gridview.setAdapter(new ImageAdapter(this));

- Get the gridView instance from the inflated layout and set a adapter object to draw its content.

- We need to implement a BaseAdapter and when the adapter instance is set to GridView , GridView queries for how many elements and what are their each view to display in the Grid.

- Each view in the grid is Icon (image) and a text corresponding to the category. So we need to create one more layout xml file to hold these content.

categoryContent.xml
--------------------



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
>
<ImageView android:id="@+id/categoryimage"
android:layout_width="50dip"
android:layout_height="50dip"
android:layout_alignParentRight="true"
android:layout_marginRight="3dp"/>
<TextView android:id="@+id/categoryText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="3dp"
android:textColor="#FFF"/>

</LinearLayout>



5 - Create a view inflating this layout xml file and set the LayoutParams for each cell size as 90x90.



convertView = mInflater.inflate(R.layout.categorycontent, null);
convertView.setLayoutParams(new GridView.LayoutParams(90, 90));


Note: Since this a view which will be set in GridView as one element of Grid, the layout params must be of type GridView.LayoutParams. This is very important otherwise, the programs will crash in run-time.




public class ImageAdapter extends BaseAdapter {
private Context mContext;
private LayoutInflater mInflater;
public ImageAdapter(Context c) {
mInflater = LayoutInflater.from(c);
mContext = c;
}
public int getCount() {
return mThumbIds.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) { // if it's not recycled,
convertView = mInflater.inflate(R.layout.categorycontent, null);
convertView.setLayoutParams(new GridView.LayoutParams(90, 90));
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.categoryText);
holder.icon = (ImageView )convertView.findViewById(R.id.categoryimage);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.icon.setAdjustViewBounds(true);
holder.icon.setScaleType(ImageView.ScaleType.CENTER_CROP);
holder.icon.setPadding(8, 8, 8, 8);
holder.title.setText(categoryContent[position]);
holder.icon.setImageResource(mThumbIds[position]);
return convertView;
}
class ViewHolder {
TextView title;
ImageView icon;
}
// references to our images
private Integer[] mThumbIds = {
R.drawable.beer, R.drawable.hotel,R.drawable.shopping,
R.drawable.theatre,R.drawable.train, R.drawable.taxi,
R.drawable.gas, R.drawable.police,R.drawable.hospital
};

}
private String[] categoryContent = {
"Pubs", "Restuarants","shopping",
"theatre","train", "taxi",
"gas", "police","hospital"
};
}





griddialog.java
--------------------

Everybody's aware that inside a dialog, a custom view can be set.

In this post, i am going to discuss how do we set a custom gridview as the custom view for dialog.

My requirement was to display a grid of options for the user to pick up a categories with a icons shown in a 3x3 matrix. User can choose a one by touching it. And the dialog had a title and at the right end a close button to dismiss the dialog.

I will take through step by steps.

1 - Dialog

onCreateDialog(int id) --> Is the function which will be called by framework,when an activity has implmented and showDialog(int id ) is called on activity's instance.
So we go ahead and implement this onCreateDialog(int id) function.

2 - Layouts

The view can be split into two parts.

- LinearLayout vertical orientation holding two elements.

- RelativeLayout - Title and Exit icons at the right extreme.
- GridView - Display 3x3 icons& text as a view.

CategoryDialog.xml
----------------------



package com.android.griddialog;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class griddialog extends Activity {
public final int CATEGORY_ID =0;
private Context mContext;
Dialog dialog;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getApplicationContext();
setContentView(R.layout.main);
Button button = (Button)findViewById(R.id.categories);
button.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v) {
showDialog(CATEGORY_ID);
}
});
}
protected Dialog onCreateDialog(int id) {

switch(id) {

case CATEGORY_ID:

AlertDialog.Builder builder;
Context mContext = this;
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.categorydialog,(ViewGroup) findViewById(R.id.layout_root));
GridView gridview = (GridView)layout.findViewById(R.id.gridview);
gridview.setAdapter(new ImageAdapter(this));

gridview.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView parent, View v,int position, long id) {
Toast.makeText(v.getContext(), "Position is "+position, 3000).show();
}
});

ImageView close = (ImageView) layout.findViewById(R.id.close);
close.setOnClickListener(new View.OnClickListener() {
public void onClick(View v){
dialog.dismiss();
}
});

builder = new AlertDialog.Builder(mContext);
builder.setView(layout);
dialog = builder.create();
break;
default:
dialog = null;
}
return dialog;
}

public class ImageAdapter extends BaseAdapter {
private Context mContext;
private LayoutInflater mInflater;
public ImageAdapter(Context c) {
mInflater = LayoutInflater.from(c);
mContext = c;
}
public int getCount() {
return mThumbIds.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) { // if it's not recycled,
convertView = mInflater.inflate(R.layout.categorycontent, null);
convertView.setLayoutParams(new GridView.LayoutParams(90, 90));
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.categoryText);
holder.icon = (ImageView )convertView.findViewById(R.id.categoryimage);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.icon.setAdjustViewBounds(true);
holder.icon.setScaleType(ImageView.ScaleType.CENTER_CROP);
holder.icon.setPadding(8, 8, 8, 8);
holder.title.setText(categoryContent[position]);
holder.icon.setImageResource(mThumbIds[position]);
return convertView;
}
class ViewHolder {
TextView title;
ImageView icon;
}
// references to our images
private Integer[] mThumbIds = {
R.drawable.beer, R.drawable.hotel,R.drawable.shopping,
R.drawable.theatre,R.drawable.train, R.drawable.taxi,
R.drawable.gas, R.drawable.police,R.drawable.hospital
};

}
private String[] categoryContent = {
"Pubs", "Restuarants","shopping",
"theatre","train", "taxi",
"gas", "police","hospital"
};


}

Friday, December 3, 2010

slide up /down translate

Many would be interested to perform some kindaa of slide up or bottom up animation of images on a button click.

Translate animation can be used to perform this.
public TranslateAnimation (float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)

FromX - 0
ToX - 0
FromY - 100
ToY - 0

// This means from the height of 100px, the animation will move up the view upwards untill toY becomes 0. THis looks like the view is moved from bottom up or slide up

What i tried out here

1 - A Linearlayout of height 100px is created and set a edit text inside it.
Intially edit text is set to invisible state.So that first time, application
started you will see nothing.
2 - On the onlick() of Button object
- created a translate animation with fromY = 100
- Set the fillafter(true) so that when animation ends, the position of view will be retained. else the view will not be visible.

3 - start the animation on the edit text object.



public void onClick(View v) {
edit.setVisibility(View.VISIBLE);
TranslateAnimation slide = new TranslateAnimation(0, 0, 100,0 );
slide.setDuration(1000);
slide.setFillAfter(true);
edit.startAnimation(slide);
}
});






edittext.java
----------------



package com.android.edittext;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.EditText;

public class edittext extends Activity {
Button b;
EditText edit;
View layout;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
edit = (EditText)findViewById(R.id.edittext);
b = (Button)findViewById(R.id.button);
layout = (View) findViewById(R.id.layout);
b.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
edit.setVisibility(View.VISIBLE);
TranslateAnimation slide = new TranslateAnimation(0, 0, 100,0 );
slide.setDuration(1000);
slide.setFillAfter(true);
edit.startAnimation(slide);
}
});
}
}





layout.xml
------------


<?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"
/>
<Button
android:id="@+id/button"

android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="slideup"
/>

<LinearLayout
android:id="@+id/layout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="100px"
>

<EditText
android:id="@+id/edittext"
android:visibility="gone"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollHorizontally="true"
/>

</LinearLayout>

</LinearLayout>

Thursday, November 25, 2010

How to disable title bar in portrait / Landscape mode

This post talks about how to hide/show title bar in portrait and landscape.

There are scenarios where we might want to display a title bar in portrait and not in landscape due to space limitations in landscape.

How do we do this ??

- Every time a screen is rotated from landscape to portrait or vice versa, the activity is destroyed and created again.

- In onCreate(), check the screen sizes and decide it is in portrait or Landscape.

- Based on the mode, set the Window attribute Window.FEATURE_NO_TITLE using requestWindowFeature() api.

Note that requestWindowFeature() api needs to be called before setting the content view.



onCreate()
{
setTitleBar(); --> Call to decide to set whether title bar or not.
setContentView(R.layout.main);
}

public void setTitleBar()
{
Display display = ((WindowManager)mContext.getSystemService(mContext.WINDOW_SERVICE)).getDefaultDisplay();

int width = display.getWidth();
int height = display.getHeight();


if(width > height)
{
/* In Landscape */
requestWindowFeature(Window.FEATURE_NO_TITLE);
}

}

Wednesday, November 10, 2010

Using canvas to draw a text whereve a click is made on screen.

How to draw a text wherever a click is made.

- A custom view is implemented, which overrides onDraw() function call.

- To get the touch point (x,y) on screen, override the onTouchEvent() and check for action ACTION_DOWN and update the global variables x,y.

- Later in onDraw(), on the canvas object supplied from framework, use the drawText api to draw the text on the touched point.

- A paint object is initialised with the color and font size values. Use this in drawText() api.



drawText(String text, float x, float y, Paint paint)
Draw the text, with origin at (x,y), using the specified paint.



- Create a instance of this custom view and set this as setContentView() of activity to display this view.



imagemove.java
---------------



package com.android.imagemove;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;

public class imagemove extends Activity {

public class DisplayView extends View
{
int positionX = 5;
int positionY = 15;
Paint mPaint;
DisplayView(Context context)
{
super(context);
mPaint = new Paint();
mPaint.setTextSize(25);
mPaint.setColor(0xFF0000FF);
mPaint.setTextSize(16);

}
@Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN: {
positionX = (int)event.getX();
positionY = (int)event.getY();

invalidate();
}
}
return true;
}
@Override
public void onDraw(Canvas canvas)
{
System.out.println("X & Y"+positionX+":"+positionY);
canvas.drawText("Welcome", positionX, positionY, mPaint);
}
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DisplayView view = new DisplayView(this);
setContentView(view);
}

}

Monday, November 8, 2010

How to apply animations when activity enters & exits in framework.??

Every time when an activity is started / entered first time, or when an activity is exited, a simple animation is performed by the framework.Currently framework performs simple fade-in and fade-out animations.

What if we like to apply our own animations when activity screen is first launched.?

It is possible. I will point out the places where you need to modify to apply you animations.

1 - Through xml

If you like to apply animations through xml then write your animation logics in these files.


frameworks/base/core/res/res/anim/activity_open_enter.xml
frameworks/base/core/res/res/anim/activity_open_exit.xml
frameworks/base/core/res/res/anim/activity_close_enter.xml
frameworks/base/core/res/res/anim/activity_open_exit.xml


These animations are given style item names in styles.xml



<style name="Animation.Activity">
<item name="activityOpenEnterAnimation">@anim/activity_open_enter</item>
<item name="activityOpenExitAnimation">@anim/activity_open_exit</item>
<item name="activityCloseEnterAnimation">@anim/activity_close_enter</item>
<item name="activityCloseExitAnimation">@anim/activity_close_exit</item>


These names are referred in frameworks while picking the resource id for animations.
ex: com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation
com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;

2 - Custom animation class.

frameworks/base/services/java/com/android/server/WindowManagerService.java

This file in services of framework is where animation is set and performs the animations for activity enter & exit.
There are two places animations are set one for activities and one for windows, where activity view is attached. Parent of all views.


private boolean applyAnimationLocked(AppWindowToken wtoken,
WindowManager.LayoutParams lp, int transit, boolean enter) --> Activity

private boolean applyAnimationLocked(WindowState win,
int transit, boolean isEntrance) ----> Windows


I added logs in both places and found that in applyAnimationLocked for activity



// Only apply an animation if the display isn't frozen. If it is
// frozen, there is no reason to animate and it can cause strange
// artifacts when we unfreeze the display if some different animation
// is running.


It dint enter into the main 'if' to set the animations. I think it is because of the above said reason.!!

So i set the animation for main window itself in the other 'applyAnimationLocked' for windows.

For testing, i took the Rotate3dAnimation.java class provided in android-sdk samples under API-Demos folder.

Create a instant of this class and set the animation.



Added the below code at line no 2739

Display display = ((WindowManager)mContext.getSystemService(mContext.WINDOW_SERVICE)).getDefaultDisplay();

int width = display.getWidth();
int height = display.getHeight();

final float centerX = width.getWidth() / 2.0f;
final float centerY = height.getHeight() / 2.0f;

// Create a new 3D rotation with the supplied parameter
// The animation listener is used to trigger the next animation
final Rotate3dAnimation rotation =
new Rotate3dAnimation(0, 0, centerX, centerY, 310.0f, true);
rotation.setDuration(500);
rotation.setFillAfter(true);
a = rotation;

win.setAnimation(a);
win.mAnimationIsEntrance = isEntrance;



This animation will be applied to all switch cases check,,which is window enter, window exit.. etc..



switch (transit) {
case WindowManagerPolicy.TRANSIT_ENTER:
attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
break;
case WindowManagerPolicy.TRANSIT_EXIT:
attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
break;
case WindowManagerPolicy.TRANSIT_SHOW:
attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
break;
case WindowManagerPolicy.TRANSIT_HIDE:
attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
break;



Now you can observe animations are applied to all types of windows...like dialog, error notes, all activitie..!!

By this you can define your own animation extending 'Animation', it can be even 3D animation and can be applied to activity enter and exit... !!

Hope it helps someone who works in framework and trying to apply animation when activity enters....!!

Sunday, November 7, 2010

Invisible activity - Why status bar is not focusable ?

I was in a situation to launch a keyboard, when a tap happens on the edittext on the status bar.
I introduced a editText in the status bar. And expected framework to launch the keyboard when tapped. But unfortunately framework doesnt popup the keyboard.

I posted in android-developers forums and got some clue by android framework engineers.

1 - When status bar view is added to windowmanager, windowManager layoutparams is set with the flag 'WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE '.

frameworks/base/services/java/com/android/server/status/StatusBarService.java



public void systemReady() {
System.out.println("Statusbar service - systemReady ");
final StatusBarView view = mStatusBarView;
WindowManager.LayoutParams lp = new
WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
view.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height),
WindowManager.LayoutParams.TYPE_STATUS_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING,
mPixelFormat);
lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
lp.setTitle("StatusBar");
lp.windowAnimations = R.style.Animation_StatusBar;
WindowManagerImpl.getDefault().addView(view, lp);
}


FLAG_NOT_FOCUSABLE Window flag: this window won't ever get key
input focus, so the user can not send key or other button events to
it.

http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_NOT_FOCUSABLE

2 - It is decided by the framework that status bar will not receive any focusable events. You can handle the touch, but it will not get any focus.!!

- I tried uncommenting that flag and checked. Then status bar window received focus, where as the other windows mainly - phone Window, where the activity window resides doesnt receive focus, so literally i couldnt do anything on the screen....:)

for more details refer here

http://groups.google.com/group/android-platform/browse_thread/thread/385fa0ede79fd7f8/e286f2ff0e6f1c16#e286f2ff0e6f1c16

So i decided to a write a invisible activity :)

Means like, if somebody taps on the status bar's edit text, i launch an activity and through that activity i launch the keyboard, giving user an illusion that keyboard is launched because of the tap on the edit text.

how do we achieve this ???

- To pop up the keyboard automatically when the activity is launched, we need a editText component in the activity.

- A runnable is created and it will be executed after the activity is launched to show the inputmethod (IME) using inputManager.Which gives user a illusion that keyboard is launched. Keyboard can be launched with any View element, Button, TextView, EditText..etc...But View has to be in foucs



@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// Launch the IME after a bit
mHandler.postDelayed(mShowInputMethodTask, 0); --> Launch the runnable
}
}

private Runnable mShowInputMethodTask = new Runnable() { -->Runnable
public void run() {
showInputMethodForQuery();
}
};

protected void showInputMethodForQuery() { -----> Shows the IME using a View
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(press, 0);
}
}



Lets see the complete example:

1- Create a simple View element in the layout.xml.. In this case i have created a button.

To make it invisible.

Apply the background, text property to color:transparent

android:textColor="@android:color/transparent"
android:background="@android:color/transparent"


Layout.xml:
------------



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>

<Button android:id="@+id/press"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/transparent"
android:background="@android:color/transparent"
android:text="@string/hello"/>

</LinearLayout>



Note:

When the keyboard is launched and when the user presses 'back' button, there is no way that inputmethodManager will let the application know that keyboard is exited.
So when back is pressed, keyboard will be dismissed and you could see a transparent window, where u cannot really do anything.

Basically it is the transparent activity which has button in it which is also a transparent one due to the settings of background color & text color.

So we need to handle the onTouchEvent() of activity and do an exit. ( this.finish() )



invisibleactivity.java
--------------------------



package com.android.urldisplay;

import android.app.Activity;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.view.Display;
import android.widget.LinearLayout;
import android.view.ViewGroup;
import android.view.Gravity;

public class invisibleactivity extends Activity
{

private static final int APP_ID = 0;
private Handler mHandler = new Handler();
Button press;

private Runnable mShowInputMethodTask = new Runnable() {
public void run() {
showInputMethodForQuery();
}
};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);


}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// Launch the IME after a bit
mHandler.postDelayed(mShowInputMethodTask, 0);
}
}
protected void showInputMethodForQuery() {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(press, 0);
}
}
public boolean onTouchEvent (MotionEvent event)
{
this.finish();
return true;
}
}




- In this approach we only launched the keyboard. But our aim was to handle the text typed in the keyboard and show it back to the editText on the status bar.

So for that instead of button we need to have a editText and launch the keyboard for this view (EditText ). and when the user presses 'Go' or 'Done' button get the text from this editText.

Will show you how to handle the button press on keyboard in coming blogs :)

How to dismiss a custom dialog based on touch points?


When we write a custom dialog class or set theme style/Theme.Dialog to activity, we might be interested in dismissing the dialog if user touches outside the dialog visible area.

This can be achieved through a special class, android.graphics.Region. It has a api contains(x,y) which is used to tell you if the passed x,y falls in the region defined.

1 - we need to override the public boolean onTouchEvent (MotionEvent event) function and get the current x,y the user touched on the screen.

2 - Define the region top left x,y and bottom right x,y of the rectangle region which you are interested in listening the touch events.

Region dialogRegion = new Region (10,12,183,179 );

3 - Dismiss the dialog using finish() if the touch points falls within this region() co-ordinates.

if( dialogRegion.contains(x, y) == true)
this.finish();

Here is the complete example.

This example shows that activity is set style - Theme.Dialog.


<activity android:name=".touch"
android:label="@string/app_name"
android:theme="@android:style/Theme.Dialog">


Size of the dialog is set using WindowManager layout params.

Depending on the height & width, i have set the region points.And in onTouchEvent() i check for the touch points, if within the region() points i simply call finish() which exits the activity.

touch.java
------------


package com.android.touch;

import android.app.Activity;
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.WindowManager;

public class touch extends Activity {
Region dialogRegion;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WindowManager.LayoutParams params = getWindow().getAttributes();

params.height = 200;
params.width = 200;


this.getWindow().setAttributes(params);

dialogRegion = new Region (10,12,183,179 );

}
public boolean onTouchEvent (MotionEvent event)
{

final int action = event.getAction();


final int x = (int) event.getX();
final int y = (int) event.getY();

switch (action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN: {
System.out.println("Action down"+x +"::"+y);
if( dialogRegion.contains(x, y) == true)
this.finish();

}
}

return true;
}
}

Thursday, October 21, 2010

Message handlers

I was facing a runtime exception like below. I couldnt figure out what was the mistake am doing. Then i went and did some google results and found out how to avoid this and why this exception happened.



E/AndroidRuntime( 60): *** FATAL EXCEPTION IN SYSTEM PROCESS: Timer-0
E/AndroidRuntime( 60): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
E/AndroidRuntime( 60): at android.view.ViewRoot.checkThread(ViewRoot.java:2802)
E/AndroidRuntime( 60): at android.view.ViewRoot.invalidateChild(ViewRoot.java:607)
E/AndroidRuntime( 60): at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:633)
E/AndroidRuntime( 60): at android.view.ViewGroup.invalidateChild(ViewGroup.java:2505)
E/AndroidRuntime( 60): at android.view.View.invalidate(View.java:5139)
E/AndroidRuntime( 60): at android.widget.TextView.checkForRelayout(TextView.java:5364)
E/AndroidRuntime( 60): at android.widget.TextView.setText(TextView.java:2688)
E/AndroidRuntime( 60): at android.widget.TextView.setText(TextView.java:2556)
E/AndroidRuntime( 60): at android.widget.TextView.setText(TextView.java:2531)
E/AndroidRuntime( 60): at com.android.server.status.StatusBarService$1$1.run(StatusBarService.java:403)
E/AndroidRuntime( 60): at java.util.Timer$TimerImpl.run(Timer.java:289)


Reason :

Every activity/application when started it creates a main UI thread to show the UI elements. When a new thread is created from main UI thread, and when UI elements where tried to access from this thread, then this exception will be thrown,
Only the original thread that created a view hierarchy can touch its views

So how do we acheive this,??

This scenario is common, we create a thread in main thread to do some light weight work, like fetching some data from net, to wait for some event from other process, and when the event is successful, we would like to update the UI elements.

Message handlers are used to achieve this.

http://developer.android.com/reference/android/os/Handler.html



"When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will than be scheduled in the Handler's message queue and processed when appropriate."



The above explanation makes it clear.

Each handler instance when created will be linked to the thread where it is created.Each thread has its own messageQueue and using this handler we can deliver the messages, runnables to this thread.

So by keeping the message handler object global, it can use used in other thread apart from main UI thread to deliver messages to main UI thread.

The below picture portrays what it is in high level picture.




I have written a simple example to explain the message handler. The application has one textview and one button.

On Click of the button, it creates a new thread() and fetches data from google local search about "pubs london" and when the response is received, this thread uses message handler to deliver the message event to main thread.(UI thread ).

Lets see in detail in code:

Layout.xml
--------------



<?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:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fetch Data" />
</LinearLayout>



handler.java
-------------



package com.android.handler;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class handler extends Activity {
private String responseText;
private TextView content;
private ProgressDialog MyDialog;
private Context mContext;
private final int UPDATE_TEXT = 0;
private Handler messageHandler = new Handler() {

public void handleMessage(Message msg) {
super.handleMessage(msg);

if(msg.what == UPDATE_TEXT)
{
MyDialog.dismiss();
content.setText(responseText);
}
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mContext = getApplicationContext();
content = (TextView)findViewById(R.id.text);
MyDialog = new ProgressDialog(this);
MyDialog.setMessage("Fetching...");
MyDialog.setIndeterminate(true);
MyDialog.setCancelable(true);

Button b = (Button)findViewById(R.id.button1);
b.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v)
{

MyDialog.show();
new Thread() {
public void run() {
try {
sendSearchRequest();
} catch (Exception e) {
}

}
}.start();

}

});
}
public void sendSearchRequest()
{
HttpClient client = new DefaultHttpClient();
String query = "http://ajax.googleapis.com/ajax/services/search/local?hl=en&v=1.0&rsz=8&q=pubs london&start=0";
try {
URL url = new URL(query);
URI uri = new URI(url.getProtocol(), url.getHost(), url.getPath(), url.getQuery(), null);
HttpGet request = new HttpGet(uri);
HttpResponse response = client.execute(request);
Userrequest(response);
}catch (URISyntaxException e){

}
catch(Exception ex){
content.setText("Failed expcetion");
}

}
public void Userrequest(HttpResponse response){

try{
InputStream in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder str = new StringBuilder();
String line = null;
while((line = reader.readLine()) != null){
str.append(line + "\n");
}
in.close();
responseText = str.toString();
messageHandler.sendEmptyMessage(UPDATE_TEXT);

}catch(Exception ex){
responseText = "Error";
}

}
}

Frame animation - How to know when animation ends?

In related to my earlier post how to use frame animation and implement a circular spinner, i noted a important factor of AnimationDrawable.

I wanted to do some work once the frame animation ends.I looked for animation listener for AnimationDrawable, unfortunately animationDrawable class doesnot have facility to listen for animationlistener object.

SO when googled i found out some suggestions and help in forums. So i am gonna share what i tried out.

1 - We need to calculate the total duration of each frame shown, using getNumberOfFrames() and getDuration() apis.

2 - As soon as the animation is started, Start a timer which will trigger timertask after the total duration. You can conclude now that animation would have ideally stopped. Then you can use stop api to stop the animation.



animation = new AnimationDrawable();

animation.addFrame(getResources().getDrawable(R.drawable.gradient_25), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_50), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_75), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_100), 500);
animation.setOneShot(true);

// Code continues................
// Code continues.................

Button start = (Button ) findViewById(R.id.start);
start.setOnClickListener(new OnClickListener ()
{
public void onClick(View v)
{
animation.start();

long totalDuration = 0;
for(int i = 0; i< animation.getNumberOfFrames();i++){
totalDuration += animation.getDuration(i);
}
Timer timer = new Timer();

TimerTask timerTask = new TimerTask(){
@Override
public void run() {

animation.stop();
}
}};
timer.schedule(timerTask, totalDuration);
}

});



Note: Once you call start on animation object, you need to call 'stop()' on the animation object to see the animation again when start() called again. otherwise, animation wont start.

Tuesday, October 19, 2010

Frame animation - circular spinner implementation

With respect to earlier post of how to implement progress bar using level-list drawable, today i will take you in steps of achieving a similar kinda of animation using Frame animation.

Level-list & animation-list both are used for changing the images.

<Level-list> has some meaning like based on some conditions, or level u can pick the up the image. Where as resource just shows the frame one by one with a duration applied to each frame.

1 - Take the images and keep in drawable directory. And write the animation xml file and place it in anim folder. (/res/anim )



<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" id="selected" android:oneshot="false">
<item android:drawable="@drawable/circular1" android:duration="500" />
<item android:drawable="@drawable/circular2" android:duration="500" />
<item android:drawable="@drawable/circular3" android:duration="500" />
<item android:drawable="@drawable/circular4" android:duration="500" />
<item android:drawable="@drawable/circular5" android:duration="500" />
<item android:drawable="@drawable/circular7" android:duration="500" />
<item android:drawable="@drawable/circular8" android:duration="500" />
<item android:drawable="@drawable/circular9" android:duration="500" />
<item android:drawable="@drawable/circular10" android:duration="500" />
<item android:drawable="@drawable/circular11" android:duration="500" />
<item android:drawable="@drawable/circular1" android:duration="500" />
</animation-list>



I have kept the circular spinner images in drawable directory.

android:duration - Tells in milliseconds a frame would be shown.
android:oneshot - If true, keep showing frames in a loop from starting to ending and then again starting. If false, when animation started, shows the frames only once from starting to ending.

2 - This animation can be applied to all widgets (to a linear layout, Imageview, Button etc. )

There are two approaches you can do this.

1 - Create the animation-list drawable and set it in the layout's background property / or set it in the code. ( 2 ways )



<LinearLayout android:id="@+id/urlBar"
android:layout_width="fill_parent"
android:layout_height="50px"
android:background="@anim/layoutanimation"
android:orientation="horizontal">

or

ViewGroup lyout = (ViewGroup)findViewById(R.id.urlBar);
layout.setBackgroundResource(R.anim.layoutanimation);




Next step is common for noth of the above cases to get the AnimationDrawable object.



AnimationDrawable animation;
animation = (AnimationDrawable) l.getBackground();
animtion.start();



2 - Another way is create a AnimationDrawable object and add frames to it. Then set the drawable object as backgroundDrawable to the widget.



AnimationDrawable animation;
animation.addFrame(getResources().getDrawable(R.drawable.circular1), 500);
animation.addFrame(getResources().getDrawable(R.drawable.circular2), 500);
animation.addFrame(getResources().getDrawable(R.drawable.circular3), 500);
animation.addFrame(getResources().getDrawable(R.drawable.circular4), 500);
animation.setOneShot(false);

ViewGroup layout = (ViewGroup)findViewById(R.id.urlBar);
layout.setBackgroundDrawable(animation);

animation.start();



One catch here, which every one must know is, if you expect your animation to start in the onCreate() of activity, then sorry, it will not start.

http://developer.android.com/guide/topics/graphics/2d-graphics.html

Please read at the end of the page. It says why it will not work. :)

Now lets look at full example.
1 - Apply frame animation to Linear Layout. [a set of gradient images will be applied]
2 - Apply frame animation to a Image View. [ circular spinner holder ]




layoutanimation.xml



<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" id="selected" android:oneshot="false">
<item android:drawable="@drawable/gradient_25" android:duration="500" />
<item android:drawable="@drawable/gradient_50" android:duration="500" />
<item android:drawable="@drawable/gradient_75" android:duration="500" />
<item android:drawable="@drawable/gradient_100" android:duration="500" />

</animation-list>



imageanimation.xml


<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" id="selected" android:oneshot="false">
<item android:drawable="@drawable/circular1" android:duration="500" />
<item android:drawable="@drawable/circular2" android:duration="500" />
<item android:drawable="@drawable/circular3" android:duration="500" />
<item android:drawable="@drawable/circular4" android:duration="500" />
<item android:drawable="@drawable/circular5" android:duration="500" />
<item android:drawable="@drawable/circular7" android:duration="500" />
<item android:drawable="@drawable/circular8" android:duration="500" />
<item android:drawable="@drawable/circular9" android:duration="500" />
<item android:drawable="@drawable/circular10" android:duration="500" />
<item android:drawable="@drawable/circular11" android:duration="500" />
<item android:drawable="@drawable/circular1" android:duration="500" />
</animation-list>



layout main.xml



<?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"
>
<LinearLayout android:id="@+id/urlBar"
android:layout_width="fill_parent"
android:layout_height="50px"
android:background="@anim/layoutanimation"
android:orientation="horizontal">

<Button android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="50px"
android:text="start" />

<Button android:id="@+id/stop"
android:layout_width="wrap_content"
android:layout_height="50px"
android:text="stop"/>
</LinearLayout>

<ImageView android:id="@+id/img"
android:layout_width="50px"
android:layout_height="50px"/>

</LinearLayout>



frameanimation.java



import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class frameanimation extends Activity {
AnimationDrawable animation;
AnimationDrawable animation1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
animation = new AnimationDrawable();

animation.addFrame(getResources().getDrawable(R.drawable.gradient_25), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_50), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_75), 500);
animation.addFrame(getResources().getDrawable(R.drawable.gradient_100), 500);
animation.setOneShot(false);


ViewGroup layout = (ViewGroup)findViewById(R.id.urlBar);
layout.setBackgroundDrawable(animation);

ImageView imageAnim = (ImageView) findViewById(R.id.img);
imageAnim.setBackgroundResource(R.anim.imageanimation);
animation1 = (AnimationDrawable) imageAnim.getBackground();

Button start = (Button ) findViewById(R.id.start);
start.setOnClickListener(new OnClickListener ()
{
public void onClick(View v)
{
animation.start();
animation1.start();
}

});
Button stop = (Button ) findViewById(R.id.stop);
stop.setOnClickListener(new OnClickListener ()
{
public void onClick(View v)
{
animation.stop();
animation1.stop();
}

});
}

}


Custom view class and fonts handling for drawing text.

This post tells about how to write a custom view, and how to deal with fonts heights,and things needs to taken care while implementing custom view.



I was trying to implement a layout which looked exactly like above.

It was a horizontal linear layout, where the fonts were cropped in top & bottom by a bit of pixels.

I was going in the following approaches.

1 - A horizontal linear layout which had two textviews. And set the textSize attribute to '24px', which was the height of the parent horizontal layout.Expected the string will start from the top corner of the parent layout (0,0). But it dint happen.



<LinearLayout
android:layout_width="fill_parent"
android:layout_height="24px"
android:orientation="horizontal"
android:background="#ffffff">

<TextView android:id="@+id/urltextbox1"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:text="90"/>

<TextView android:id="@+id/urltextbox2"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:text="12:24p"/>

</LinearLayout>



And the output was like below.




Now the problem is , the fonts doesnt start exactly at the top - corner ( 0, 0) of the parent ViewGroup. TextView leaves some space and then started the text.

Later i tried to set diff parameters/attributes of textView thinking it will push the fonts above the size.
- android:height = "28px" --> Dint work. Still it left some padding in the top.

- android:includeFontPadding="false" --> Some hope. It reduced some space in the top.Removed the font padding at the top. ( Mainly this is left by TextView for languages like german where characters will have something like this. )



Then finally decided to write a custom view class and draw the text using canvas.

Here two things are important, fontSize/textSize and drawText- Y co-ordinate, Which tells canvas from where the text to start for drawing.


- Create paint object. Set textsize, padding, textcolor etc.
- Override onMeasure(), OnDraw() functions.
- onMeasure() - set the required width and height.


Some calculations are required to acheieve the fonts being cropped at the top & bottom.

Layout height = 24px.

We need to calculate 'y' value of drawText() api, which determines the text drawing starting point.



canvas.drawText(content,leftPadding, y, mPaint);

If we start at 0, then fonts wont be seen only.
If we start at 24, then fonts wont grow out below the layout's width. But it can grow on if we keep the textSize more than 24.

So it has to be 3 or 4 px > 24, may be 28px. So that font drawing will start from 28px and 4px height will be cropped in the bottom. And keeping proper textSize, it will grow beyond height of 24px and it will be cropped in top as well.




- setScaling() takes a float value, which multiplied by layout heights gives the starting point of font in drawText api.

- setText() will take the text.



 

package com.android.server.status;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.graphics.Typeface;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.Rect;

public class FontFitTextView extends View {

private String content="";
private Paint mPaint;
private float scaling;
private float textSize=0.0f;
private int leftPadding= 0;

public FontFitTextView(Context context) {
super(context);
init();
}

public FontFitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

private void init(){

mPaint = new Paint();
// set's the paint's colour
mPaint.setColor(Color.rgb(0x58, 0x58, 0x58));
// smooth's out the edges of what is being drawn
mPaint.setAntiAlias(true);

mPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
}

public void setScaling(float scale)
{
this.scaling = scale;
}

public void setText(String text)
{
this.content = text;
invalidate();
}
public void setTextSize(float size)
{
this.textSize = size;
}
public void setPadding(int left,int right,int top,int bottom)
{
this.leftPadding = left;
}

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{

int height = MeasureSpec.getSize(heightMeasureSpec);

if(textSize == 0.0f )
{
textSize = height + ((height* 50 ) / 100);
mPaint.setTextSize(textSize);
}
else
mPaint.setTextSize(textSize);

int width =(int) mPaint.measureText(content) + leftPadding;
setMeasuredDimension(width,height);

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

float y = getHeight() * scaling;

if(textSize == 0.0f )
{
textSize = getHeight() + ((getHeight()* 50 ) / 100);
mPaint.setTextSize(textSize);
}
else
mPaint.setTextSize(textSize);

System.out.println(" y " + " " + y + " text size " + textSize+"padding "+ leftPadding);

canvas.drawText(content,leftPadding, y, mPaint);

}

}