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

4 comments:

  1. How do I implement the above control to my existing main.xml with a linear layout-Please Help

    ReplyDelete
  2. Hi Raven,

    This example is for case you implement a custom view. In case of Button resources in main.xml, you can use a drawable and set it as background in main.xml for button.

    Create a button_close.xml in drawable folder










    and apply this 'button_close' to background

    android:background="@drawable/button_close"

    Then when click and leave, different images will be picked up.

    - mani

    ReplyDelete
  3. You can check files in this path for reference.

    android/framework/base/core/res/res/drawable/btn_close.xml

    ReplyDelete