Android development blog
Tutorials about Android dev topics

How to create a simple game:Peg board game

Topics covered

Android App tutorial

SlidingPaneLayout

Fragments

Custom layout

Custom view

Save layout as image

This post describes how to create a simple board game. It can be useful if you want to know how to create custom components, handle user touch.
The source code is available at Github.
We want to create an app like the images shown below:

board_1board_2board_3
There are some interesting aspects we can consider in this app and they are described below. It can be used as example how to integrate different elements like SlidingPaneLayout, fragments, event handling and so on.

App structure

The main android UI pattern used by the app is the Sliding Pane layout because we need to have the screen divided in two area: the main area is where we can play with our pegs and the other one that usually is closed is the menu.
So the overall layout is:
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sp"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:background="@drawable/tilebkg"
    >

    <fragment
        android:id="@+id/pinMenu"
        android:name="com.survivingwithandroid.pegboard.fragment.MenuFragment"
        android:layout_width="200dp"
        android:layout_height="match_parent"
        android:layout_gravity="left" 
        />

    <fragment
        android:id="@+id/pinTable"
        android:name="com.survivingwithandroid.pegboard.fragment.DreamPinTableFragment"
        android:layout_width="600dp"
        android:layout_height="match_parent"
        android:layout_gravity="right"
        android:layout_weight="1" />
    
</android.support.v4.widget.SlidingPaneLayout>

The result is shown below:
board_2_fragment 


As you can notice the SlidingPaneLayout handles two fragments DreamPinTableFragment and MenuFragment. DreamPinTableFragment takes care of creating the table and handling the user touch so that the user can place pins on it, while the MenuFragment handles the some options like choosing the pin colors, save the work and so on.

Custom Layout with ViewGroup


The peg board is implemented as custom layout. The class that implements it is called PinTableView that extends ViewGroup. This custom layout takes care of dividing the UI screen in small cells that corresponds to the holes where the pegs are placed. The app screen is treated as a table divided in rows and columns.

The first method called by DreamPinTableFragment is disposePin. This method calculates the number or rows and columns and initializes the table:
public int[] disposePins(int width, int height, int dotSize) {
  
    this.dotSize = dotSize;
    
    numRow = height / dotSize + 1;
    numCol =  width / dotSize + 1;
   
    int[] dotColors = Pin.createDotArray(dotSize, true);
    
    for (int r=0; r < numRow ; r++) {
        for (int c=0; c < numCol; c++) {
            PinImageView pinImg = new PinImageView(getContext(), r, c);
            this.addView(pinImg);                
        }
    }
    
    
    return new int[]{numRow, numCol};
}

Notice that in this method we add PinImageView (another custom view) to the layout (line…) giving to them the right row and column number.

Then in onLayout method we traverse the list of childs (we added previously) and start placing them in the right position. In this case we need to calculate the x and y coordinates in real pixel, this is simple once we know the row and column number and the cell pixel size:
@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
    int childCount = getChildCount();

    for (int i=0; i < childCount; i++) {
        PinImageView pinImg = (PinImageView) getChildAt(i);
        
        //int left = pinImg.getCol() * dotSize + dotSize * (pinImg.getType() == PinImageView.COLOR_COMMANDS || pinImg.getType() == PinImageView.DELETE ? 0 : 1);
        int left = pinImg.getCol() * dotSize;
        //int top = pinImg.getRow()  * dotSize + dotSize * (pinImg.getType() == PinImageView.COLOR_COMMANDS || pinImg.getType() == PinImageView.DELETE ? 0 : 1);
        int top = pinImg.getRow()  * dotSize;
        int right = left + dotSize ;
        int bottom = top + dotSize ;
        
        pinImg.layout(left, top, right, bottom);            
    }
    
}

In this way, we created an empty board that looks like the picture shown below:

board_1

Custom imageView: PinImageView


As we saw before, we place in our layout a custom ImageView childs. PinImageView is a simple class that extends ImageView and represents a single Peg (or pin as we prefer to call it). This class has an internal state that holds the type of the peg it represents and implements some kind of animation to make the UI more attractive.
public class PinImageView extends ImageView  {
private int row;
private int col;    
private int xSize;
private int ySize;    
private int stato = -1;    
private Context ctx;
private int currentPinId;

public PinImageView(Context context, int row, int col) {
    super(context);
    this.ctx = context;
    this.row = row;
    this.col = col;
    //this.parent = parent;        
    
    // Load image
    Drawable d  = getResources().getDrawable(TableConfig.pinBackground);
    setImageDrawable(d);        
    xSize = this.getWidth();
    ySize = this.getHeight();
    this.currentPinId = TableConfig.pinBackground;
    
}
...
class AnimView implements Runnable {

        Animation anim;
        Drawable d;
        
        public AnimView(Animation anim, Drawable d) {
            this.anim = anim;
            this.d = d;
        }
        @Override
        public void run() {
            anim.setAnimationListener(new Animation.AnimationListener() {
                
                @Override
                public void onAnimationStart(Animation animation) {
                    TableConfig.playSound(PinImageView.this.ctx);
                }
                
                @Override
                public void onAnimationRepeat(Animation animation) {}
                
                @Override
                public void onAnimationEnd(Animation animation) {
                    setImageDrawable(d);
                    PinImageView.this.setVisibility(View.VISIBLE);
                }
            });
            
            PinImageView.this.startAnimation(anim);    
        }
        
    }
}

Handle user touch: OnTouchEvent and OnTouchListener


The next step is handling the user touch. We define that DreamPinTableFragment listens to the events triggered when user touches the screen. We want to place a peg in the position where user touched the screen:
@Override
public boolean onTouch(View v, MotionEvent event) {
            
    int x = (int) event.getX() ;
    int y = (int) event.getY() ;

    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        pinsTable.changePinColor(x, y);            
        return true;
    }
    else if (event.getAction() == MotionEvent.ACTION_MOVE) {
        pinsTable.changePinColor(x, y);    
        return true;
    }
    
    return false;

}

and then we delegate to the PinTableView to handle the event passing the coordinates:
public void changePinColor(int x, int y) {
    int row = getRow(y);
    int col = getColumn(x);
    
    PinImageView pinImg = (PinImageView) getChildAt(col + ( row  * numCol ));
    if (pinImg != null) {
        pinImg.setPinColor(currentColor);    
        
    }
}

Knowing the x and y coordinates we can calculate the corresponding row and column and set the peg with the right color.

App Menu


As we explained before the app has a menu where user can select the pincolor, save the work or change the table background. The class that handles the menu is called MenuFragment. This is very simple class, it just notifies to the main activity the events triggered when user selects a menu item. In turn, the main activity handle this event informing the other fragment when these event regards pegs or some actions that have to be taken on the table.

The MenuFragment defines an interface (a listener) that we have to implement when we want to have information on the events occurred in the menu:
public static interface MenuEventListener {
    public void onPinSelected(int pinId);
    public void onClearSelected();
    public void onSaveSelected();
    public void onBackgroundSelected();
}

So the main activity:
public class DreamPinsActivity extends Activity implements
MenuFragment.MenuEventListener {
....
    @Override
    public void onPinSelected(int pinId) {

        pinTableFrag.setCurrentPin(pinId);
        closeMenu();
    }

    @Override
    public void onClearSelected() {
        ...
    }

    @Override
    public void onSaveSelected() {
        closeMenu();
        ...
    }

    public void onBackgroundSelected() {
        ...
    }
}

Save layout as image


The last aspect we want to cover is saving the layout as image. We can implement it in this way:

in the PinsTableView:
public Bitmap createBitmap() {
    Bitmap b = Bitmap.createBitmap(this.getWidth(), this.getHeight(), Bitmap.Config.RGB_565);        
    Canvas c = new Canvas(b);
    
    this.draw(c);
    
    return b;
}

where we create an empty bitmap having the same size of the screen and then draw on the corresponding Canvas. Once we have our bitmap that contains the screenshot of the app screen we save it in a file:
OutputStream outStream = context.getContentResolver()
        .openOutputStream(uri);

b.compress(Bitmap.CompressFormat.PNG, 100, outStream);
outStream.flush();
outStream.close();

Source code availabe @ github

No comments:

Post a Comment

Related Posts with Thumbnails