作为一个没有学习Android的菜鸟,近期一直在工作之外努力地学习的Android的使用。
这周看了下Android的画图。主要是Canvas,Paint等,感觉须要实践下。下午正好有空,就想整一个乒乓球的游戏,算是巩固学的知识。
首先,须要了解下Android的画图须要掌握的经常使用类。包含Canvas,就像一个画板一样,全部的东西都是在其上画的。Paint就是画笔。用其能够画各种基本图形和文字。 Canvas和Paint经常使用的方法就不列举了,这种东西网上到处是。有了这两个东西。想实现游戏,还差个手势感应,对这个游戏来说。仅仅须要识别出左移和右移就可以。
为此,我们须要使用Android的OnGestureListener接口和GestureDetector类。
重要的方法例如以下所看到的:
GestureDetector detector; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_start); detector = new GestureDetector(this, this); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub return detector.onTouchEvent(event); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // 最小移动标准 float minMove = 30; if ((e1.getX() - e2.getX()) > minMove && Math.abs(velocityX) > Math.abs(velocityY)) { Toast.makeText(this, "Move to Left", Toast.LENGTH_LONG).show(); } else if ((e2.getX() - e1.getX()) > minMove && Math.abs(velocityX) > Math.abs(velocityY)) { Toast.makeText(this, "Move to Right", Toast.LENGTH_LONG).show(); } return false; }
我们须要在onTouchEvent中使用调用GestureDetector的onTouchEvent()方法,仅仅有这样手势感应的接口才干够被回调。当中。onFling()方法中,我们实现了左移和右移的推断。
游戏的截图例如以下所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >
实现该游戏,主要须要考虑的地方有下面几个地方:
1.游戏的状态怎样描写叙述。详细来说,就是小球和球拍的状态
2.视图怎样动态刷新
对问题1来说,球拍的状态比較简单。仅仅有左移和右移两种,设置球拍的x,y坐标,利用步长就可非常好改变,小球的话。略微麻烦一些。我们须要用其x,y坐标。x移动速度和y移动速度来控制其状态。碰到左右强。x速度反转,碰到球拍或者上边y速度反转,碰到下边(未碰到球拍)则比赛结束。
对问题2来说,我们须要启动定时刷新任务,定时运行推断当前状态,然后依据数据来刷新视图。
以下就主要介绍下自己的小游戏实现了。代母包括以下几个类,TablePlayActivity。TableView,MsgDefine,TablePlayModel。TablePlayController。
TablePlayActivity例如以下:
package com.example.tableplay; import android.support.v7.app.ActionBarActivity; import android.app.Service; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Vibrator; import android.util.DisplayMetrics; import android.util.Log; import android.view.GestureDetector; import android.view.GestureDetector.OnGestureListener; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.Window; import android.view.WindowManager; import android.widget.Toast; public class TablePlayActivity extends ActionBarActivity implements OnGestureListener{ private GestureDetector dector; private TableView myView; private Vibrator vibrator; @Override protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); init(); dector = new GestureDetector(this, this); vibrator = (Vibrator) getSystemService(Service.VIBRATOR_SERVICE); } private void init() { myView = new TableView(this); setContentView(myView); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); WindowManager wm = getWindowManager(); DisplayMetrics metrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(metrics); Log.i("dingbin", "width " + metrics.widthPixels + "height " + metrics.heightPixels); TablePlayModel.getInstance().mScreenX = metrics.widthPixels; TablePlayModel.getInstance().mScreenY = metrics.heightPixels; TablePlayModel.getInstance().mRacketY = metrics.heightPixels - TablePlayModel.getInstance().SHIFT_DISTANCE; createHandler(); TablePlayController.getInstance().startGame(mHandler); } private Handler mHandler; public void createHandler() { mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch(msg.what) { case MsgDefine.MSG_TYPE_REFRESH:{ myView.invalidate(); break; } case MsgDefine.MSG_TYPE_VIBROTOR:{ vibrator.vibrate(1000); Toast.makeText(TablePlayActivity.this, "Nice", Toast.LENGTH_SHORT).show(); break; } default :break; } } }; } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.table_play, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } @Override public boolean onTouchEvent(MotionEvent event) { return dector.onTouchEvent(event); } @Override public boolean onDown(MotionEvent e) { // TODO Auto-generated method stub return false; } @Override public void onShowPress(MotionEvent e) { // TODO Auto-generated method stub } @Override public boolean onSingleTapUp(MotionEvent e) { // TODO Auto-generated method stub return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // TODO Auto-generated method stub return false; } @Override public void onLongPress(MotionEvent e) { // TODO Auto-generated method stub } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // 最小移动标准 float minMove = 30; if ((e1.getX() - e2.getX()) > minMove && Math.abs(velocityX) > Math.abs(velocityY)) { // 左移 TablePlayController.getInstance().racketMoveLeft(); } else if ((e2.getX() - e1.getX()) > minMove && Math.abs(velocityX) > Math.abs(velocityY)) { // 右移 TablePlayController.getInstance().racketMoveRight(); } // 重绘View myView.invalidate(); return false; } }
在TablePlayActivity类中。我们主要实现了手势感应的推断和Handler消息的处理,另外初始化中获取了屏幕的宽和高。
TableView是我们基本的绘图类,我们在其的onDraw方法中,实现了小球和球拍的重绘,代码例如以下:
package com.example.tableplay; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.util.Log; import android.view.View; import android.widget.Toast; public class TableView extends View{ private Paint paint; private Context mContext; public TableView(Context context) { super(context); mContext = context; // 创建画笔 paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Style.FILL); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); TablePlayModel data = TablePlayModel.getInstance(); if (data.isGameOver) { paint.setTextSize(80); paint.setColor(Color.BLUE); setBackgroundResource(R.drawable.game_over); canvas.drawText("Game is over!", 270, data.mScreenY / 2, paint); } else { // 画球拍 paint.setColor(Color.GREEN); canvas.drawRect(data.mRacketX, data.mRacketY, data.mRacketX + data.RACKET_WIDTH, data.mRacketY + data.RACKET_HEIGHT, paint); // 画球 paint.setColor(Color.GRAY); canvas.drawCircle(data.mBallX, data.mBallY, data.mBallRadius, paint); } } }
接下来是数据类TablePlayModel,该类主要缓存我们须要使用的数据,包含我们全部须要使用的游戏状态和常量。代码例如以下:
package com.example.tableplay; import java.util.Random; import android.util.Log; public class TablePlayModel { private static TablePlayModel mInstance = null; // 屏幕的宽和高 public int mScreenX; public int mScreenY; // 球拍的宽度和高度 public final int RACKET_WIDTH = 180; public final int RACKET_HEIGHT = 25; // 球拍移动的步长 public int mRacketStep = 150; // 球拍的水平和垂直位置 public int mRacketX = 100; public int mRacketY; private Random rand = new Random(); // 小球的半径 public int mBallRadius = 35; // 小球的x,y坐标 public int mBallX = rand.nextInt(200) + 20; public int mBallY = rand.nextInt(500) + 20; // 小球x,y移动速度 public int mBallYSpeed = 28; private double mXRate = rand.nextDouble() - 0.5; public int mBallXSpeed = (int) (mBallYSpeed * mXRate * 2); // 刷新间隔 ms public int mTimeInterval = 100; // 刷新延时 ms public int mTimeDelay = 0; public final int SHIFT_DISTANCE = 200; public boolean isGameOver = false; public static TablePlayModel getInstance() { if (mInstance == null) { synchronized (TablePlayModel.class) { if (mInstance == null) { Log.i("dingbin", "TablePlayModel.getInstance()"); mInstance = new TablePlayModel(); } } } return mInstance; } }
接下来是最重要的控制类TablePlayController,我们的核心逻辑都在该类中。详细来说。就是启动定时器运行任务,依据当前的状态运行小球的运动位置,更新小球的坐标。并通知视图刷新界面。代码例如以下:
package com.example.tableplay; import java.util.Timer; import java.util.TimerTask; import android.os.Handler; import android.os.Message; import android.util.Log; import android.widget.Toast; public class TablePlayController { private static TablePlayController mInstance = null; private TablePlayModel data = TablePlayModel.getInstance(); private TablePlayController() { } public static TablePlayController getInstance() { if (mInstance == null) { synchronized (TablePlayController.class) { if (mInstance == null) { return new TablePlayController(); } } } return mInstance; } public void startGame(final Handler handler) { Log.i("dingbin", "startGame"); data = TablePlayModel.getInstance(); Log.i("dingbin", data.toString()); final Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { // 假设碰到左右边界 if (data.mBallX <= data.mBallRadius || data.mBallX >= data.mScreenX - data.mBallRadius) { data.mBallXSpeed = -data.mBallXSpeed; } // 假设位置低于球拍的高度可是不在球拍的范围内,则比赛结束 if (data.mBallY > data.mRacketY && (data.mBallX < data.mRacketX || data.mBallX > data.mRacketX + data.RACKET_WIDTH)) { timer.cancel(); data.isGameOver = true; } else if(data.mBallY <= data.mBallRadius ) { data.mBallYSpeed = - data.mBallYSpeed; } else if ( (data.mBallX >= data.mRacketX && data.mBallX <= data.mRacketX + data.RACKET_WIDTH && (data.mBallY + data.mBallRadius) >= data.mRacketY)) { data.mBallYSpeed = - data.mBallYSpeed; Message msg = Message.obtain(handler); msg.what = MsgDefine.MSG_TYPE_VIBROTOR; msg.sendToTarget(); } // 小球坐标改变 data.mBallX += data.mBallXSpeed; data.mBallY += data.mBallYSpeed; // 发送消息 Message msg = Message.obtain(handler); msg.what = MsgDefine.MSG_TYPE_REFRESH; msg.sendToTarget(); } }, data.mTimeDelay, data.mTimeInterval); } /* * 球拍左移 */ public void racketMoveLeft() { if (data.mRacketX >= data.mRacketStep) { data.mRacketX += -data.mRacketStep; } else { data.mRacketX = 0; } } /* * 球拍右移 */ public void racketMoveRight() { if ((data.mRacketX + data.RACKET_WIDTH) < data.mScreenX) { data.mRacketX += data.mRacketStep; } else { data.mRacketX = data.mScreenX - data.RACKET_WIDTH; } } }
为了实现较好的效果,我们在成功打击球时,会震动一下(须要在AndroidManifest.xml中添加<uses-permission android:name="android.permission.VIBRATE"/>),并弹toast祝贺。
最后剩下的是信息数据类MsgDefine。存放着数据的类型。详细来说,包含刷新消息和震动消息类型,代码例如以下:
package com.example.tableplay; public class MsgDefine { public static final int MSG_TYPE_REFRESH = 1; public static final int MSG_TYPE_VIBROTOR = 2; }
以上就是小游戏的所有代码实现,做的比較粗糙,希望后面有机会了能够优化下。