Showing posts with label android-UI. Show all posts
Showing posts with label android-UI. Show all posts

Thursday, January 5, 2012

How to write custom CheckBox View?

Many would be interested in writing custom Checkbox in aligning to the theme of the application. For instance changing the checkbox background color and tick color.

You can do in many ways, one usual way is to define a drawables for different states and use selector resource to pick the right image for different states of checkbox.
and set the selector resource as background drawable to CheckBox.In this you need to have different sets of images.

This implementation is about having only two states, checked and not checked. ( basically meaning, how this widget behaves touched and touched again). My requirement was to have tick mark extending outside the checkbox square. Something like this.



So i planned to write a custom view, doing this job for me.

This implementation doesnt include flexible width, height for the checkbox. If you want to do so, use AttributeSet in the constructor and take the height and width from XML attributes and use those values in setMeasuredDimension().

- Written a custom View, a class extending 'View' class and drawing necessary things required to bring the checkbox effect.
- Since I needed 'tick' mark to cross the boundary of checkbox square, i have drawn the checkbox rectangle within View's boundary ( with rectangle width and height is set lesser than View's rectangle ), after drawing the checkbox rectangle then drawn the tick mark image which was positioned in such a way to occupy the entire View rectangle.

View Rect and CheckBox square Rect:



Tick Mark:




- I have given harcoded values of 48,48 for checkboxView,you can change this by gettin g values from xml.
- Overriden onTouchEvent() to know when to draw the tickmark and when not to draw the tick mark. Its a simple check with boolean variable.

- Provided a interface onCheckedChange() to let users listen for checkbox change events.

public interface onCheckedChange {
void onCheckClick(View v);
}

public void setChecklistener(onCheckedChange );

checkBoxView.java
--------------------



import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;

public class CheckBoxView extends View{

public interface onCheckedChange {
void onCheckClick(View v);
}
boolean isTouchDown = false;
Bitmap mTick;
Rect mViewRect;
Rect mCheckboxRect;
Paint mPaint;
int mHeight;

public CheckBoxView(Context context) {
super(context);
}

public CheckBoxView(Context context,AttributeSet attrs) {
super(context,attrs);
mTick = BitmapFactory.decodeResource(context.getResources(), R.drawable.tick);
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);
mHeight = metrics.heightPixels;
if(mHeight == 320 || mHeight == 240) {
mViewRect = new Rect(0,0,32,32);
mCheckboxRect = new Rect(0,12,25,32);
} else {
mViewRect = new Rect(0,0,48,48);
mCheckboxRect = new Rect(0,20,38,48);
}
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.rgb(0x60,0x33,0x11));
}

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
if(mHeight == 320 || mHeight == 240)
setMeasuredDimension(32,32);
else
setMeasuredDimension(48,48);
}

public boolean isChecked() {
return isCheckDrawn;
}

public void setCheckBox(boolean draw) {
isCheckDrawn = draw;
invalidate();
}
private onCheckedChange mCB = null;
public void setChecklistener(onCheckedChange aCB) {
mCB = aCB;
}

int currX;
boolean isMovementDetected = false;
boolean isCheckDrawn = false;

public boolean onTouchEvent(MotionEvent event) {

int pointerX = (int) event.getX();
int pointerY = (int) event.getY();
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(mCheckboxRect.contains(pointerX, pointerY) == true)
isTouchDown = true;
break;
case MotionEvent.ACTION_MOVE:
currX = (int) event.getX();
int deltaX = Math.abs(currX - pointerX);

if(deltaX > ViewConfiguration.getTouchSlop())
{
isMovementDetected = true;
}

break;
case MotionEvent.ACTION_UP:
if(isMovementDetected == false && isTouchDown == true) {
if(isCheckDrawn == false) {
isCheckDrawn = true;
} else {
isCheckDrawn = false;
}
if(mCB != null)
mCB.onCheckClick(this);
invalidate();
isTouchDown = false;
}
isMovementDetected = false;
break;
}
return true;
}

public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(mRect1, mPaint);
if(isCheckDrawn == true) {
canvas.drawBitmap(mTick, null, mRect, null);
}
}
}



Example usage of checkBoxView:
-------------------------------

Wednesday, November 30, 2011

how to write app Widgets - Home screen widgets ? How to implement different sized home screen widgets?

Recently i gave a presentation in google office singapore for gtug-ph-sg android community about Layouts and Home screen widgets.

http://gtug-ph-sg.blogspot.com/2011/11/android-talk-update.html

So i wished to share more details about how to write home screen widgets.
Also when i attended a google dev talk here happened on nov27th, the key steps to keep a user engaged in keeping your application is

1 - Write home screen widget and keep the user bsy in refreshing the data on widget
2 - Push notifications then and there so that user has not forgotten your application.

So we home screen widget is one way you can keep user bsy with your application. Lets go into the details.

* What is an AppWidget ?

* AppWidget framework

* Steps for creating an AppWidget

* Example demonstrating widget showing google static map of location as home screen widget.


AppWidget:

- AppWidget is a android techinical terminology for "Home screen widgets". What is said in developers guide is "App Widgets are miniature application views that can be embedded in other applications (such as the Home screen) and receive periodic updates."
-AppWidget is a representation of an Android application on the home screen. The application can decide, what representative information it publishes on the home screen



- Home screen widgets are quick glimpse of the fully functional apps like calendar,music,weather applicaiton. Users can quickly get live data from an application.Live data i mean, widgets can keep refreshing the data based on location,time basis, or per schedule input from user.

- Widgets let users interact with the widgets like slide the images like in gallery, scroll the weather data, play,pause songs..etc.






RemoteViews and Launcher:

Before diving into how to implement app widgets, we should be aware of how the appWidgets mechanism works and what are remoteviews ?

How does Launcher (i.e Home screen provider) able to show a view created by another application in its UI ? Functionality and design of the widget is defined by one application but UI of the widget is hosted in another application (Launcher). What would help us achieving this ? Basically we need an IPC mechanism here. Inter process communication. One process sending the UI data to another process Launcher to display in it.

Remoteviews helps in solving this and is the key behind appwidget framework. Remoteviews are parcelable data strucutre that holds information about a view hierarchy and can be transferred from one process to another. Any process can recieve this RemoteView instance via IPC and get a "View" instance from it and add to its Layout and be part of receving process. The creator of remoteview can define actions for the elements in remoteview like what should happen when a button is pressed. Receiving process cannot change these properties, they can only get a view instance and host it.

So who sends remoteviews and how does Launcher communicates with creator.

- AppWidgets framework includes implementing AppWidgetProvider class which is a broadcast receiver which would receive events of when an AppWidget is added to homescreen or deleted from homescreen.

- Launcher is the guy who sends these broadcasts when the appWidgets are added to the home screen with an AppWidgetID

- AppWidgetProvider then sends an RemoteView with the AppWidgetId, which would be then be received by Launcher and Launcher updates the corresponding appWidget with that remoteView.




AppWidget FrameWork

Now lets see the four necessary things involved in creating appWidgets.

* App Widget Provider Metadata XML file

* AppWidgetProvider class

* View layout

* App Widget configuration Activity (Optional)


App Widget Provider Metadata XML file

* Describes the metadata for an App Widget, such as minimum width, minimum height, update frequency, the AppWidgetProvider class.
* It also references App widget's layout file
* This should be defined in res/xml folder.

AppWidgetProvider class

* Defines the basic methods that allow you to programmatically interface with the App Widget, based on broadcast events. (AppWidgetProvider class is a child class of BroadcastReceiver class.)
* Through it, you will receive broadcasts when the ApWidget is updated, enabled, disabled and deleted

View layout

* Intial layout to be displayed when the appWidget is added to the homescreen. It is defined in metadata XML file.

App Widget configuration Activity

* This is an optional activity which users can define to show to the users before adding the appWidget to homescreen.Usually useful in collecting some values required for your appWidget settings.

Building an AppWidget

* Declare an AppWidgetProvider in the Manifestfile

* Create the App Widget Provider Info Metadata XML file

* Create the App Widget Layout XML file

* Write the AppWidgetProvider Class

1 - Declare AppWidgetProvider in Manifest




android:resource="@xml/example_appwidget_info"/>


2 - Create App Widget Provider Info Metadata XML file

xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dp"
android:minHeight="72dp"
android:updatePeriodMillis="86400000"  --> Every one day
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure" >


Some of the attributes are added newly in android 3.0
previewImage --> Added in android 3.0
android:previewImage="@drawable/preview"

More details take look at http://developer.android.com/reference/android/appwidget/AppWidgetProviderInfo.html

3 - Create App Widget Layout

* App Widget layouts are based on RemoteViews,which do not support every kind of layout or view widget.

* A RemoteViews object (and, consequently, an App Widget) can support the following layouts and Widget classes
FrameLayout, LinearLayout, RelativeLayoyt
AnalogClock, Button, Chronometer, ImageButton,
ImageView, ProgressBar, TextView

* Android 3.0 supports additional widgets
ListView
GridView
StackView
AdapterViewFlipper

4 - Write AppWidgetProvider Class

* The AppWidgetProvider class extends BroadcastReceiver to handle the App Widget broadcasts. The AppWidgetProvider receives only the event broadcasts that are relevant to this App Widget, such as when the App Widget is updated, deleted, enabled, and disabled.

Methods to override

onUpdate(Context, AppWidgetManager, int[]) - called
when each App Widget is added to a host (unless you use a configuration Activity), Typically the onlymethod that needs to be present

onDeleted(Context, int[])

onEnabled(Context)

onDisabled(Context)

onReceive(Context, Intent)

Screenshot showing demo of adding Digital and analog clock in Home screen:





AppWidget provider - onUpdate

There are certain few points about AppWidget Provider behaviour we need to know.

* First is , android:updatePeriodMillis="86400000" which defines the frequency when the appWidget will be updates. Meaning on this schedule, onUpdate on AppWidget Provider class will be called. The restriction with this timing is the minimum period is 30mins. You cannot give 10000 and except the onUpdate() to be called every 10 secs. Minimum time period is 30 mins.

* Because AppWidgetProvider is an extension of BroadcastReceiver, your process is not guaranteed to keep running after the callback methods returns i.e onUpdate,onEnabled.
So in case, you need to perform some network communication to fetch some data

* Consider starting a Service in the onUpdate() method. And delegate the network communication work to a asynchronous task and update the widget with the result.

* To update an appWidget all you need to know is AppWidgetId and have AppWidgetManager instance. So ideally pass all the appWidgetIds to "service" i.e whenever onUpdate() is triggered call startService() with appWidgetIds as intent data and ask the service to update the widgets with data fetched from network.
appWidgetManager.updateAppWidget(appWidgetId, views);

Now how to do we change the updatePeriodMillis to trigger onUpdate before 30mins.

* Use AlarmManager to update.In onUpdate() when the first time appWidget is added, set a setRepeating alarm for the schedule you wish and pass an pendingIndent to launch the service. In service you can update the appWidgets with appWidgetids and remoteview.

final AlarmManager m = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 
final Intent intent = new Intent(context, MyService.class);  
if (service == null)  
{  
 service = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); 
}  
m.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), 1000 * 60, service); 


More details take a look at this post
http://www.parallelrealities.co.uk/2011/09/using-alarmmanager-for-updating-android.html

Multiple sized widgets :

- How do we support multiple sized widgets ? 4x1,4x2,2x1 there are maximum of 4 rows and 4 columns. 4x4 in handsets and tablets support upto 8x7.
Here is one of the way you can achieve this.

- Declare as many AppWidgetProviderInfo metafiles required
2x2 :
android:minWidth="147dp"
android:minHeight="142dp"
4x2 :
android:minWidth="294dp"
android:minHeight="142dp"

- Define corresponding Appwidget classes.

- Declare corresponding AppWidget Broadcast receivers in AndroidManifest.xml


For more details on the cells calculation, minWidth,minHeight please chk in this link.
http://developer.android.com/guide/practices/ui_guidelines/widget_design.html

Snapshot showing four types of appWidgets for the demo application



Things to keep in mind

* Frequency of update should not be high. ex. every 5 mins.It will reduce the battery life of the phone.

* Handled badly your widget could end up making the phone completely unresponsive. Pushing updates to onscreen widgets is a somewhat costly process.

* Avoid nesting of layouts within a layout.

* Give proper padding. As it might collide with adjacent widget. No boundary seperation would be seen.

* Use a nine patch image as background to support multiple sized widgets

* Offload any webrequest through a service to avoid ANRs.


Demo example of widget showing google static map of location as home screen widget.

Quick points of implementation.

* onEnabled() -> Start a service

* onUpdate() -> Get the appWidgetId and appWidgetType and pass to the service.

* onDeleted() -> Get the appWidgetId and pass to the service.

* Service -> Listens for movement change using PhoneStateListener and fetches current location using GPS or Wifi and downloads the static map as Bitmap  and updates the widgets 2x2 and 4x2.

I am not giving in detail explanation of the code. Please go through this block diagram which will give you high level overview. You can go through the code for more details Any queries u can drop me a comment..!!

Implementation overview:







http://code.google.com/apis/maps/documentation/staticmaps/

Demo code:
The entire source code for the demo can be downloaded from the below link.
http://www.4shared.com/get/_FeDVgpD/HomescreenWidget.html

Friday, July 8, 2011

How to perform entry animations for listview.

From my work related learnings, i would like to share the three ways of doing entry animation for listview elements.


1 - LayoutAnimation Controller
This controller is viewgroup animation controller means, it will apply animation to each of the child added to a view group. When this controller is set to the listview
it animates each listviews items. So that you can have entry animation for listview.

But the drawback is this controller will apply the animation to the listview items only for very first time this layout is drawn/shown. The next time when you do
hide / show the layout, you cannot expect the listview to have its element animated.

2 - So the second approach i tried is over riding the onvisibilitychanged() api of Layout class where your listview is added.check for visibility,if someone made the layout visible then this the right place to perform the animation on each views of listview. Use getChildAt() api to get the listview items and start a animation on each view. So you get a entry animation for listview whenever, this layout is made visible. You can also write a exit animation and start when visibility == INVISIBLE


protected void onVisibilityChanged (View changedView, int visibility)
{
super.onVisibilityChanged(changedView,visibility);
if(visibility == View.VISIBLE) {
startEnterAnimation();
}
}

public void startEnterAnimation() {

Animation animation;
int offsetTime=0;
for(int i=0;i View view = list.getChildAt(i);
animation = new AlphaAnimation(0.0f,1.0f);
animation.setFillAfter(true);
animation.setDuration(100);
animation.setStartOffset( i * 100);

if(view != null)
{
view.startAnimation(animation);
}
}
}


There is also a limitation in the second approach. Scenario is, what happens before u perform the animation on the listview,there is a change in the data, where in you need to call the notifydatasetchanged api, then u wanted to start the entry animation.
But by the time you start performing the entry animation, notifydatasetchagned api would have layouted out the listview elements.

So we need to start the animation as and when listview items are drawn on the screen. how do we do this ??

Third approach solves this issue.

3 - In the adapter's getview() api, as and when we return the convertView to the framework, start the animation that instance by checiking for a flag,
which will be set to true for the first time listview is shown. Approach looks good, but how do u know when the listview has finished calling all its getview
and drawn its child so that you can make the flag false ??. Here is the solution i have tried, as soon as notifydatasetchanged api is called,

UI thread will have tasks in its messagequeue to perform getview for the count returned by getCount() adapter.so after notifydatasetchanged api is called,
get the handler of listview(which is nothing but UI thread handler) and post a runnable to make the boolean flag false, So that this runnable will be
executed after all the getview() calls are finished for all its child elements. So We know when to make the boolean false at the right instance.


protected void onVisibilityChanged (View changedView, int visibility)
{
super.onVisibilityChanged(changedView,visibility);
if(visibility == View.VISIBLE) {
// Suppose your data is upadted.
mListAdapter.notifyDataSetChanged();
isInitialLayout = true;
listview.getHandler().post(new Runnable() {
public void run() {
isInitialLayout = false;
}
});
}
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.listview_item, null);
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.title);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

holder.title.setText(mData.get(position));

/* Start the animation for this item for the position */
if(isInitialLayout == true ) {
Animation animation = new AlphaAnimation(0.0f,1.0f);
animation.setFillAfter(true);
animation.setDuration(100);
animation.setStartOffset(position * 100);
convertView.startAnimation(animation);
}

return convertView;
}


If there are any other ways of performing the entry animation for a listview in a layout, please let me/everybody know :)

Friday, June 24, 2011

How to calculate lsitview's total element's height

Sometimes we would be interested in finding height of the listview including all the child's height.(including the child which are not visible )

Usually the api listview.getChildCount() returns the count of number of elements which can be seen in the listview's height. But what i am insisting now is finding the height of all the childs of listview a collective height.

we will see how can we find the collective height of all child.

- Get the adapter instance from the listview.
- Get the count of adapter.
- And use adapter's getView(int position,View view,ViewGroup parent) api to get the view instance of all child
elements.
- Then use the measure api of View to measure the height of the view as below. I used here UNSPECIFIED to find out how big the view is.
- This api measures the widht and height. And use the getMeasuredHeight() api to find
the Measured Height.

private int getTotalHeightofListView() {
ListAdapter mAdapter = listview.getAdapter();
int listviewElementsheight = 0;
for(int i =0;i View mView = mAdapter.getView(i, null, listview);
mView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
listviewElementsheight+= mView.getMeasuredHeight();
}
return listviewElementsheight;
}


For additional information on MeasureSpec, taken from
http://developer.android.com/reference/android/view/View.html

MeasureSpecs are used to push requirements down the tree from parent to child. A MeasureSpec can be in one of three modes:

UNSPECIFIED: This is used by a parent to determine the desired dimension of a child view. For example, a LinearLayout may call measure() on its child with the height set to UNSPECIFIED and a width of EXACTLY 240 to find out how tall the child view wants to be given a width of 240 pixels.
EXACTLY: This is used by the parent to impose an exact size on the child. The child must use this size, and guarantee that all of its descendants will fit within this size.
AT_MOST: This is used by the parent to impose a maximum size on the child. The child must gurantee that it and all of its descendants will fit within this size."


Sunday, June 12, 2011

How to perform exit animation for listview

Some would like to perform animations on list elements when listview is being made hidden or when listview is set to View.INVISIBLE, View.GONE.This is what i term it as "Exit animation of listview."


- listview has an api getChildCount() this will tell you how many number of list view elements are visible on screen and 'getChildAt(int)' api will give you the listview elements View instance.
- Get the childcount and iterate through the loop and get each child view instance and start a animation on each views, you will see a exit animation performed on it.


Below example shows how to perform alpha animation from 1 to 0, disappearing elements one by one from top to bottom.

public void startExitAnimation() {

Animation animation;
listcount = gridview.getChildCount();
int offsetTime=0;
animatedcount = 0;
for(int i=0;i {
View view = gridview.getChildAt(i);
animation = new AlphaAnimation(1.0f,0.0f);
animation.setAnimationListener(mExitAnimationListener);
animation.setFillAfter(true);
animation.setDuration(100);
animation.setStartOffset( i * 100);

if(view != null)
{
view.startAnimation(animation);
}
}
}

Monday, June 6, 2011

How to detect which is the current top activity.

Here is one of the way you can detect


public boolean whatIsCurrentActivity()
{
ActivityManager am = (ActivityManager) mContext.getSystemService(mContext.ACTIVITY_SERVICE);
List ActivityManager.RunningTaskInfo taskInfo = am.getRunningTasks(1);
if(taskInfo != null ){
System.out.println("Top activity - Package name of the process is "+taskInfo.get(0).topActivity.getPackageName() );
}

Wednesday, April 27, 2011

Inent-filter to listen for internet/data connectivity status

When designing home screen widgets which tries to talk to cloud, we might be interested in knowing internet connectivity status of device. Suppoese when adding widget, data connectivity might not be available, but your would expect your widget to start collecting data, as and when device is connected to internet.

1- Check for internet connectivity / data connectivity.



private boolean checkInternetConnection() {

ConnectivityManager conMgr = (ConnectivityManager) mContext.getSystemService (mContext.CONNECTIVITY_SERVICE);

// ARE WE CONNECTED TO THE NET

if (conMgr.getActiveNetworkInfo() != null
&& conMgr.getActiveNetworkInfo().isAvailable()
&& conMgr.getActiveNetworkInfo().isConnected()) {
return true;
} else {
return false;
}

}


2 - To receive for Internet Connectivity dropped / up, listen for
'ConnectivityManager.CONNECTIVITY_ACTION' action.

IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(mNetworkStateIntentReceiver, filter);


3 - Whenever there is a change in connectivity i.e it could be either data connection is connected or disconnected, you will receive this event as broadcast, so in onreceive of the broadcast receiver, please check for internetconnect connection and decide whether internet is up or down.


BroadcastReceiver mNetworkStateIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(
ConnectivityManager.CONNECTIVITY_ACTION)) {

NetworkInfo info = intent.getParcelableExtra(
ConnectivityManager.EXTRA_NETWORK_INFO);
String typeName = info.getTypeName();
String subtypeName = info.getSubtypeName();
System.out.println("Network is up ******** "+typeName+":::"+subtypeName);

if( checkInternetConnection()== true )
{
"Decide your code logic here...."
}
}
}

};

Wednesday, April 13, 2011

How to dismiss your non-modal dialog, when touched outside dialog region

When you implement your dialog as non-modal dialog, means when your dialog is shown, you can interact with other elements on the screen, in such case, you might be interested to dismiss the dialog, when user touches/press/interacts with other elments on the screen. The following steps will help you to reeceive for the touch events outside your non-modal dialog.

1 - Set the flag-FLAG_NOT_TOUCH_MODAL for your dialog's window attribute

Window window = this.getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);

2 - Add another flag to windows properties,, FLAG_WATCH_OUTSIDE_TOUCH - this one is for dialog to receive touch event outside its visible region.

3 - Override onTouchEvent() of dialog and check for action type. if the action type is
'MotionEvent.ACTION_OUTSIDE' means, user is interacting outside the dialog region. So in this case, you can dimiss your dialog or decide what you wanted to perform.

public boolean onTouchEvent(MotionEvent event)
{

if(event.getAction() == MotionEvent.ACTION_OUTSIDE){
System.out.println("TOuch outside the dialog ******************** ");
this.dismiss();
}
return false;
}

Monday, March 28, 2011

how to launch pending intent

Pending Intent:

An application can create a Pending Intent, which is nothing but an intent and action to it, so that it can passed to some other applications which can execute this pendingintent as if original application executes it.

PendingIntents can be created of three types

getActivity(Context, int, Intent, int), getBroadcast(Context, int, Intent, int),
getService(Context, int, Intent, int);


You can create a pending intent to launch an activity, or service, broadcast an intent.

Let us see how to create an pendingIntent which can launch an activity and see once you have pendingIntent, how can you launch make operation on the intent.

- Create a two activities in your android project.
- One activity create an pendingIntent through 'getActivity()' api

PendingIntent pendingIntent;
Intent intent = new Intent();
intent.setClass(mContext,activity2.class);
pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);

- pass activity2 as setClass parameter to intent and create the pendingIntent.
- In activty1, for button click action, use the pendingIntent instance to perform the operation on it using 'send()' api.

Intent intent = new Intent();
try {
pendingIntent.send(mContext, 0, intent);
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here. Just log the exception message.
System.out.println( "Sending contentIntent failed: " );
}


activity1.java
---------------

package com.mani.pending;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;


public class activity extends Activity {
Button b1;
PendingIntent pendingIntent;
Context mContext;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mContext = this.getApplicationContext();
b1 = (Button) findViewById(R.id.button);
Intent intent = new Intent();
intent.setClass(mContext,activity2.class);
pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);

b1.setOnClickListener( new View.OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent();
try {
pendingIntent.send(mContext, 0, intent);

} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here. Just log the exception message.
System.out.println( "Sending contentIntent failed: " );
}
}
});

}
}


activity2.java
----------------


package com.mani.pending;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class activity2 extends Activity{

TextView v1;
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.main);
v1 = (TextView)findViewById(R.id.text);
v1.setTextSize(30);
v1.setText("Welcome to pendinIntent");
}

}


Tuesday, January 25, 2011

How to bring spinner at end of button using custom view

I was trying to implement a spinner at the end of button, to indicate user that some background connection to internet was going on to fetch some data.

First approach i took was to implement a Frame animation.

- Create a different images of spinner at different positions and then add a each images to animationDrawable instance and set the drawable to button using
'setCompoundDrawable()' api as defined below


http://developer.android.com/reference/android/widget/TextView.html#setCompoundDrawables(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable)

When needed to start/stop use start() and stop() apis and setCompoundDrawable(null,null,null,null) to hide the drawable.



package com.mani.spinner;

import android.app.Activity;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class spinner extends Activity {
/** Called when the activity is first created. */
Drawable spinnerAnimation;
Drawable spinnerBackground;
Button b1;
Button b2;
sample view;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
view = (sample)findViewById(R.id.sampleView);

spinnerAnimation = new AnimationDrawable();
spinnerAnimation.setBounds(0, 0, 20, 20);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a1);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a2);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a3);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a4);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a5);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a6);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a7);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a8);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a9);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a10);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

spinnerBackground =getApplicationContext().getResources().getDrawable(R.drawable.a11);
((AnimationDrawable)spinnerAnimation).addFrame(spinnerBackground, 200);

((AnimationDrawable)spinnerAnimation).setOneShot(false);

b1 = (Button)findViewById(R.id.button1);
b1.setCompoundDrawables(null, null, spinnerAnimation, null);
b1.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {
b1.setCompoundDrawables(null, null, spinnerAnimation, null);
((Animatable)spinnerAnimation).start();

}
});
b2 = (Button)findViewById(R.id.button2);
b2.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

((Animatable)spinnerAnimation).stop();
b1.setCompoundDrawables(null, null, null, null);

}
});

}
}





More info on Frame animation you can check here :
http://iserveandroid.blogspot.com/2010/10/frame-animation-circular-spinner.html

I was thinking how to use one image and rotate it to bring the same effect of spinning. Then i tried this approach.

Second approach rotate animation resource

Spin.xml @res/anim folder.


xmlns:android="http://schemas.android.com/apk/res/android"
android:repeatCount="infinite"
android:duration="1000"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="0"
android:toDegrees="360" />



ImageView v1;
Animation mAnimation = AnimationUtils.loadAnimation(mContext, R.anim.spin);
v1.startAnimation(mAnimation);

Load the animation and apply to a image. But i could bring this effect to only the spinner image alone. Together with button and spinner @ end of button, i couldnt achieve using this.




Then i planned to create a custom view and create a button kinda of illusion and draw the spinner bitmap with different angles and create the effect like spinner @ end of button.

Third approach: Custom View

- Inherit from View class and over ride onDraw() method.
- Some special api's in canvas allows you to achieve the spinning of image.



canvas.drawRect(mButtonRect, mPaint);
canvas.drawText("More", 40,40, mPaint);
canvas.save();
canvas.translate(mSpinnerX,0);
canvas.rotate(mAngle,mSpinnerPivotX,mSpinnerPivotY);
canvas.drawBitmap(mSpinnerBitmap, null, mSpinnerRect, mPaint);
canvas.restore();


- there is a option in canvas to 'save' and 'restore'. using these apis we can save the canvas state and do some manipulation to new canvas, place objects whereever needed and then do restore so that canvas contents before save is drawn without disturbed.

- Like in this, a rectangle is drawn (imagine a button ) and a text on the button is drawn at correct cordinates.
- Then 'save' the canvas and move the canvas to the 80% length of the screen(or width of the button) using 'translate' and rotate the canvas by certain angle then draw the spinner bitmap. It looks like the bitmap is drawn at the end of original canvas with rotated.

- When the angle is kept incremented using a runnable and invalidate() is called, then you would see like the image is rotated at the end...!!

-make the view class implement 'Runnable" and implement run as below to call onDraw() for every angle change.


public void run()
{
if(mAngle == 360)
mAngle=45;
else
mAngle+=45;
invalidate();
mHandler.postDelayed(this, 100);
}




main.xml:
----------


android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
android:id="@+id/button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Start"
/>
android:id="@+id/button2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Stop"
/>
android:id="@+id/image"
android:src="@drawable/spinner_white_48"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
android:id="@+id/sampleView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>




sample.java:

package com.mani.spinner;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;

public class sample extends View implements Runnable{

Bitmap mSpinnerBitmap;
Paint mPaint;
Rect mSpinnerRect;
Rect mButtonRect;
int mButtonWidth;
int mButtonHeight;
int mSpinnerX;
int mSpinnerPivotX;
int mSpinnerPivotY;
int mSpinnerWidth;
int mSpinnerHeight;
boolean mSpinnerVisible;
Runnable mTask;
float mAngle=0;

Handler mHandler = new Handler();
String mButtonText="More";
public sample(Context context,AttributeSet attrs)
{
super(context,attrs);
mSpinnerBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.spinner_white_48);
mPaint = new Paint();
mPaint.setColor(0xFF0000FF);
mPaint.setTextSize(16);

Display display = ((WindowManager)context.getSystemService(context.WINDOW_SERVICE)).getDefaultDisplay();
mButtonWidth = display.getWidth();
mButtonHeight = 70;
mSpinnerHeight = mSpinnerWidth = mButtonHeight;
mSpinnerPivotX = mSpinnerPivotY = mSpinnerWidth /2;
mSpinnerX = (mButtonWidth-mButtonHeight);
mSpinnerRect = new Rect(0,0,mSpinnerWidth,mSpinnerHeight);
mButtonRect = new Rect(0,0,mButtonWidth,mButtonHeight);
}
public void setText(String text)
{
mButtonText = text;
}
public void setTextSize(int size)
{
mPaint.setTextSize(size);
}

public void setButtonColor(Color color)
{

}
public void start()
{
mHandler.post(this);
mSpinnerVisible = true;
}

public void stop()
{
mSpinnerVisible = false;
mHandler.removeCallbacks(this);
invalidate();
}

public void run()
{
if(mAngle == 360)
mAngle=45;
else
mAngle+=45;
invalidate();
mHandler.postDelayed(this, 100);
}

@Override
public void onDraw(Canvas canvas)
{

canvas.drawRect(mButtonRect, mPaint);
canvas.drawText("More", 40,40, mPaint);
if(mSpinnerVisible == true)
{
canvas.save();
canvas.translate(mSpinnerX,0);
canvas.rotate(mAngle,mSpinnerPivotX,mSpinnerPivotY);
canvas.drawBitmap(mSpinnerBitmap, null, mSpinnerRect, mPaint);
canvas.restore();
}

}

}


Note:
If you would like to see how the canvas is rotated, create a visibleRect using view's height and width and draw a rectangle. Which will show you that entire canvas content is rotated.

Rect visibleRect;
visibleRect.set(0, 0, this.getWidth(), this.getHeight());

if(mSpinnerVisible == true)
{
canvas.save();
canvas.translate(mSpinnerX,0);
canvas.rotate(mAngle,mSpinnerPivotX,mSpinnerPivotY);
canvas.drawRect(visibleRect, mPaint);
canvas.drawBitmap(mSpinnerBitmap, null, mSpinnerRect, mPaint);
canvas.restore();
}

Thursday, January 20, 2011

Drag/Move a image in a custom view - part I

This post could be a first step of approach in moving a image in a view. With this approach you can start creating (base view and image movements) for a simple games like number puzzles, crosswords, pin ball.

1 - Create a class extends view. and override onDraw() and decide what are the contents you need to draw.

2 -To Keep moving a image in a view, we should be keep changing the x,y position of the image. This is the basic for image movement in a view

3 - In this example, a rectangle is drawn. And its position is hold in a variable ' Rect ImagePosition'

4 - When movement is detected 'ACTION_MOVE' event in 'OnTouchEvent()' function, deltaX is calcualted with the previous (x,y) and current moved (x,y) and a check is made like, whether , if the deltas are added to mImagePosition (top, left and bottom right ) is withing the visible screen. If 'yes', then deltax are added to 'Rect mImagePosition'. And invalidate() is called to draw() method to draw the rectangle. So it looks like the object is moving as when touched and moved along with the finger.


mImagePosition.left = mImagePosition.left + deltaX;
mImagePosition.top = mImagePosition.top + deltaY;
mImagePosition.right = mImagePosition.left + mImageWidth;
mImagePosition.bottom = mImagePosition.top + mImageHeight;
mImageRegion.set(mImagePosition);
prevX = positionX;
prevY = positionY;

invalidate();


Here is the complete code, i have tried out.

dragimage.java
----------------


import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;

public class dragimage extends Activity {

sample mView;
sample1 mView1;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

}
}


main.xml:
----------


android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:id="@+id/view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>




sample.java:
-------------


package com.mani.dragimage;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
public final class sample extends View{
Paint mPaint;
Rect mRect;
Bitmap bitmap;
private int mTouchSlop;
private int mTouchMode;
int mScreenHeight;
int mScreenWidth;
int prevX;
int prevY;
static final int TOUCH_MODE_TAP = 1;
static final int TOUCH_MODE_DOWN = 2;
final int mImageWidth = 100;
final int mImageHeight = 100;
Rect mImagePosition;
Region mImageRegion;
boolean canImageMove;

public sample(Context context,AttributeSet attrs)
{
super(context,attrs);
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.chrome);
mPaint = new Paint();
mPaint.setTextSize(25);
mPaint.setColor(0xFF0000FF);
//Size for image
mImagePosition = new Rect(10,10,mImageWidth,mImageHeight);
mImageRegion = new Region();
mImageRegion.set(mImagePosition);
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
Display display = (WindowManager)context.getSystemService(context.WINDOW_SERVICE)).getDefaultDisplay();
mScreenHeight = display.getHeight();
mScreenWidth = display.getWidth();
canImageMove = false;
}


public boolean onTouchEvent(MotionEvent event)
{
int positionX = (int)event.getRawX();
int positionY = (int)event.getRawY();

switch(event.getAction())
{
case MotionEvent.ACTION_DOWN: {
mTouchMode = TOUCH_MODE_DOWN;

if(mImageRegion.contains(positionX, positionY))
{
prevX = positionX;
prevY = positionY;
canImageMove = true;
}
}
break;

case MotionEvent.ACTION_MOVE:
{
if(canImageMove == true)
{
// Check if we have moved far enough that it looks more like a
// scroll than a tap
final int distY = Math.abs(positionY - prevY);
final int distX = Math.abs(positionX - prevX);

if (distX > mTouchSlop || distY > mTouchSlop)
{
int deltaX = positionX-prevX ;
int deltaY = positionY-prevY;
// Check if delta is added, is the rectangle is within the visible screen
if((mImagePosition.left+ deltaX) > 0 && ((mImagePosition.right +deltaX) < mScreenWidth ) && (mImagePosition.top +deltaY) >0 && ((mImagePosition.bottom+deltaY)))
{
// invalidate current position as we are moving...
mImagePosition.left = mImagePosition.left + deltaX;
mImagePosition.top = mImagePosition.top + deltaY;
mImagePosition.right = mImagePosition.left + mImageWidth;
mImagePosition.bottom = mImagePosition.top + mImageHeight;
mImageRegion.set(mImagePosition);
prevX = positionX;
prevY = positionY;

invalidate();
}
}
}
}
break;
case MotionEvent.ACTION_UP:
canImageMove = false;
break;
}
return true;
}

@Override
public void onDraw(Canvas canvas)
{
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);

// make the entire canvas white
paint.setColor(Color.CYAN);
Rect rect = new Rect(0,0,this.getWidth(),this.getHeight());
canvas.drawRect(mImagePosition, paint);
//canvas.drawBitmap(bitmap, null,mImagePosition, null);
}


}

Wednesday, January 12, 2011

How to access resources from other application (.apk)




We might be interested in accessing the resources of other application. Or inflate a particular view from other applications layout xml file.In this post, we will discuss more about how to achieve this.

1 - What ever we need to access (resources or layout), we need two main things from other application .
       
1 - R.java.
Required for you during compilation time.

2 - Application's context.
Resources needs to be inflated with the correct application's 'Context' instance.Else at runtime the prog will crash.

2 - The app1(.apk) needs to be installed on the device in order for you to create app1's context instance and then inflate the resources / layouts.

app1 :
------
Create a an android application with package (com.android.app1 ) which contains resources you want to expose to others.

AndroidManifest.xml:

Manifest file doesnt have any entry for activity or application tags. it just says, under which package the resources will be present.


package="com.android.app1"
android:versionCode="1"
android:versionName="1.0">




Keep the resources you want to expose in drawables. In this case i have kept the picture named 'pic1.jpg' .

app2:

1 - Create an android application with package name com.android.app2.

we need to include the com.android.app1.R.class into app2.

2 - Select project Right click -> Properties -> Java Build Path -> Libraries -> Add External class folder -> "Choose the app1/bin folder"
Now app2 can reference 'com.android.app1.R'

And create the context for app1.

Context otherAppContext = getApplicatoinContext().createPackageContext("com.android.app1", Context.CONTEXT_IGNORE_SECURITY);
Bitmap b1 = BitmapFactory.decodeResource(otherAppContext.getResources(),com.android.app1.R.drawable.pic1);


Use the bitmap from 'app1' to apply to a Imageview.

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


android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
android:id="@+id/image1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="@drawable/rajini"
/>
android:id="@+id/image2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>



app2.java:
-----------

package com.android.app2;


import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.widget.ImageView;


public class app2 extends Activity {
ImageView imgView;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imgView = (ImageView)findViewById(R.id.image2);
try
{
Context otherAppContext = getApplicationContext().createPackageContext("com.android.app1", Context.CONTEXT_IGNORE_SECURITY);
Bitmap b1 = BitmapFactory.decodeResource(otherAppContext.getResources(),com.android.app1.R.drawable.pic1);
BitmapDrawable bitmapDrawable = new BitmapDrawable(b1);
imgView.setBackgroundDrawable(bitmapDrawable);
}
catch(Exception e)
{

}
}
}


How to start an activity if it is not see in launcher application list.

$ am start -n com.android.app2/com.android.app2.app2
Starting: Intent { cmp=com.android.app2/.app2 }

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"
};


}