作为一个没有学习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" alt="">

实现该游戏,主要须要考虑的地方有下面几个地方:

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

以上就是小游戏的所有代码实现,做的比較粗糙,希望后面有机会了能够优化下。

Android使用学习之画图(Canvas,Paint)与手势感应及其应用(乒乓球小游戏)的更多相关文章

  1. canvas绘制“飞机大战”小游戏,真香!

    canvas是ArkUI开发框架里的画布组件,常用于自定义绘制图形.因为其轻量.灵活.高效等优点,被广泛应用于UI界面开发中. 本期,我们将为大家介绍canvas组件的使用. 一.canvas介绍 1 ...

  2. 近期微信上非常火的小游戏【壹秒】android版——开发分享

    近期在朋友圈,朋友转了一个html小游戏[壹秒],游戏的规则是:用户按住button然后释放,看谁能精准地保持一秒的时间.^_^刚好刚才在linuxserver上调试程序的时候server挂了,腾出点 ...

  3. android学习5——画图问题

    重写View中的onDraw函数可以实现画图.代码如下: @Override public void onDraw(Canvas canvas) { Paint paint = new Paint() ...

  4. Android 2D Graphics学习 Region和Canvas裁剪

    1.首先介绍Region类 Region,中文意思即区域的意思,它表示的是canvas图层上的某一块封闭的区域. /**构造方法*/ public Region()  //创建一个空的区域 publi ...

  5. android 绘图之Canvas,Paint类

    Canvas,Paint 1.在android 绘图但中经常要用到Canvas和Paint类,Canvas好比是一张画布,上面已经有你想绘制图画的轮廓了,而Paint就好比是画笔,就要给Canvas进 ...

  6. Android为TV端助力 Canvas 和 Paint用法

    自定义view里面的onDraw方法,在这里我们可以绘制各种图形,onDraw里面有两个API我们需要了解清楚他们的用法:Canvas 和 Paint. Canvas翻译成中文就是画布的意思,Canv ...

  7. Android Animation学习(四) ApiDemos解析:多属性动画

    Android Animation学习(四) ApiDemos解析:多属性动画 如果想同时改变多个属性,根据前面所学的,比较显而易见的一种思路是构造多个对象Animator , ( Animator可 ...

  8. Android Animation学习(三) ApiDemos解析:XML动画文件的使用

    Android Animation学习(三) ApiDemos解析:XML动画文件的使用 可以用XML文件来定义Animation. 文件必须有一个唯一的根节点: <set>, <o ...

  9. Android Animation学习(二) ApiDemos解析:基本Animators使用

    Android Animation学习(二) ApiDemos解析:基本Animatiors使用 Animator类提供了创建动画的基本结构,但是一般使用的是它的子类: ValueAnimator.O ...

随机推荐

  1. NGUI 按钮音效问题

    昨天给NGUI的按钮添加音效时,刚开始是自己新建空对象绑定声音的,后来发现NGUI按钮携带button sound组件,直接将音效拖入即可,不用写一行代码,非常简单.但是后来发现添加相同的音效有的按钮 ...

  2. 调用 sphinx-build生成HTML文件

    安装 Sphinx $ easy_install sphinx Searching for sphinx Reading http://pypi.python.org/simple/sphinx/ R ...

  3. AutoCAD 2014简体中文版官方正式版x86 x64下载,带注册机,永久免费使用

    注册机使用说明:会有部分杀毒软件报病毒,请无视.操作步骤:1.安装Autodesk AutoCAD 20142.使用以下序列号666-69696969安装.3.产品密码为001F14.安装完成后,启动 ...

  4. 最详细最实用-Orcad10.5安装说明

    接受协议 选择安装 忽略警告 全部为空 忽略警告 直接下一步 选择YES 为空,直接下一步 全选,根据需要修改该路径,下一步 根据需要修改该路径 下一步 直接next 忽略提示 直接下一步 直接下一步 ...

  5. The Priest Mathematician

    http://acm.hust.edu.cn/vjudge/contest/view.action?cid=31329#problem/F f[0] = 1 , f[ i ] = f[ i - 1 ] ...

  6. elk 架构

  7. android linearlayout imageview置顶摆放

    在练习android时,想在Linearlayout内放一图片,使其图片置顶,预期效果是这样的: 但xml代码imageview写成这样后, <ImageView android:layout_ ...

  8. hdu 1236 1.3.2排名

    排名 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission ...

  9. 国际名品SYSTEM入驻北京金融街购物中心__购物败家_YOKA时尚网

    国际名品SYSTEM入驻北京金融街购物中心__购物败家_YOKA时尚网 国际名品SYSTEM入驻北京金融街购物中心

  10. VC++中的头文件包含问题

    在一些大的工程中,可能会包含几十个基础类,免不了之间会互相引用(不满足继承关系,而是组合关系).也就是需要互相声明.好了,这时候会带来一些混乱.如果处理得不好,会搞得一团糟,根据我的经验,简单谈谈自已 ...