Implementation of the tic-tac-toe sample.

Added some README.txt

Change-Id: Ie2703ffdfdeba773d0fd27153ec0cdb806b2e2dc
This commit is contained in:
Raphael
2010-04-02 22:05:14 -07:00
parent 718a35143d
commit b8a884fbab
13 changed files with 697 additions and 58 deletions

View File

@@ -19,7 +19,4 @@
package="com.example.tictactoe.library" package="com.example.tictactoe.library"
android:versionCode="1" android:versionCode="1"
android:versionName="1.0"> android:versionName="1.0">
<application>
<activity android:name="GameActivity" />
</application>
</manifest> </manifest>

View File

@@ -0,0 +1,7 @@
Sample: TicTacToeLib and TicTacToeMain.
These two projects work together. They demonstrate how to use the ability to
split an APK into multiple projects.
Please see the README.txt file in ../TicTacToeMain for more details.

View File

@@ -16,13 +16,13 @@
# This file is automatically generated by Android Tools. # This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED! # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
# #
# This file must be checked in Version Control Systems. # This file must be checked in Version Control Systems.
# #
# To customize properties used by the Ant build system use, # To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your # "build.properties", and override values to adapt the script to your
# project structure. # project structure.
android.library=true android.library=true
# Project target. # Project target.
target=android-7 target=android-3

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_vertical|center_horizontal"
>
<com.example.tictactoe.library.GameView
android:id="@+id/game_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="20dip"
/>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
>
<TextView
android:id="@+id/info_turn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginBottom="10dip"
/>
<Button
android:id="@+id/next_turn"
android:text="I'm done"
android:minEms="10"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
/>
</LinearLayout>
</LinearLayout>

View File

@@ -19,13 +19,13 @@
android:orientation="vertical" android:orientation="vertical"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:weightSum="2" android:gravity="center_horizontal"
> >
<com.example.tictactoe.library.GameView <com.example.tictactoe.library.GameView
android:id="@+id/game_view" android:id="@+id/game_view"
android:layout_width="fill_parent" android:layout_width="wrap_content"
android:layout_height="fill_parent" android:layout_height="wrap_content"
android:layout_margin="20dip" android:layout_margin="20dip"
android:layout_weight="1" android:layout_weight="1"
/> />
@@ -35,16 +35,16 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:layout_weight="1" android:layout_marginBottom="10dip"
/> />
<Button <Button
android:id="@+id/info_turn" android:id="@+id/next_turn"
android:text="I'm done"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="20dip" android:layout_marginLeft="20dip"
android:layout_marginRight="20dip" android:layout_marginRight="20dip"
android:layout_weight="1"
/> />
</LinearLayout> </LinearLayout>

View File

@@ -16,6 +16,5 @@
--> -->
<resources> <resources>
<!-- TODO externalize strings here. -->
</resources> </resources>

View File

@@ -16,15 +16,36 @@
package com.example.tictactoe.library; package com.example.tictactoe.library;
import java.util.Random;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Handler.Callback;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import com.example.tictactoe.library.GameView.ICellListener;
import com.example.tictactoe.library.GameView.State;
public class GameActivity extends Activity { public class GameActivity extends Activity {
public final static String EXTRA_START_WITH_HUMAN = /** Start player. Must be 1 or 2. Default is 1. */
"com.example.tictactoe.library.GameActivity.EXTRA_START_WITH_HUMAN"; public static final String EXTRA_START_PLAYER =
"com.example.tictactoe.library.GameActivity.EXTRA_START_PLAYER";
private boolean mTurnIsHuman; private static final int MSG_COMPUTER_TURN = 1;
private static final long COMPUTER_DELAY_MS = 500;
private Handler mHandler = new Handler(new MyHandlerCallback());
private Random mRnd = new Random();
private GameView mGameView;
private TextView mInfoView;
private Button mButtonNext;
/** Called when the activity is first created. */ /** Called when the activity is first created. */
@Override @Override
@@ -32,22 +53,207 @@ public class GameActivity extends Activity {
super.onCreate(bundle); super.onCreate(bundle);
/* /*
* IMPORTANT TIP: all resource IDs from this library must be * IMPORTANT: all resource IDs from this library will eventually be merged
* different from the projects that will include it. E.g. * with the resources from the main project that will use the library.
* if my layout were named "main.xml", I would have to use the ID
* R.layout.main; however there is already a *different*
* ID with the same name in the main project and when the library
* gets merged in the project the wrong ID would end up being used.
* *
* To avoid such potential conflicts, it's probably a good idea * If the main project and the libraries define the same resource IDs,
* to add a prefix to the library resource names. * the application project will always have priority and override library resources
* and IDs defined in multiple libraries are resolved based on the libraries priority
* defined in the main project.
*
* An intentional consequence is that the main project can override some resources
* from the library.
* (TODO insert example).
*
* To avoid potential conflicts, it is suggested to add a prefix to the
* library resource names.
*/ */
setContentView(R.layout.lib_game); setContentView(R.layout.lib_game);
mTurnIsHuman = getIntent().getBooleanExtra( mGameView = (GameView) findViewById(R.id.game_view);
EXTRA_START_WITH_HUMAN, true); mInfoView = (TextView) findViewById(R.id.info_turn);
mButtonNext = (Button) findViewById(R.id.next_turn);
mGameView.setFocusable(true);
mGameView.setFocusableInTouchMode(true);
mGameView.setCellListener(new MyCellListener());
mButtonNext.setOnClickListener(new MyButtonListener());
} }
@Override
protected void onResume() {
super.onResume();
State player = mGameView.getCurrentPlayer();
if (player == State.UNKNOWN) {
player = State.fromInt(getIntent().getIntExtra(EXTRA_START_PLAYER, 1));
if (!checkGameFinished(player)) {
selectTurn(player);
}
}
if (player == State.PLAYER2) {
mHandler.sendEmptyMessageDelayed(MSG_COMPUTER_TURN, COMPUTER_DELAY_MS);
}
if (player == State.WIN) {
setWinState(mGameView.getWinner());
}
}
private State selectTurn(State player) {
mGameView.setCurrentPlayer(player);
mButtonNext.setEnabled(false);
if (player == State.PLAYER1) {
mInfoView.setText("Player 1's turn -- that's you!");
mGameView.setEnabled(true);
} else if (player == State.PLAYER2) {
mInfoView.setText("Player 2's turn (that's the computer)");
mGameView.setEnabled(false);
}
return player;
}
private class MyCellListener implements ICellListener {
public void onCellSelected() {
if (mGameView.getCurrentPlayer() == State.PLAYER1) {
int cell = mGameView.getSelection();
mButtonNext.setEnabled(cell >= 0);
}
}
}
private class MyButtonListener implements OnClickListener {
public void onClick(View v) {
State player = mGameView.getCurrentPlayer();
if (player == State.WIN) {
GameActivity.this.finish();
} else if (player == State.PLAYER1) {
int cell = mGameView.getSelection();
if (cell >= 0) {
mGameView.stopBlink();
mGameView.setCell(cell, player);
finishTurn();
}
}
}
}
private class MyHandlerCallback implements Callback {
public boolean handleMessage(Message msg) {
if (msg.what == MSG_COMPUTER_TURN) {
// Pick a non-used cell at random. That's about all the AI you need for this game.
State[] data = mGameView.getData();
int used = 0;
while (used != 0x1F) {
int index = mRnd.nextInt(9);
if (((used >> index) & 1) == 0) {
used |= 1 << index;
if (data[index] == State.EMPTY) {
mGameView.setCell(index, mGameView.getCurrentPlayer());
break;
}
}
}
finishTurn();
return true;
}
return false;
}
}
private State getOtherPlayer(State player) {
return player == State.PLAYER1 ? State.PLAYER2 : State.PLAYER1;
}
private void finishTurn() {
State player = mGameView.getCurrentPlayer();
if (!checkGameFinished(player)) {
player = selectTurn(getOtherPlayer(player));
if (player == State.PLAYER2) {
mHandler.sendEmptyMessageDelayed(MSG_COMPUTER_TURN, COMPUTER_DELAY_MS);
}
}
}
public boolean checkGameFinished(State player) {
State[] data = mGameView.getData();
boolean full = true;
int col = -1;
int row = -1;
int diag = -1;
// check rows
for (int j = 0, k = 0; j < 3; j++, k += 3) {
if (data[k] != State.EMPTY && data[k] == data[k+1] && data[k] == data[k+2]) {
row = j;
}
if (full && (data[k] == State.EMPTY ||
data[k+1] == State.EMPTY ||
data[k+2] == State.EMPTY)) {
full = false;
}
}
// check columns
for (int i = 0; i < 3; i++) {
if (data[i] != State.EMPTY && data[i] == data[i+3] && data[i] == data[i+6]) {
col = i;
}
}
// check diagonals
if (data[0] != State.EMPTY && data[0] == data[1+3] && data[0] == data[2+6]) {
diag = 0;
} else if (data[2] != State.EMPTY && data[2] == data[1+3] && data[2] == data[0+6]) {
diag = 1;
}
if (col != -1 || row != -1 || diag != -1) {
setFinished(player, col, row, diag);
return true;
}
// if we get here, there's no winner but the board is full.
if (full) {
setFinished(State.EMPTY, -1, -1, -1);
return true;
}
return false;
}
private void setFinished(State player, int col, int row, int diagonal) {
mGameView.setCurrentPlayer(State.WIN);
mGameView.setWinner(player);
mGameView.setEnabled(false);
mGameView.setFinished(col, row, diagonal);
setWinState(player);
}
private void setWinState(State player) {
mButtonNext.setEnabled(true);
mButtonNext.setText("Back");
String text;
if (player == State.EMPTY) {
text = "This is a tie! No one wins!";
} else if (player == State.PLAYER1) {
text = "Player 1 (you) wins!";
} else {
text = "Player 2 (computer) wins!";
}
mInfoView.setText(text);
}
} }

View File

@@ -16,7 +16,7 @@
package com.example.tictactoe.library; package com.example.tictactoe.library;
import java.lang.reflect.Field; import java.util.Random;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
@@ -25,10 +25,14 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory.Options; import android.graphics.BitmapFactory.Options;
import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.os.Parcelable;
import android.os.Handler.Callback; import android.os.Handler.Callback;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
@@ -39,41 +43,166 @@ import android.view.View;
public class GameView extends View { public class GameView extends View {
private final static String TAG = "GameView"; private static final String TAG = "GameView";
private static final int MSG_FRAME = 1;
public static final long FPS_MS = 1000/2; public static final long FPS_MS = 1000/2;
public static final int EMPTY = 0; public enum State {
public static final int CROSS = 1; UNKNOWN(-3),
public static final int CIRCLE = 2; WIN(-2),
EMPTY(0),
PLAYER1(1),
PLAYER2(2);
private int mValue;
/** Contains one of {@link #EMPTY}, {@link #CROSS} or {@link #CIRCLE} */ private State(int value) {
private final int[] mData = new int[9]; mValue = value;
}
private final Rect mBgRect = new Rect(); public int getValue() {
private final Rect mTempDst = new Rect(); return mValue;
}
public static State fromInt(int i) {
for (State s : values()) {
if (s.getValue() == i) {
return s;
}
}
return EMPTY;
}
}
private static final int MARGIN = 4;
private static final int MSG_BLINK = 1;
private final Handler mHandler = new Handler(new MyHandler());
private final Rect mSrcRect = new Rect();
private final Rect mDstRect = new Rect();
private int mSxy; private int mSxy;
private int mOffetX; private int mOffetX;
private int mOffetY; private int mOffetY;
private Paint mWinPaint;
private Paint mLinePaint; private Paint mLinePaint;
private Paint mBmpPaint;
private Bitmap mBmpPlayer1;
private Bitmap mBmpPlayer2;
private Drawable mDrawableBg; private Drawable mDrawableBg;
private ICellListener mCellListener;
/** Contains one of {@link State#EMPTY}, {@link State#PLAYER1} or {@link State#PLAYER2}. */
private final State[] mData = new State[9];
private int mSelectedCell = -1;
private State mSelectedValue = State.EMPTY;
private State mCurrentPlayer = State.UNKNOWN;
private State mWinner = State.EMPTY;
private int mWinCol = -1;
private int mWinRow = -1;
private int mWinDiag = -1;
private boolean mBlinkDisplayOff;
private final Rect mBlinkRect = new Rect();
public interface ICellListener {
abstract void onCellSelected();
}
public GameView(Context context, AttributeSet attrs) { public GameView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
requestFocus(); requestFocus();
mDrawableBg = getResources().getDrawable(R.drawable.lib_bg); mDrawableBg = getResources().getDrawable(R.drawable.lib_bg);
setBackgroundDrawable(mDrawableBg);
mBmpPlayer1 = getResBitmap(R.drawable.lib_cross);
mBmpPlayer2 = getResBitmap(R.drawable.lib_circle);
if (mBmpPlayer1 != null) {
mSrcRect.set(0, 0, mBmpPlayer1.getWidth() -1, mBmpPlayer1.getHeight() - 1);
}
mBmpPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint = new Paint(); mLinePaint = new Paint();
mLinePaint.setColor(0xFFFFFFFF); mLinePaint.setColor(0xFFFFFFFF);
mLinePaint.setStrokeWidth(5); mLinePaint.setStrokeWidth(5);
mLinePaint.setStyle(Style.STROKE);
mWinPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mWinPaint.setColor(0xFFFF0000);
mWinPaint.setStrokeWidth(10);
mWinPaint.setStyle(Style.STROKE);
for (int i = 0; i < mData.length; i++) {
mData[i] = State.EMPTY;
}
if (isInEditMode()) {
// In edit mode (e.g. in the Eclipse ADT graphical layout editor)
// we'll use some random data to display the state.
Random rnd = new Random();
for (int i = 0; i < mData.length; i++) {
mData[i] = State.fromInt(rnd.nextInt(3));
}
}
} }
public State[] getData() {
return mData;
}
public void setCell(int cellIndex, State value) {
mData[cellIndex] = value;
invalidate();
}
public void setCellListener(ICellListener cellListener) {
mCellListener = cellListener;
}
public int getSelection() {
if (mSelectedValue == mCurrentPlayer) {
return mSelectedCell;
}
return -1;
}
public State getCurrentPlayer() {
return mCurrentPlayer;
}
public void setCurrentPlayer(State player) {
mCurrentPlayer = player;
mSelectedCell = -1;
}
public State getWinner() {
return mWinner;
}
public void setWinner(State winner) {
mWinner = winner;
}
/** Sets winning mark on specified column or row (0..2) or diagonal (0..1). */
public void setFinished(int col, int row, int diagonal) {
mWinCol = col;
mWinRow = row;
mWinDiag = diagonal;
}
//-----------------------------------------
@Override @Override
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
@@ -83,24 +212,69 @@ public class GameView extends View {
int x7 = mOffetX; int x7 = mOffetX;
int y7 = mOffetY; int y7 = mOffetY;
mDrawableBg.draw(canvas);
for (int i = 0, k = sxy; i < 2; i++, k += sxy) { for (int i = 0, k = sxy; i < 2; i++, k += sxy) {
canvas.drawLine(x7 , y7 + k, x7 + s3, y7 + k , mLinePaint); canvas.drawLine(x7 , y7 + k, x7 + s3 - 1, y7 + k , mLinePaint);
canvas.drawLine(x7 + k, y7 , x7 + k , y7 + s3, mLinePaint); canvas.drawLine(x7 + k, y7 , x7 + k , y7 + s3 - 1, mLinePaint);
} }
for (int j = 0, k = 0, y = y7; j < 3; j++, y += sxy) { for (int j = 0, k = 0, y = y7; j < 3; j++, y += sxy) {
for (int i = 0, x = x7; i < 3; i++, x += sxy) { for (int i = 0, x = x7; i < 3; i++, k++, x += sxy) {
mDstRect.offsetTo(MARGIN+x, MARGIN+y);
State v;
if (mSelectedCell == k) {
if (mBlinkDisplayOff) {
continue;
}
v = mSelectedValue;
} else {
v = mData[k];
}
switch(v) {
case PLAYER1:
if (mBmpPlayer1 != null) {
canvas.drawBitmap(mBmpPlayer1, mSrcRect, mDstRect, mBmpPaint);
}
break;
case PLAYER2:
if (mBmpPlayer2 != null) {
canvas.drawBitmap(mBmpPlayer2, mSrcRect, mDstRect, mBmpPaint);
}
break;
}
} }
} }
if (mWinRow >= 0) {
int y = y7 + mWinRow * sxy + sxy / 2;
canvas.drawLine(x7 + MARGIN, y, x7 + s3 - 1 - MARGIN, y, mWinPaint);
} else if (mWinCol >= 0) {
int x = x7 + mWinCol * sxy + sxy / 2;
canvas.drawLine(x, y7 + MARGIN, x, y7 + s3 - 1 - MARGIN, mWinPaint);
} else if (mWinDiag == 0) {
// diagonal 0 is from (0,0) to (2,2)
canvas.drawLine(x7 + MARGIN, y7 + MARGIN,
x7 + s3 - 1 - MARGIN, y7 + s3 - 1 - MARGIN, mWinPaint);
} else if (mWinDiag == 1) {
// diagonal 1 is from (0,2) to (2,0)
canvas.drawLine(x7 + MARGIN, y7 + s3 - 1 - MARGIN,
x7 + s3 - 1 - MARGIN, y7 + MARGIN, mWinPaint);
}
} }
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Keep the view squared
super.onMeasure(widthMeasureSpec, heightMeasureSpec); int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
int d = w == 0 ? h : h == 0 ? w : w < h ? w : h;
setMeasuredDimension(d, d);
} }
@Override @Override
@@ -108,8 +282,8 @@ public class GameView extends View {
super.onSizeChanged(w, h, oldw, oldh); super.onSizeChanged(w, h, oldw, oldh);
Log.d(TAG, String.format("onSizeChanged: %dx%d", w, h)); Log.d(TAG, String.format("onSizeChanged: %dx%d", w, h));
int sx = w / 3; int sx = (w - 2 * MARGIN) / 3;
int sy = h / 3; int sy = (h - 2 * MARGIN) / 3;
int size = sx < sy ? sx : sy; int size = sx < sy ? sx : sy;
@@ -117,8 +291,7 @@ public class GameView extends View {
mOffetX = (w - 3 * size) / 2; mOffetX = (w - 3 * size) / 2;
mOffetY = (h - 3 * size) / 2; mOffetY = (h - 3 * size) / 2;
mDrawableBg.setBounds(mOffetX, mOffetY, mDstRect.set(MARGIN, MARGIN, size - MARGIN, size - MARGIN);
mOffetX + 3 * size, mOffetY + 3 * size);
} }
@Override @Override
@@ -128,23 +301,170 @@ public class GameView extends View {
if (action == MotionEvent.ACTION_DOWN) { if (action == MotionEvent.ACTION_DOWN) {
return true; return true;
} else if (action == MotionEvent.ACTION_DOWN) { } else if (action == MotionEvent.ACTION_UP) {
float x = event.getX(); int x = (int) event.getX();
float y = event.getY(); int y = (int) event.getY();
int sxy = mSxy;
x = (x - MARGIN) / sxy;
y = (y - MARGIN) / sxy;
if (isEnabled() && x >= 0 && x < 3 && y >= 0 & y < 3) {
int cell = x + 3 * y;
State state = cell == mSelectedCell ? mSelectedValue : mData[cell];
state = state == State.EMPTY ? mCurrentPlayer : State.EMPTY;
stopBlink();
mSelectedCell = cell;
mSelectedValue = state;
mBlinkDisplayOff = false;
mBlinkRect.set(MARGIN + x * sxy, MARGIN + y * sxy,
MARGIN + (x + 1) * sxy, MARGIN + (y + 1) * sxy);
if (state != State.EMPTY) {
// Start the blinker
mHandler.sendEmptyMessageDelayed(MSG_BLINK, FPS_MS);
}
if (mCellListener != null) {
mCellListener.onCellSelected();
}
}
// TODO
return true; return true;
} }
return false; return false;
} }
public Bitmap getResBitmap(int bmpResId) { public void stopBlink() {
boolean hadSelection = mSelectedCell != -1 && mSelectedValue != State.EMPTY;
mSelectedCell = -1;
mSelectedValue = State.EMPTY;
if (!mBlinkRect.isEmpty()) {
invalidate(mBlinkRect);
}
mBlinkDisplayOff = false;
mBlinkRect.setEmpty();
mHandler.removeMessages(MSG_BLINK);
if (hadSelection && mCellListener != null) {
mCellListener.onCellSelected();
}
}
@Override
protected Parcelable onSaveInstanceState() {
Bundle b = new Bundle();
Parcelable s = super.onSaveInstanceState();
b.putParcelable("gv_super_state", s);
b.putBoolean("gv_en", isEnabled());
int[] data = new int[mData.length];
for (int i = 0; i < data.length; i++) {
data[i] = mData[i].getValue();
}
b.putIntArray("gv_data", data);
b.putInt("gv_sel_cell", mSelectedCell);
b.putInt("gv_sel_val", mSelectedValue.getValue());
b.putInt("gv_curr_play", mCurrentPlayer.getValue());
b.putInt("gv_winner", mWinner.getValue());
b.putInt("gv_win_col", mWinCol);
b.putInt("gv_win_row", mWinRow);
b.putInt("gv_win_diag", mWinDiag);
b.putBoolean("gv_blink_off", mBlinkDisplayOff);
b.putParcelable("gv_blink_rect", mBlinkRect);
return b;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof Bundle)) {
// Not supposed to happen.
super.onRestoreInstanceState(state);
return;
}
Bundle b = (Bundle) state;
Parcelable superState = b.getParcelable("gv_super_state");
setEnabled(b.getBoolean("gv_en", true));
int[] data = b.getIntArray("gv_data");
if (data != null && data.length == mData.length) {
for (int i = 0; i < data.length; i++) {
mData[i] = State.fromInt(data[i]);
}
}
mSelectedCell = b.getInt("gv_sel_cell", -1);
mSelectedValue = State.fromInt(b.getInt("gv_sel_val", State.EMPTY.getValue()));
mCurrentPlayer = State.fromInt(b.getInt("gv_curr_play", State.EMPTY.getValue()));
mWinner = State.fromInt(b.getInt("gv_winner", State.EMPTY.getValue()));
mWinCol = b.getInt("gv_win_col", -1);
mWinRow = b.getInt("gv_win_row", -1);
mWinDiag = b.getInt("gv_win_diag", -1);
mBlinkDisplayOff = b.getBoolean("gv_blink_off", false);
Rect r = b.getParcelable("gv_blink_rect");
if (r != null) {
mBlinkRect.set(r);
}
// let the blink handler decide if it should blink or not
mHandler.sendEmptyMessage(MSG_BLINK);
super.onRestoreInstanceState(superState);
}
//-----
private class MyHandler implements Callback {
public boolean handleMessage(Message msg) {
if (msg.what == MSG_BLINK) {
if (mSelectedCell >= 0 && mSelectedValue != State.EMPTY && mBlinkRect.top != 0) {
mBlinkDisplayOff = !mBlinkDisplayOff;
invalidate(mBlinkRect);
if (!mHandler.hasMessages(MSG_BLINK)) {
mHandler.sendEmptyMessageDelayed(MSG_BLINK, FPS_MS);
}
}
return true;
}
return false;
}
}
private Bitmap getResBitmap(int bmpResId) {
Options opts = new Options(); Options opts = new Options();
opts.inDither = false; opts.inDither = false;
Resources res = getResources(); Resources res = getResources();
Bitmap bmp = BitmapFactory.decodeResource(res, bmpResId, opts); Bitmap bmp = BitmapFactory.decodeResource(res, bmpResId, opts);
if (bmp == null && isInEditMode()) {
// BitmapFactory.decodeResource doesn't work from the rendering
// library in Eclipse's Graphical Layout Editor. Use this workaround instead.
Drawable d = res.getDrawable(bmpResId);
int w = d.getIntrinsicWidth();
int h = d.getIntrinsicHeight();
bmp = Bitmap.createBitmap(w, h, Config.ARGB_8888);
Canvas c = new Canvas(bmp);
d.setBounds(0, 0, w - 1, h - 1);
d.draw(c);
}
return bmp; return bmp;
} }
} }

View File

@@ -30,7 +30,7 @@
<!-- This is defined in TicTacToeLib. Right now we need to manually <!-- This is defined in TicTacToeLib. Right now we need to manually
copy it here. Eventually it should get merged automatically. --> copy it here. Eventually it should get merged automatically. -->
<activity android:name=".library.GameActivity" /> <activity android:name="com.example.tictactoe.library.GameActivity" />
</application> </application>

View File

@@ -0,0 +1,48 @@
Sample: TicTacToeLib and TicTacToeMain.
--------
Summary:
--------
These two projects work together. They demonstrate how to use the ability to
split an APK into multiple projects.
Build is supported both via Ant (command-line tools) or via ADT (the Android
plugin for Eclipse).
--------
Details:
--------
TicTacToeMain is the main project. It defines a main activity that is first
displayed to the user. When one of the start buttons is selected, an
activity defined in TicTacToeLib is started.
To define that TicTacToeMain uses TicTacToeLib as a "project library", the
file TicTacToeMain/default.properties contains the special line:
android.library.reference.1=../TicTacToeLib/
TicTacToeLib is the "project library". It can contain both source code (.java)
and Android resources (anything under /res) that will be merged in the final
APK. To define this is a library, the file TicTacToeLib/default.project
contains the special line:
android.library=true
One important thing to realize is that the library is not a separately-compiled
JAR file: the source and resources from the library are _actually_ merged in
the main project and the result is used to generate the APK. This means that
the main project can either use or redefine behavior from the libraries.
To use the main vs livrary project:
- In ADT, just open import both projects and launch the main project.
- In Ant, use 'android update project' to create the build files and set the SDK location,
and then run 'ant debug' on the main project.
For more details on the purpose of this feature, its limitations and detailed usage,
please read the SDK guide at
<STOPSHIP insert URL here to library-project on developers.android.com>

View File

@@ -24,5 +24,6 @@
# project structure. # project structure.
android.library.reference.1=../TicTacToeLib/ android.library.reference.1=../TicTacToeLib/
# Project target. # Project target. This requires the tools from SDK Froyo (API 8) to be used.
# STOPSHIP change to target=8 once Froyo API is set to 8.
target=android-Froyo target=android-Froyo

View File

@@ -21,6 +21,5 @@
<string name="welcome"><b>Welcome to the Tic-Tac-Toe Sample!</b></string> <string name="welcome"><b>Welcome to the Tic-Tac-Toe Sample!</b></string>
<string name="explain1">This sample code demonstrates how to split an application in multiple projects by using the \'project library\' available in the Froyo SDK Tools.</string> <string name="explain1">This sample code demonstrates how to split an application in multiple projects by using the \'project library\' available in the Froyo SDK Tools.</string>
<string name="explain2">This activity is defined in one project. The second activity, launched by one of the buttons below, is located in another project which is a \"library\" to the main one and merged in the same APK.</string> <string name="explain2">This activity is defined in one project. The second activity, launched by one of the buttons below, is located in another project which is a \"library\" to the main one and merged in the same APK.</string>
<string name="hello">Hello World, MainActivity!</string>
<string name="app_name">Tic-Tac-Toe Sample</string> <string name="app_name">Tic-Tac-Toe Sample</string>
</resources> </resources>

View File

@@ -16,6 +16,7 @@
package com.example.tictactoe; package com.example.tictactoe;
import com.example.tictactoe.library.GameActivity; import com.example.tictactoe.library.GameActivity;
import com.example.tictactoe.library.GameView.State;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
@@ -47,7 +48,8 @@ public class MainActivity extends Activity {
private void startGame(boolean startWithHuman) { private void startGame(boolean startWithHuman) {
Intent i = new Intent(this, GameActivity.class); Intent i = new Intent(this, GameActivity.class);
i.putExtra(GameActivity.EXTRA_START_WITH_HUMAN, startWithHuman); i.putExtra(GameActivity.EXTRA_START_PLAYER,
startWithHuman ? State.PLAYER1.getValue() : State.PLAYER2.getValue());
startActivity(i); startActivity(i);
} }
} }