Monday, July 18, 2011

Android: Really cool customized toast like notifications


If you've ever had the need to display a Toast notification and customize it the way you thinks its right, say for example a custom duration other than Toast.LENGTH_SHORT and Toast.LENGTH_LONG, or to display styled text (italics/bold), you would know that the functionality provided byt the default Android Toast notification is rather limited.


I faced this recently in the design of my 'Tic Tac Toe Classic' game. For this game I wanted to display Tips and Interesting information about the game to users when the game was started (as shown in this pic below),

BTW, I encourage you to give this game a try and see how this actually looks like on your phone.


I first tried using the default Android Toast but soon discovered that the LENGTH_LONG duration was not long enough for users to actually read what was on the screen. So I had a look at the documentation and found a Toast.setDuration() method which seemed to be what I needed. I used this to set a custom duration of 10 seconds (10000 ms) to this function. However I was surprised to see that the Toast still disappeared quite soon. On digging deeper I found that the LENGTH_SHORT and LENGTH_LONG are treated as flags that are passed to the setDuration() and work out to a durations of 2.5 and 3 seconds respectively. i.e. there is no actual option to set a desired duration.

This was quite disappointing, but I soon came across a workaround (read "ugly hack") which was to invoke Toast.show() repeatedly which would keep it on screen longer. I tried this in a loop with a count of 3 (to get near to 10 seconds as possible) and everything seemed fine.

for (int i=0; i < 3; ++i) {
  Toast.makeText(this, "My Text", Toast.LENGTH_LONG).show();
 }
My joy was short lived though. When I tried a case where a user would quit the app just as the game was launched (say for e.g. another Activity/phone call interrupts it at this time), the toast was still displayed on screen for almost 10 seconds even after the app was quit (that is after all the behavior of Toast's in Android though) which seemed a little annoying. So I then started investigating alternatives to help me solve this small but annoying problem.

I soon came across an Article on Android ViewStubs and this seemed just perfect for what I had in mind. In short an Android ViewStub is a lightweight view which does not participate in the layout, hence is very cheap to inflate and keep in memory. In my opinion this is exactly what I needed for my Window to be like.

So to achieve this I added a tip.xml within my res/layout directory for the layout of my custom toast I needed (which is cool as I was also able to add a header element and a a horizontal rule below it),

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:orientation="vertical"
 android:padding="10dip"
 android:background="@drawable/rounded_rectangle">
 <TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:textStyle="bold"
  android:textColor="#FFF"
  android:text="My Tip Title" />
 <View
  android:layout_gravity="center"
  android:layout_width="fill_parent"
  android:layout_height="2dip"
  android:background="#FFFFFFFF" />
 <TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:textColor="#FFF"
  android:maxEms="15"
  android:id="@+id/tip_trivia_text"
  android:text="Test. Alpha Beta Charlie Bravo..." />
</LinearLayout>
and in the XML layout where I wanted this Toast like Window to appear, I added a ViewStub which referenced the above tip.xml layout,
<ViewStub
 android:id="@+id/stub_import"
 android:inflatedId="@+id/toast_panel_import"
 android:layout="@layout/tip"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content" />
Finally within my Activity I added a simple AsyncTask that did nothing more than to inflate() the ViewStub, sleep for the required time and then disappear with setVisibility(View.GONE); as shown below,
private class MyCustomToastTask extends AsyncTask<Void, Void, Void> {
  private long mToastDisplayTimeMs = 0;
  private View mTipTriviaPanel;

  // This runs on the UI thread itself
  @Override
  protected void onPreExecute() {
   if (mTipTriviaPanel == null) {
     // Get the display time and String to be populated
     mToastDisplayTimeMs = getResources().getInteger(R.integer.toast_display_time_ms);
     tipTriviaMsg = getResources().getStringArray(R.array.tip_trivia_list);

     mTipTriviaPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
     TextView text = (TextView) mTipTriviaPanel.findViewById(R.id.tip_trivia_text);
     text.setText(tipTriviaMsg);
   }
  }

  @Override
  protected Void doInBackground(Void... arg0) {
   try {
    Thread.sleep(mToastDisplayTimeMs);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   return null;
  }

  // This is on the UI thread itself
  @Override
  protected void onPostExecute(Void v) {
   mTipTriviaPanel.setVisibility(View.GONE);
  }
 }

Thats it!. This worked excellently for me and resolved all the above problems I was facing. The only additional change I think could be added is that a fade animation to fade out the window (for better effect). Also do check out my game on the Android Market. to see how this works firsthand (QR code below).

I hope you find this useful. If you have any alternatives or would like to add to this I would love to hear from you. Please leave a message if so.

Monday, April 11, 2011

Android 2D Graphics



This is a continuation of my previous article on my attempts with a simple Android game.

After some more reading of the Android developer guide and some googl'ing, I finally managed to draw a ball on a background and make it move around. :)

To begin with I decided to only display a background with a ball on it and get this ball to bounce around within the boundaries of the screen.

So I first had to create a custom view by extending the View class and defining my own onDraw() method. As explained in the Developer guide, the Android framework would call my onDraw() method to request this View to be drawn and this is where I should perform all drawing to my Canvas.

I created a View named 'MyView' (not a very original name :)) and defined the default constructor in it. Within this constructor I created the Ball and Background image bitmaps from the resources (which I added under res/drawable-hdpi) using BitmapFactory.decodeResource() and initialized a few other variables initial placement of the ball and direction of bounce.

I also found that apart from the Constructor and onDraw() I had to override onMeasure() and invoke setMeasuredDimension() with the correct width and height for my view to correctly determine its boundaries. I also initialized a few more variables within this to help identify the maximum X and Y boundaries for the ball, initial placement and direction of bounce.

Within the onDraw() I just display the background, ball images and update the x and y co-ordinates for the next location the ball needs to be drawn at. Also quickly determine if a collision is going to occur with the boundaries and reverse directions accordingly.

I think the actual code would help explain this a lot better,

public class MyView extends View {

 private Context mContext;
 private Bitmap mBack;
 private Bitmap mBall;
 private int ballRadius;
 private int ballXMax, ballYMax;
 private int x,y;
 private int xInc, yInc;
  
 // c-tor for in code construction
 public MyView(Context context) {
  super(context);
  
  mContext = context;
  
  mBack = BitmapFactory.decodeResource(getResources(), R.drawable.wood);        
  mBall = BitmapFactory.decodeResource(getResources(), R.drawable.ball2);
  ballRadius = mBall.getWidth();
 }

 @Override
 protected void onMeasure(int width, int height){
  int measuredWidth = MeasureSpec.getSize(width);
  int measuredHeight = MeasureSpec.getSize(height);
  
  xInc = 1;
  yInc = 1;
  
  ballXMax = measuredWidth - ballRadius;
  ballYMax = measuredHeight - ballRadius;
  
  x = ballXMax/2;
  y = ballYMax/2;
 }

 @Override
 protected void onDraw(Canvas canvas) {
  canvas.drawBitmap(mBack, 0, 0, null);

  x += xInc;
  y += yInc;
       
  canvas.drawBitmap(mBall, x, y, null);

  // boundary collision detection and reversal
  if (x > ballXMax) xInc = -1;
  if (x < 0) xInc = 1;
  if (y > ballYMax) yInc = -1;
  if (y < 0) yInc = 1;

  //and redraw now
  invalidate();
 }
}
Further optimization:
I did however notice that with this code the simulation of the ball moving seemed "jumpy" on the emulator. i .e. it did not seem to be moving across the screen at a uniform speed. At some points it seemed to move slowly and then suddenly a little faster.
Also the onDraw() method where the static background image is redrawn every iteration seemed like something that could be avoided. With some searching I found that it is possible to create a Theme (theme.xml within the res/values directory) with the desired background image and use this in the AndroidManifest.xml for the Activity. This avoids having to redraw the background each time in our onDraw() method.
theme.xml
<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <style name="Theme.StaticBackground" parent="android:Theme.NoTitleBar.Fullscreen">
        <item name="android:windowBackground">@drawable/wood</item>
    </style>
</resources>
With this change the animation appeared much smoother on the emulator than before (I'm not aware of how this could be measured as yet though).

View construction from XML:
Also as you may have noticed, I only have one constructor in my above code. This constructor only allows in-code creation on my View. If you want to create the View from XML then you also need to add a constructor with the additional AttributeSet argument (as shown below).
// c-tor for construction from XML Layout
 public MyView(Context context, AttributeSet attr) {
  super(context, attr);

  mContext = context;
  
  mBall = BitmapFactory.decodeResource(getResources(), R.drawable.ball2);
  ballRadius = mBall.getWidth();
 }
In this case you can have something like this in your layout XML (res/layout/main.xml) to display the custom view,
<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent" >

  <com.meyn.android.mygame.MyView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" />

</FrameLayout>
BTW, this is the onCreate() from the activity,
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // No Title bar
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        //MyView mMyView = new MyView(this);
        //setContentView(mMyView);

        setContentView(R.layout.main);
    }

Monday, April 4, 2011

Beginning with Android 2D Graphics

I am planning to develop a simple Android game with the main intention of learning Android game programming while doing so. I plan to start out with a rather simple board game. The user controls a ball and has to direct it to a specific position (the target) in a maze. On reaching the target the game is complete and points will be awarded to the user based on the time taken to reach the target (i.e. lesser time higher score).

This post is intended as a reference guide (mostly for myself but if it does help anyone else that would be cool).

To get started, I first looked up options Android offers and found several ways of implementing 2D graphics. I had to dig a little deeper to know which was best suited for the task I had in mind.

The first option Android offers is drawing into a View object (with the android.graphics.drawable and the android.view.animation packages). However (from the developers guide) this option seems to be best suited for display of static graphics OR pre-defined animation within an otherwise static application. As this does not suit my needs I decided to check out the other options.

The next option is drawing to a Canvas, which from the developer guide is a better choice when the application needs to regularly re-draw itself (as in my case).
There are however 2 ways to go about this. Either have the game draw to the Canvas in the same thread as the UI Activity or handle this in a separate thread. Doing this in the same thread is the recommended approach for games that do not require a significant amount of processing or frame-rate speed (such as board games or any slowly animated game), while using a separate thread for updating the drawing surface is recommended for faster animations and gives the game/app more control on the drawing pace.

To summarize these are the options,
  1. Drawing to a View object - Suited for Display of Static graphics / pre-defined animations
  2. Drawing to a Canvas - For apps that need to re-draw regularly
    • In the same thread as the UI activity
    • In a separate thread from UI activity (more control on drawing pace)
With my first attempt I've decided to try drawing in the same thread, and if performance does not seem satisfactory, I will try the separate thread approach (or may as well try it anyway once done with this:)). In my next post I'll share whatever I've learned trying to implement this. The Snake game is given as a references for this kind of an app.

Updated: Continued here