当前位置:首页 >> 信息与通信 >>

Android游戏开发20回合 有米分享


有米手机应用广告(优蜜信息科技)

www.youmi.net

Android 游戏开发 20 回合

试 阅:
……下面我们就以实现手势识别的 onFling 动作,在 CwjView 中我们从 View 类继承,当然大家 可以从 TextView 等更高层的界面中实现触控。 class CwjView extends View { private GestureDetector mGD; public CwjView(Context context, AttributeSet attrs) { super(context, attrs); mGD = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { int dx = (int) (e2.getX() - e1.getX()); //计算滑动的距离 if (Math.abs(dx) > MAJOR_MOVE && Math.abs(velocityX) > Math.abs(velocityY)) { //降噪处理,必须有较大的动作才识别 if (velocityX > 0) { //向右边 } else { //向左边 } return true; 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
1/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

} else { return false; //当然可以处理 velocityY 处理向上和向下的动作……

正 文: 目 录:
Android 游戏开发一 长按 Button 原理 Android 游戏开发二 View 和 SurfaceView Android 游戏开发三 View 类详解 Android 游戏开发四 Canvas 和 Paint 实例 Android 游戏开发五 Path 和 Typeface Android 游戏开发六 自定义 View Android 游戏开发七 自定义 SurfaceView Android 游戏开发八 SurfaceView 类实例 Android 游戏开发九 VideoView 类剖析 Android 游戏开发十 位图旋转 Android 游戏开发 11 View 中手势识别 Android 游戏开发 12 Sensor 重力感应 Android 游戏开发 13 Sensor 感应示例 Android 游戏开发 14 游戏开发实战一 Android 游戏开发 15 按键中断处理 Android 游戏开发 16 异步音乐播放 Android 游戏开发 17 图像渐变特效 Android 游戏开发 18 SoundPool 类 Android 游戏开发 19 分辨率大全 Android 游戏开发 20 双按事件捕获

Android 游戏开发一 长按 Button 原理
今天 Android123 开始新的 Android 游戏开发系列,主要从控制方法(按键、轨迹球、触屏、重力 感应、摄像头、话筒气流、光线亮度)、图形 View(高效绘图技术如双缓冲)、音效(游戏音乐)以 及最后的 OpenGL ES(Java 层)和 NDK 的 OpenGL 和 J2ME 游戏移植到 Android 方法,当然还 有一些游戏实现惯用方法,比如地图编辑器,在 Android OpenGL 如何使用 MD2 文件,个部分 讲述下 Android 游戏开发的过程最终实现一个比较完整的游戏引擎。相信大家都清楚 Android Market 下载量比较好的都是游戏, 未来手机网游的发展相信 Android 使用的 Java 在这方面有比 iPhone 有更低的入门门槛。 对于很多游戏使用屏幕控制一般需要考虑长按事件,比如在动作类的游戏中需要长按发射武 器,结合 Android Button 模型,我们实现一个带图片的 Button 的长按,为了更清晰的显示原理, Android 开发网这里使用 ImageButton 作为基类

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

2/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

public class RepeatingImageButton extends ImageButton { private long mStartTime; //记录长按开始 private int mRepeatCount; //重复次数计数 private RepeatListener mListener; private long mInterval = 500; //Timer 触发间隔,即每 0.5 秒算一次按下 public RepeatingImageButton(Context context) { this(context, null); } public RepeatingImageButton(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.imageButtonStyle); } public RepeatingImageButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setFocusable(true); //允许获得焦点 setLongClickable(true); //启用长按事件 } public void setRepeatListener(RepeatListener l, long interval) { //实现重复按下事件 listener mListener = l; mInterval = interval; } @Override public boolean performLongClick() { mStartTime = SystemClock.elapsedRealtime(); mRepeatCount = 0; post(mRepeater); return true; }

@Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { // removeCallbacks(mRepeater); if (mStartTime != 0) { doRepeat(true); mStartTime = 0; } 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
3/64

本方法原理同 onKeyUp 的一样,

这里处理屏幕事件,下面的 onKeyUp 处理 Android 手机上的物理按键事件

有米手机应用广告(优蜜信息科技)

www.youmi.net

} return super.onTouchEvent(event); } //处理导航键事件的中键或轨迹球按下事件 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: super.onKeyDown(keyCode, event); return true; } return super.onKeyDown(keyCode, event); } //当按键弹起通知长按结束 @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: removeCallbacks(mRepeater); //取消重复 listener 捕获 if (mStartTime != 0) { doRepeat(true); //如果长按事件累计时间不为 0 则说明长按了 mStartTime = 0; //重置长按计时器 } } return super.onKeyUp(keyCode, event); } private Runnable mRepeater = new Runnable() { //在线程中判断重复 public void run() { doRepeat(false); if (isPressed()) { postDelayed(this, mInterval); //计算长按后延迟下一次累加 } } };

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

4/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

private void doRepeat(boolean last) { long now = SystemClock.elapsedRealtime(); if (mListener != null) { mListener.onRepeat(this, now - mStartTime, last ? -1 : mRepeatCount++); } } 下面是重复 Button Listener 接口的定义,调用时在 Button 中先使用 setRepeatListener()方法 实现 RepeatListener 接口 public interface RepeatListener { void onRepeat(View v, long duration, int repeatcount); //参数一为用户传入的 Button 对象,参数二为延迟的毫秒数,第三位重复次数回调。 } }

Android 游戏开发二 View 和 SurfaceView
在 Android 游戏当中充当主要的除了控制类外就是显示类, J2ME 中我们用 Display 和 Canvas 在 来实现这些,而 Google Android 中涉及到显示的为 view 类,Android 游戏开发中比较重要和复 杂的就是显示和游戏逻辑的处理。这里我们说下 android.view.View 和 android.view.SurfaceView。SurfaceView 是从 View 基类中派生出来的显示类,直接子类有 GLSurfaceView 和 VideoView,可以看出 GL 和视频播放以及 Camera 摄像头一般均使用 SurfaceView,到底有哪些优势呢? SurfaceView 可以控制表面的格式,比如大小,显示在屏幕 中的位置,最关键是的提供了 SurfaceHolder 类,使用 getHolder 方法获取,相关的有 Canvas lockCanvas() Canvas lockCanvas(Rect dirty) 、void removeCallback(SurfaceHolder.Callback callback)、 void unlockCanvasAndPost(Canvas canvas) 控制图形以及绘制,而在 SurfaceHolder.Callback 接口回调中可以通过下面三个抽象类可以自己定义具体的实现, 比如第 一个更改格式和显示画面。 abstract void surfaceChanged(SurfaceHolder holder, int format, int width, int height) abstract void surfaceCreated(SurfaceHolder holder) abstract void surfaceDestroyed(SurfaceHolder holder) 对于 Surface 相关的,Android 底层还提供了 GPU 加速功能,所以一般实时性很强的应用中 主要使用 SurfaceView 而不是直接从 View 构建,同时 Android123 未来后面说到的 OpenGL 中 的 GLSurfaceView 也是从该类实现。

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

5/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

Android 游戏开发三 View 类详解
在 Android 游戏开发二中我们讲到了 View 和 SurfaceView 的区别, 今天 Android123 从 View 类 开始着重的介绍 Android 图形显示基类的相关方法和注意点。 自定义 View 的常用方法:

onFinishInflate() 当 View 中所有的子控件均被映射成 xml 后触发 onMeasure(int, int) 确定所有子元素的大小 onLayout(boolean, int, int, int, int) 当 View 分配所有的子元素的大小和位置时触发 onSizeChanged(int, int, int, int) 当 view 的大小发生变化时触发 onDraw(Canvas) view 渲染内容的细节 onKeyDown(int, KeyEvent) 有按键按下后触发 onKeyUp(int, KeyEvent) 有按键按下后弹起时触发 onTrackballEvent(MotionEvent) 轨迹球事件 onTouchEvent(MotionEvent) 触屏事件 onFocusChanged(boolean, int, Rect) 当 View 获取或失去焦点时触发 onWindowFocusChanged(boolean) 当窗口包含的 view 获取或失去焦点时触发 onAttachedToWindow() 当 view 被附着到一个窗口时触发 onDetachedFromWindow() 当 view 离开附着的窗口时触发,Android123 提示该方法 和 onAttachedToWindow() 是相反的。 onWindowVisibilityChanged(int) 当窗口中包含的可见的 view 发生变化时触发 以上是 View 实现的一些基本接口的回调方法,一般我们需要处理画布的显示时,重写 onDraw(Canvas)用的的是最多的: @Override protected void onDraw(Canvas canvas) { //这里我们直接使用 canvas 对象处理当前的画布,比如说使用 Paint 来选择要填充的颜色 Paint paintBackground = new Paint(); paintBackground.setColor(getResources().getColor(R.color.xxx)); //从 Res 中找到名为 xxx 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
6/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

的 color 颜色定义 canvas.drawRect(0, 0, getWidth(), getHeight(), paintBackground); //设置当前画布的背景颜色 为 paintBackground 中定义的颜色,以 0,0 作为为起点,以当前画布的宽度和高度为重点即整块 画布来填充。 具体的请查看 Android123 未来讲到的 Canvas 和 Paint,在 Canvas 中我们可以实现画路径, 图形,区域,线。而 Paint 作为绘画方式的对象可以设置颜色,大小,甚至字体的类型等等。 } 当然还有就是处理窗口还原状态问题(一般用于横竖屏切换),除了在 Activity 中可以调用外,开 发游戏时我们尽量在 View 中使用类似 @Override protected Parcelable onSaveInstanceState() { Parcelable p = super.onSaveInstanceState(); Bundle bundle = new Bundle(); bundle.putInt("x", pX); bundle.putInt("y", pY); bundle.putParcelable("android123_state", p); return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { Bundle bundle = (Bundle) state; dosomething(bundle.getInt("x"), bundle.getInt("y")); //获取刚才存储的 x 和 y 信息 super.onRestoreInstanceState(bundle.getParcelable("android123_state")); return; } 在 View 中如果需要强制调用绘制方法 onDraw,可以使用 invalidate()方法,它有很多重载版 本,同时在线程中的 postInvailidate()方法将在 Android 游戏开发六中的 自定义 View 完整篇讲 到。

Android 游戏开发四 Canvas 和 Paint 实例
昨天我们在 Android 游戏开发三 View 详解中提到了 onDraw 方法, 有关详细的实现我们今天主 要说下 Android 的 Canvas 和 Paint 对象的使用实例。 Canvas 类主要实现了屏幕的绘制过程,其中包含了很多实用的方法,比如绘制一条路径、区 域、贴图、画点、画线、渲染文本,下面是 Canvas 类常用的方法,当然 Android 开发网提示大 家很多方法有不同的重载版本,参数更灵活。 void drawRect(RectF rect, Paint paint) //绘制区域,参数一为 RectF 一个区域

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

7/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

void drawPath(Path path, Paint paint) //绘制一个路径,参数一为 Path 路径对象 void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) //贴图,参数一就是我们

常规的 Bitmap 对象,参数二是源区域(Android123 提示这里是 bitmap),参数三是目标区域(应 该在 canvas 的位置和大小),参数四是 Paint 画刷对象,因为用到了缩放和拉伸的可能,当原始 Rect 不等于目标 Rect 时性能将会有大幅损失。 void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) //画线,参数一 起始点的 x 轴位置,参数二起始点的 y 轴位置,参数三终点的 x 轴水平位置,参数四 y 轴垂直位 置,最后一个参数为 Paint 画刷对象。 void drawPoint(float x, float y, Paint paint) //画点,参数一水平 x 轴,参数二垂直 y 轴,第三 个参数为 Paint 对象。 void drawText(String text, float x, float y, Paint paint) //渲染文本,Canvas 类除了上面的还可 以描绘文字,参数一是 String 类型的文本,参数二 x 轴,参数三 y 轴,参数四是 Paint 对象。 void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) //在路 径上绘制文本,相对于上面第二个参数是 Path 路径对象 从上面来看我们可以看出 Canvas 绘制类比较简单同时很灵活, 实现一般的方法通常没有问题, 同时可以叠加的处理设计出一些效果,不过细心的网友可能发现最后一个参数均为 Paint 对象。 如果我们把 Canvas 当做绘画师来看,那么 Paint 就是我们绘画的工具,比如画笔、画刷、颜料 等等。 Paint 类常用方法: void setARGB(int a, int r, int g, int b) 设置 Paint 对象颜色,参数一为 alpha 透明通道 void setAlpha(int a) 设置 alpha 不透明度,范围为 0~255 void setAntiAlias(boolean aa) //是否抗锯齿 void setColor(int color) //设置颜色,这里 Android 内部定义的有 Color 类包含了一些常见颜色 定义 . void setFakeBoldText(boolean fakeBoldText) //设置伪粗体文本 void setLinearText(boolean linearText) //设置线性文本 PathEffect setPathEffect(PathEffect effect) //设置路径效果 Rasterizer setRasterizer(Rasterizer rasterizer) //设置光栅化 Shader setShader(Shader shader) //设置阴影 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
8/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

void setTextAlign(Paint.Align align) //设置文本对齐 void setTextScaleX(float scaleX) //设置文本缩放倍数,1.0f 为原始 void setTextSize(float textSize) //设置字体大小 Typeface setTypeface(Typeface typeface) //设置字体,Typeface 包含了字体的类型,粗细, 还有倾斜、颜色等。 void setUnderlineText(boolean underlineText) //设置下划线 最终 Canvas 和 Paint 在 onDraw 中直接使用 @Override protected void onDraw(Canvas canvas) { Paint paintRed=new Paint(); paintRed.setColor(Color.Red); canvas.drawPoint(11,3,paintRed); //在坐标 11,3 上画一个红点 } 下一次 Android123 将会具体讲到强大的 Path 路径,和字体 Typeface 相关的使用。

Android 游戏开发(五)Path 和 Typeface
今天我们继续处理上次 Android 游戏开发(四)Canvas 和 Paint 实例 中提到的 Path 路径和 Typeface 字体两个类。 对于 Android 游戏开发或者说 2D 绘图中来讲 Path 路径可以用强大这个 词来形容。在 Photoshop 中我们可能还记得使用钢笔工具绘制路径的方法。Path 路径类在位于 android.graphics.Path 中,Path 的构造方法比较简单,如下 Path cwj=new Path(); //构造方法 复制代码 下面我们画一个封闭的原型路径,我们使用 Path 类的 addCircle 方法 cwj.addCircle(10,10,50,Direction.CW); //参数一为 x 轴水平位置,参数二为 y 轴垂直位置,第三 个参数为圆形的半径,最后是绘制的方向,CW 为顺时针方向,而 CCW 是逆时针方向 复制代码 结合 Android 上次提到的 Canvas 类中的绘制方法 drawPath 和 drawTextOnPath,我们继续可 以在 onDraw 中加入。 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
9/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

canvas.drawPath(cwj,paintPath); //Android123 提示大家这里 paintPath 为路径的画刷颜色,可 以见下文完整的源代码。 canvas.drawTextOnPath("Android123 - CWJ",cwj,0,15,paintText); //将文字绘制到路径中去, 复制代码 有关 drawTextOnPath 的参数如下: 方法原型 public void drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint) 复制代码 参数列表 text 为需要在路径上绘制的文字内容。 path 将文字绘制到哪个路径。 hOffset 距离路径开始的距离 vOffset 离路径的上下高度,这里 Android 开发网提示大家,该参数类型为 float 浮点型,除了 精度为 8 位小数外,可以为正或负,当为正时文字在路径的圈里面,为负时在路径的圈外面。 paint 最后仍然是一个 Paint 对象用于制定 Text 本文的颜色、字体、大小等属性。 下面是我们的 onDraw 方法中如何绘制路径的演示代码为: @Override protected void onDraw(Canvas canvas) {

Paint paintPath=new Paint(); Paint paintText=new Paint(); paintPath.setColor(Color.Red); //路径的画刷为红色 paintText.setColor(Color.Blue); //路径上的文字为蓝色 Path pathCWJ=new Path(); pathCWJ.addCircle(10,10,50,Direction.CW); // 半径为 50px,绘制的方向 CW 为顺时针 canvas.drawPath(pathCWJ,paintPath);

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

10/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

canvas.drawTextOnPath("Android123 - CWJ",pathCWJ,0,15,paintText); //在路径上绘制文字 } 复制代码 有关路径类常用的方法如下: void addArc(RectF oval, float startAngle, float sweepAngle) //为路径添加一个多边形 void addCircle(float x, float y, float radius, Path.Direction dir) //给 path 添加圆圈 void addOval(RectF oval, Path.Direction dir) //添加椭圆形 void addRect(RectF rect, Path.Direction dir) //添加一个区域 void addRoundRect(RectF rect, float[] radii, Path.Direction dir) //添加一个圆角区域 boolean isEmpty() //判断路径是否为空 void transform(Matrix matrix) //应用矩阵变换 void transform(Matrix matrix, Path dst) //应用矩阵变换并将结果放到新的路径中,即第二个参 数。 复制代码 有关路径的高级效果大家可以使用 PathEffect 类,有关路径的更多实例 Android123 将在今后的 游戏开发实战中讲解道。 Typeface 字体类 平时我们在 TextView 中需要设置显示的字体可以通过 TextView 中的 setTypeface 方法来指定 一个 Typeface 对象,因为 Android 的字体类比较简单,我们列出所有成员方法 static Typeface create(Typeface family, int style) //静态方法,参数一为字体类型这里是 Typeface 的静态定义,如宋体,参数二风格,如粗体,斜体 static Typeface create(String familyName, int style) //静态方法,参数一为字体名的字符串,参 数二为风格同上,这里我们推荐使用上面的方法。 static Typeface createFromAsset(AssetManager mgr, String path) //静态方法,参数一为 AssetManager 对象,主要用于从 APK 的 assets 文件夹中取出字体,参数二为相对于 Android 工程下的 assets 文件夹中的外挂字体文件的路径。

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

11/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

static Typeface createFromFile(File path) //静态方法,从文件系统构造一个字体,这里参数可 以是 sdcard 中的某个字体文件 static Typeface createFromFile(String path) //静态方法,从指定路径中构造字体 static Typeface defaultFromStyle(int style) //静态方法,返回默认的字体风格 int getStyle() //获取当前字体风格 final boolean isBold() //判断当前是否为粗体 final boolean isItalic() //判断当前风格是否为斜体 复制代码 本类的常量静态定义,首先为字体类型名称 Typeface DEFAULT Typeface DEFAULT_BOLD Typeface MONOSPACE Typeface SANS_SERIF Typeface SERIF 字体风格名称 int BOLD int BOLD_ITALIC int ITALIC int NORMAL 明天我们将在 Android 游戏开发六 自定义 View 一文中具体讲解 onDraw 以及什么时候会触发 绘制方法,来实现我们自定义或子类化控件。

Android 游戏开发六 自定义 View
有关 Android 的自定义 View 的框架今天我们一起讨论下,对于常规的游戏,我们在 View 中需 要处理以下几种问题: 1.控制事件 2.刷新 View 3. 绘制 View 1. 对于控制事件今天我们只处理按键事件 onKeyDown,以后的文章中将会讲到屏幕触控的具 体处理 onTouchEvent 以及 Sensor 重力感应等方法。 2. 刷新 view 的方法这里主要有 invalidate(int l, int t, int r, int b) 刷新局部,四个参数分别为左、 上、右、下。整个 view 刷新 invalidate(),刷新一个矩形区域 invalidate(Rect dirty) ,刷新一个 特性 Drawable, invalidateDrawable(Drawable drawable) ,执行 invalidate 类的方法将会设置 view 为无效,最终导致 onDraw 方法被重新调用。由于今天的 view 比较简单,Android123 提 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
12/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

示大家如果在线程中刷新,除了使用 handler 方式外,可以在 Thread 中直接使用 postInvalidate 方法来实现。 3. 绘制 View 主要是 onDraw()中通过形参 canvas 来处理,相关的绘制主要有 drawRect、 drawLine、drawPath 等等。view 方法内部还重写了很多接口,其回调方法可以帮助我们判断出 view 的位置和大小,比如 onMeasure(int, int) Called to determine the size requirements for this view and all of its children. 、onLayout(boolean, int, int, int, int) Called when this view should assign a size and position to all of its children 和 onSizeChanged(int, int, int, int) Called when the size of this view has changed. 具体的作用,大家可以用 Logcat 获取当 view 变化时每个形 参的变动。 下面 cwjView 是我们为今后游戏设计的一个简单自定义 View 框架,我们可以看到在 Android 平台自定义 view 还是很简单的,同时 Java 支持多继承可以帮助我们不断的完善复杂的问题。 public class cwjView extends View { public cwjView(Context context) { super(context); setFocusable(true); //允许获得焦点 setFocusableInTouchMode(true); //获取焦点时允许触控 } @Override protected Parcelable onSaveInstanceState() { //处理窗口保存事件 Parcelable pSaved = super.onSaveInstanceState(); Bundle bundle = new Bundle(); //dosomething return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { //处理窗口还原事件 Bundle bundle = (Bundle) state; //dosomething super.onRestoreInstanceState(bundle.getParcelable("cwj")); return; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) //处理窗口大小变化事件 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
13/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

{ super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //如果不让父类处理记住调用 setMeasuredDimension } @Override protected void onLayout (boolean changed, int left, int top, int right, int bottom) { super.onLayout (changed,left,top, ight,bottom) ; } @Override protected void onDraw(Canvas canvas) { Paint bg = new Paint(); bg.setColor(Color.Red); canvas.drawRect(0, 0, getWidth()/2, getHeight()/2, bg); //将 view 的左上角四分之一填充为 红色 } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); //让父类处理屏幕触控事件 } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { //处理按键事件,响应的轨迹球 事件为 public boolean onTrackballEvent (MotionEvent event) switch (keyCode) { case KeyEvent.KEYCODE_DPAD_UP: break; case KeyEvent.KEYCODE_DPAD_DOWN: break; 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
14/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

case KeyEvent.KEYCODE_DPAD_LEFT: break; case KeyEvent.KEYCODE_DPAD_RIGHT: break; case KeyEvent.KEYCODE_DPAD_CENTER: //处理中键按下 break; default: return super.onKeyDown(keyCode, event); } return true; } } 上面我们可以看到 onMeasure 使用的是父类的处理方法, 如果我们需要解决自定义 View 的大 小,可以尝试下面的方法

@Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { height = View.MeasureSpec.getSize(heightMeasureSpec); width = View.MeasureSpec.getSize(widthMeasureSpec); setMeasuredDimension(width,height); 行 //dosomething } //这里面是原始的大小,需要重新计算可以修改本

Android 游戏开发七 自定义 SurfaceView
今天我们说下未来的 Android 游戏引擎模板架构问题,对于游戏我们还是选择 SurfaceView,相 关的原因 Android123 已经在 Android 游戏开发二 View 和 SurfaceView 中说的很清楚了,这里 我们直接继承 SurfaceView,实现 SurfaceHolder.Callback 接口,处理 surfaceCreated、 surfaceChanged 以及 surfaceDestroyed 方法,这里我们并没有把按键控制传入,最终游戏的控 制方面仍然由 View 内部类处理比较好,有关 SurfaceView 的具体我们可以参见 Android 开源项 目的 Camera 中有关画面捕捉以及 VideoView 的控件实现大家可以清晰了解最终的用意。 public class cwjView extends SurfaceView implements SurfaceHolder.Callback {

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

15/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

public cwjView(Context context, AttributeSet attrs) { super(context, attrs); SurfaceHolder holder=getHolder(); holder.addCallback(this); setFocusable(true); } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

} public void surfaceCreated(SurfaceHolder holder) {

} public void surfaceDestroyed(SurfaceHolder holder) {

} @Override public void onWindowFocusChanged(boolean hasWindowFocus) { } }

Android 游戏开发八 SurfaceView 类实例
有关 SurfaceView 我们将通过三个系统自带的例子来深入掌握 Android 绘图必会的 SurfaceView,今天我们以 SDK 中的 Sample 游戏 lunarlander 中的 LunarView 具体实现, Android123 建议大家导入该游戏工程到你的 Eclipse 然后自己编译先玩一下这个游戏,然后再 看代码比较好理解。 class LunarView extends SurfaceView implements SurfaceHolder.Callback { class LunarThread extends Thread { /* * Difficulty setting constants */ public static final int DIFFICULTY_EASY = 0; 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
16/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

public static final int DIFFICULTY_HARD = 1; public static final int DIFFICULTY_MEDIUM = 2; /* * Physics constants */ public static final int PHYS_DOWN_ACCEL_SEC = 35; public static final int PHYS_FIRE_ACCEL_SEC = 80; public static final int PHYS_FUEL_INIT = 60; public static final int PHYS_FUEL_MAX = 100; public static final int PHYS_FUEL_SEC = 10; public static final int PHYS_SLEW_SEC = 120; // degrees/second rotate public static final int PHYS_SPEED_HYPERSPACE = 180; public static final int PHYS_SPEED_INIT = 30; public static final int PHYS_SPEED_MAX = 120; /* * State-tracking constants */ public static final int STATE_LOSE = 1; public static final int STATE_PAUSE = 2; public static final int STATE_READY = 3; public static final int STATE_RUNNING = 4; public static final int STATE_WIN = 5; /* * Goal condition constants */ public static final int TARGET_ANGLE = 18; // > this angle means crash public static final int TARGET_BOTTOM_PADDING = 17; // px below gear public static final int TARGET_PAD_HEIGHT = 8; // how high above ground public static final int TARGET_SPEED = 28; // > this speed means crash public static final double TARGET_WIDTH = 1.6; // width of target /* * UI constants (i.e. the speed & fuel bars) */ public static final int UI_BAR = 100; // width of the bar(s) public static final int UI_BAR_HEIGHT = 10; // height of the bar(s) private static final String KEY_DIFFICULTY = "mDifficulty"; private static final String KEY_DX = "mDX"; private static final String KEY_DY = "mDY"; private static final String KEY_FUEL = "mFuel"; private static final String KEY_GOAL_ANGLE = "mGoalAngle"; private static final String KEY_GOAL_SPEED = "mGoalSpeed"; private static final String KEY_GOAL_WIDTH = "mGoalWidth";

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

17/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

private static final String KEY_GOAL_X = "mGoalX"; private static final String KEY_HEADING = "mHeading"; private static final String KEY_LANDER_HEIGHT = "mLanderHeight"; private static final String KEY_LANDER_WIDTH = "mLanderWidth"; private static final String KEY_WINS = "mWinsInARow"; private static final String KEY_X = "mX"; private static final String KEY_Y = "mY"; /* * Member (state) fields */ /** The drawable to use as the background of the animation canvas */ private Bitmap mBackgroundImage; /** * Current height of the surface/canvas. * * @see #setSurfaceSize */ private int mCanvasHeight = 1; /** * Current width of the surface/canvas. * * @see #setSurfaceSize */ private int mCanvasWidth = 1; /** What to draw for the Lander when it has crashed */ private Drawable mCrashedImage; /** * Current difficulty -- amount of fuel, allowed angle, etc. Default is * MEDIUM. */ private int mDifficulty; /** Velocity dx. */ private double mDX; /** Velocity dy. */ private double mDY;

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

18/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

/** Is the engine burning? */ private boolean mEngineFiring; /** What to draw for the Lander when the engine is firing */ private Drawable mFiringImage; /** Fuel remaining */ private double mFuel; /** Allowed angle. */ private int mGoalAngle; /** Allowed speed. */ private int mGoalSpeed; /** Width of the landing pad. */ private int mGoalWidth; /** X of the landing pad. */ private int mGoalX; /** Message handler used by thread to interact with TextView */ private Handler mHandler; /** * Lander heading in degrees, with 0 up, 90 right. Kept in the range * 0..360. */ private double mHeading; /** Pixel height of lander image. */ private int mLanderHeight; /** What to draw for the Lander in its normal state */ private Drawable mLanderImage; /** Pixel width of lander image. */ private int mLanderWidth; /** Used to figure out elapsed time between frames */ private long mLastTime; /** Paint to draw the lines on screen. */ private Paint mLinePaint;

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

19/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

/** "Bad" speed-too-high variant of the line color. */ private Paint mLinePaintBad; /** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */ private int mMode; /** Currently rotating, -1 left, 0 none, 1 right. */ private int mRotating; /** Indicate whether the surface has been created & is ready to draw */ private boolean mRun = false; /** Scratch rect object. */ private RectF mScratchRect; /** Handle to the surface manager object we interact with */ private SurfaceHolder mSurfaceHolder; /** Number of wins in a row. */ private int mWinsInARow; /** X of lander center. */ private double mX; /** Y of lander center. */ private double mY; public LunarThread(SurfaceHolder surfaceHolder, Context context, Handler handler) { // get handles to some important objects mSurfaceHolder = surfaceHolder; mHandler = handler; mContext = context; Resources res = context.getResources(); // cache handles to our key sprites & other drawables mLanderImage = context.getResources().getDrawable( R.drawable.lander_plain); mFiringImage = context.getResources().getDrawable( R.drawable.lander_firing); mCrashedImage = context.getResources().getDrawable( R.drawable.lander_crashed); // load background image as a Bitmap instead of a Drawable b/c // we don't need to transform it and it's faster to draw this way 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
20/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

mBackgroundImage = BitmapFactory.decodeResource(res, R.drawable.earthrise); // Use the regular lander image as the model size for all sprites mLanderWidth = mLanderImage.getIntrinsicWidth(); mLanderHeight = mLanderImage.getIntrinsicHeight(); // Initialize paints for speedometer mLinePaint = new Paint(); mLinePaint.setAntiAlias(true); mLinePaint.setARGB(255, 0, 255, 0); mLinePaintBad = new Paint(); mLinePaintBad.setAntiAlias(true); mLinePaintBad.setARGB(255, 120, 180, 0); mScratchRect = new RectF(0, 0, 0, 0); mWinsInARow = 0; mDifficulty = DIFFICULTY_MEDIUM; // initial show-up of lander (not yet playing) mX = mLanderWidth; mY = mLanderHeight * 2; mFuel = PHYS_FUEL_INIT; mDX = 0; mDY = 0; mHeading = 0; mEngineFiring = true; } /** * Starts the game, setting parameters for the current difficulty. */ public void doStart() { synchronized (mSurfaceHolder) { // First set the game for Medium difficulty mFuel = PHYS_FUEL_INIT; mEngineFiring = false; mGoalWidth = (int) (mLanderWidth * TARGET_WIDTH); mGoalSpeed = TARGET_SPEED; mGoalAngle = TARGET_ANGLE; int speedInit = PHYS_SPEED_INIT;

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

21/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

// Adjust difficulty params for EASY/HARD if (mDifficulty == DIFFICULTY_EASY) { mFuel = mFuel * 3 / 2; mGoalWidth = mGoalWidth * 4 / 3; mGoalSpeed = mGoalSpeed * 3 / 2; mGoalAngle = mGoalAngle * 4 / 3; speedInit = speedInit * 3 / 4; } else if (mDifficulty == DIFFICULTY_HARD) { mFuel = mFuel * 7 / 8; mGoalWidth = mGoalWidth * 3 / 4; mGoalSpeed = mGoalSpeed * 7 / 8; speedInit = speedInit * 4 / 3; } // pick a convenient initial location for the lander sprite mX = mCanvasWidth / 2; mY = mCanvasHeight - mLanderHeight / 2; // start with a little random motion mDY = Math.random() * -speedInit; mDX = Math.random() * 2 * speedInit - speedInit; mHeading = 0; // Figure initial spot for landing, not too near center while (true) { mGoalX = (int) (Math.random() * (mCanvasWidth - mGoalWidth)); if (Math.abs(mGoalX - (mX - mLanderWidth / 2)) > mCanvasHeight / 6) break; } mLastTime = System.currentTimeMillis() + 100; setState(STATE_RUNNING); } } /** * Pauses the physics update & animation. */ public void pause() { synchronized (mSurfaceHolder) { if (mMode == STATE_RUNNING) setState(STATE_PAUSE); } }

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

22/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

/** * Restores game state from the indicated Bundle. Typically called when * the Activity is being restored after having been previously * destroyed. * * @param savedState Bundle containing the game state */ public synchronized void restoreState(Bundle savedState) { synchronized (mSurfaceHolder) { setState(STATE_PAUSE); mRotating = 0; mEngineFiring = false; mDifficulty = savedState.getInt(KEY_DIFFICULTY); mX = savedState.getDouble(KEY_X); mY = savedState.getDouble(KEY_Y); mDX = savedState.getDouble(KEY_DX); mDY = savedState.getDouble(KEY_DY); mHeading = savedState.getDouble(KEY_HEADING); mLanderWidth = savedState.getInt(KEY_LANDER_WIDTH); mLanderHeight = savedState.getInt(KEY_LANDER_HEIGHT); mGoalX = savedState.getInt(KEY_GOAL_X); mGoalSpeed = savedState.getInt(KEY_GOAL_SPEED); mGoalAngle = savedState.getInt(KEY_GOAL_ANGLE); mGoalWidth = savedState.getInt(KEY_GOAL_WIDTH); mWinsInARow = savedState.getInt(KEY_WINS); mFuel = savedState.getDouble(KEY_FUEL); } } @Override public void run() { while (mRun) { Canvas c = null; try { c = mSurfaceHolder.lockCanvas(null); synchronized (mSurfaceHolder) { if (mMode == STATE_RUNNING) updatePhysics(); doDraw(c); } } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
23/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

if (c != null) { mSurfaceHolder.unlockCanvasAndPost(c); } } } } /** * Dump game state to the provided Bundle. Typically called when the * Activity is being suspended. * * @return Bundle with this view's state */ public Bundle saveState(Bundle map) { synchronized (mSurfaceHolder) { if (map != null) { map.putInt(KEY_DIFFICULTY, Integer.valueOf(mDifficulty)); map.putDouble(KEY_X, Double.valueOf(mX)); map.putDouble(KEY_Y, Double.valueOf(mY)); map.putDouble(KEY_DX, Double.valueOf(mDX)); map.putDouble(KEY_DY, Double.valueOf(mDY)); map.putDouble(KEY_HEADING, Double.valueOf(mHeading)); map.putInt(KEY_LANDER_WIDTH, Integer.valueOf(mLanderWidth)); map.putInt(KEY_LANDER_HEIGHT, Integer .valueOf(mLanderHeight)); map.putInt(KEY_GOAL_X, Integer.valueOf(mGoalX)); map.putInt(KEY_GOAL_SPEED, Integer.valueOf(mGoalSpeed)); map.putInt(KEY_GOAL_ANGLE, Integer.valueOf(mGoalAngle)); map.putInt(KEY_GOAL_WIDTH, Integer.valueOf(mGoalWidth)); map.putInt(KEY_WINS, Integer.valueOf(mWinsInARow)); map.putDouble(KEY_FUEL, Double.valueOf(mFuel)); } } return map; } /** * Sets the current difficulty. * * @param difficulty */ public void setDifficulty(int difficulty) { synchronized (mSurfaceHolder) { mDifficulty = difficulty;

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

24/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

} } /** * Sets if the engine is currently firing. */ public void setFiring(boolean firing) { synchronized (mSurfaceHolder) { mEngineFiring = firing; } } /** * Used to signal the thread whether it should be running or not. * Passing true allows the thread to run; passing false will shut it * down if it's already running. Calling start() after this was most * recently called with false will result in an immediate shutdown. * * @param b true to run, false to shut down */ public void setRunning(boolean b) { mRun = b; } /** * Sets the game mode. That is, whether we are running, paused, in the * failure state, in the victory state, etc. * * @see #setState(int, CharSequence) * @param mode one of the STATE_* constants */ public void setState(int mode) { synchronized (mSurfaceHolder) { setState(mode, null); } } /** * Sets the game mode. That is, whether we are running, paused, in the * failure state, in the victory state, etc. * * @param mode one of the STATE_* constants * @param message string to add to screen or null */ public void setState(int mode, CharSequence message) { 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
25/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

/* * This method optionally can cause a text message to be displayed * to the user when the mode changes. Since the View that actually * renders that text is part of the main View hierarchy and not * owned by this thread, we can't touch the state of that View. * Instead we use a Message + Handler to relay commands to the main * thread, which updates the user-text View. */ synchronized (mSurfaceHolder) { mMode = mode; if (mMode == STATE_RUNNING) { Message msg = mHandler.obtainMessage(); Bundle b = new Bundle(); b.putString("text", ""); b.putInt("viz", View.INVISIBLE); msg.setData(b); mHandler.sendMessage(msg); } else { mRotating = 0; mEngineFiring = false; Resources res = mContext.getResources(); CharSequence str = ""; if (mMode == STATE_READY) str = res.getText(R.string.mode_ready); else if (mMode == STATE_PAUSE) str = res.getText(R.string.mode_pause); else if (mMode == STATE_LOSE) str = res.getText(R.string.mode_lose); else if (mMode == STATE_WIN) str = res.getString(R.string.mode_win_prefix) + mWinsInARow + " " + res.getString(R.string.mode_win_suffix); if (message != null) { str = message + "\n" + str; } if (mMode == STATE_LOSE) mWinsInARow = 0; Message msg = mHandler.obtainMessage(); Bundle b = new Bundle(); b.putString("text", str.toString()); b.putInt("viz", View.VISIBLE); msg.setData(b); 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
26/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

mHandler.sendMessage(msg); } } } /* Callback invoked when the surface dimensions change. */ public void setSurfaceSize(int width, int height) { // synchronized to make sure these all change atomically synchronized (mSurfaceHolder) { mCanvasWidth = width; mCanvasHeight = height; // don't forget to resize the background image mBackgroundImage = mBackgroundImage.createScaledBitmap( mBackgroundImage, width, height, true); } } /** * Resumes from a pause. */ public void unpause() { // Move the real time clock up to now synchronized (mSurfaceHolder) { mLastTime = System.currentTimeMillis() + 100; } setState(STATE_RUNNING); } /** * Handles a key-down event. * * @param keyCode the key that was pressed * @param msg the original event object * @return true */ boolean doKeyDown(int keyCode, KeyEvent msg) { synchronized (mSurfaceHolder) { boolean okStart = false; if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true; if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true; if (keyCode == KeyEvent.KEYCODE_S) okStart = true; boolean center = (keyCode == KeyEvent.KEYCODE_DPAD_UP);

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

27/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

if (okStart && (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) { // ready-to-start -> start doStart(); return true; } else if (mMode == STATE_PAUSE && okStart) { // paused -> running unpause(); return true; } else if (mMode == STATE_RUNNING) { // center/space -> fire if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_SPACE) { setFiring(true); return true; // left/q -> left } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_Q) { mRotating = -1; return true; // right/w -> right } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_W) { mRotating = 1; return true; // up -> pause } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { pause(); return true; } } return false; } } /** * Handles a key-up event. * * @param keyCode the key that was pressed * @param msg the original event object * @return true if the key was handled and consumed, or else false */

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

28/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

boolean doKeyUp(int keyCode, KeyEvent msg) { boolean handled = false; synchronized (mSurfaceHolder) { if (mMode == STATE_RUNNING) { if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_SPACE) { setFiring(false); handled = true; } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_Q || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_W) { mRotating = 0; handled = true; } } } return handled; } /** * Draws the ship, fuel/speed bars, and background to the provided * Canvas. */ private void doDraw(Canvas canvas) { // Draw the background image. Operations on the Canvas accumulate // so this is like clearing the screen. canvas.drawBitmap(mBackgroundImage, 0, 0, null); int yTop = mCanvasHeight - ((int) mY + mLanderHeight / 2); int xLeft = (int) mX - mLanderWidth / 2; // Draw the fuel gauge int fuelWidth = (int) (UI_BAR * mFuel / PHYS_FUEL_MAX); mScratchRect.set(4, 4, 4 + fuelWidth, 4 + UI_BAR_HEIGHT); canvas.drawRect(mScratchRect, mLinePaint); // Draw the speed gauge, with a two-tone effect double speed = Math.sqrt(mDX * mDX + mDY * mDY); int speedWidth = (int) (UI_BAR * speed / PHYS_SPEED_MAX); if (speed <= mGoalSpeed) { mScratchRect.set(4 + UI_BAR + 4, 4, 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
29/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT); canvas.drawRect(mScratchRect, mLinePaint); } else { // Draw the bad color in back, with the good color in front of // it mScratchRect.set(4 + UI_BAR + 4, 4, 4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT); canvas.drawRect(mScratchRect, mLinePaintBad); int goalWidth = (UI_BAR * mGoalSpeed / PHYS_SPEED_MAX); mScratchRect.set(4 + UI_BAR + 4, 4, 4 + UI_BAR + 4 + goalWidth, 4 + UI_BAR_HEIGHT); canvas.drawRect(mScratchRect, mLinePaint); } // Draw the landing pad canvas.drawLine(mGoalX, 1 + mCanvasHeight - TARGET_PAD_HEIGHT, mGoalX + mGoalWidth, 1 + mCanvasHeight - TARGET_PAD_HEIGHT, mLinePaint);

// Draw the ship with its current rotation canvas.save(); canvas.rotate((float) mHeading, (float) mX, mCanvasHeight - (float) mY); if (mMode == STATE_LOSE) { mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight); mCrashedImage.draw(canvas); } else if (mEngineFiring) { mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight); mFiringImage.draw(canvas); } else { mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight); mLanderImage.draw(canvas); } canvas.restore(); } /** * Figures the lander state (x, y, fuel, ...) based on the passage of * realtime. Does not invalidate(). Called at the start of draw(). * Detects the end-of-game and sets the UI to the next state. */ 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
30/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

private void updatePhysics() { long now = System.currentTimeMillis(); // Do nothing if mLastTime is in the future. // This allows the game-start to delay the start of the physics // by 100ms or whatever. if (mLastTime > now) return; double elapsed = (now - mLastTime) / 1000.0; // mRotating -- update heading if (mRotating != 0) { mHeading += mRotating * (PHYS_SLEW_SEC * elapsed); // Bring things back into the range 0..360 if (mHeading < 0) mHeading += 360; else if (mHeading >= 360) mHeading -= 360; } // Base accelerations -- 0 for x, gravity for y double ddx = 0.0; double ddy = -PHYS_DOWN_ACCEL_SEC * elapsed; if (mEngineFiring) { // taking 0 as up, 90 as to the right // cos(deg) is ddy component, sin(deg) is ddx component double elapsedFiring = elapsed; double fuelUsed = elapsedFiring * PHYS_FUEL_SEC; // tricky case where we run out of fuel partway through the // elapsed if (fuelUsed > mFuel) { elapsedFiring = mFuel / fuelUsed * elapsed; fuelUsed = mFuel; // Oddball case where we adjust the "control" from here mEngineFiring = false; } mFuel -= fuelUsed; // have this much acceleration from the engine double accel = PHYS_FIRE_ACCEL_SEC * elapsedFiring;

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

31/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

double radians = 2 * Math.PI * mHeading / 360; ddx = Math.sin(radians) * accel; ddy += Math.cos(radians) * accel; } double dxOld = mDX; double dyOld = mDY; // figure speeds for the end of the period mDX += ddx; mDY += ddy; // figure position based on average speed during the period mX += elapsed * (mDX + dxOld) / 2; mY += elapsed * (mDY + dyOld) / 2; mLastTime = now; // Evaluate if we have landed ... stop the game double yLowerBound = TARGET_PAD_HEIGHT + mLanderHeight / 2 - TARGET_BOTTOM_PADDING; if (mY <= yLowerBound) { mY = yLowerBound; int result = STATE_LOSE; CharSequence message = ""; Resources res = mContext.getResources(); double speed = Math.sqrt(mDX * mDX + mDY * mDY); boolean onGoal = (mGoalX <= mX - mLanderWidth / 2 && mX + mLanderWidth / 2 <= mGoalX + mGoalWidth); // "Hyperspace" win -- upside down, going fast, // puts you back at the top. if (onGoal && Math.abs(mHeading - 180) < mGoalAngle && speed > PHYS_SPEED_HYPERSPACE) { result = STATE_WIN; mWinsInARow++; doStart(); return; // Oddball case: this case does a return, all other cases // fall through to setMode() below. } else if (!onGoal) { message = res.getText(R.string.message_off_pad); } else if (!(mHeading <= mGoalAngle || mHeading >= 360 - mGoalAngle)) { 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
32/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

message = res.getText(R.string.message_bad_angle); } else if (speed > mGoalSpeed) { message = res.getText(R.string.message_too_fast); } else { result = STATE_WIN; mWinsInARow++; } setState(result, message); } } } /** Handle to the application context, used to e.g. fetch Drawables. */ private Context mContext; /** Pointer to the text view to display "Paused.." etc. */ private TextView mStatusText; /** The thread that actually draws the animation */ private LunarThread thread; public LunarView(Context context, AttributeSet attrs) { super(context, attrs); // register our interest in hearing about changes to our surface SurfaceHolder holder = getHolder(); holder.addCallback(this); // create thread only; it's started in surfaceCreated() thread = new LunarThread(holder, context, new Handler() { @Override public void handleMessage(Message m) { mStatusText.setVisibility(m.getData().getInt("viz")); mStatusText.setText(m.getData().getString("text")); } }); setFocusable(true); // make sure we get key events } /** * Fetches the animation thread corresponding to this LunarView. * * @return the animation thread 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
33/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

*/ public LunarThread getThread() { return thread; } /** * Standard override to get key-press events. */ @Override public boolean onKeyDown(int keyCode, KeyEvent msg) { return thread.doKeyDown(keyCode, msg); } /** * Standard override for key-up. We actually care about these, so we can * turn off the engine or stop rotating. */ @Override public boolean onKeyUp(int keyCode, KeyEvent msg) { return thread.doKeyUp(keyCode, msg); } /** * Standard window-focus override. Notice focus lost so we can pause on * focus lost. e.g. user switches to take a call. */ @Override public void onWindowFocusChanged(boolean hasWindowFocus) { if (!hasWindowFocus) thread.pause(); } /** * Installs a pointer to the text view used for messages. */ public void setTextView(TextView textView) { mStatusText = textView; } /* Callback invoked when the surface dimensions change. */ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { thread.setSurfaceSize(width, height); }

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

34/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

/* * Callback invoked when the Surface has been created and is ready to be * used. */ public void surfaceCreated(SurfaceHolder holder) { // start the thread here so that we don't busy-wait in run() // waiting for the surface to be created thread.setRunning(true); thread.start(); } /* * Callback invoked when the Surface has been destroyed and must no longer * be touched. WARNING: after this method returns, the Surface/Canvas must * never be touched again! */ public void surfaceDestroyed(SurfaceHolder holder) { // we have to tell thread to shut down & wait for it to finish, or else // it might touch the Surface after we return and explode boolean retry = true; thread.setRunning(false); while (retry) { try { thread.join(); retry = false; } catch (InterruptedException e) { } } } }

Android 游戏开发九 VideoView 类剖析
有关 SurfaceView 相关的内容今天 Android123 继续延用系统的示例类 VideoView 来让大家深入 了解 Android 平台的图形绘制基础类的实现原理。大家可能会发现 VideoView 类的控制方面无 法改变,我们可以通过重构 VideoView 类来实现更个性化的播放器。 public class VideoView extends SurfaceView implements MediaPlayerControl { private String TAG = "VideoView"; // settable by the client private Uri private int mUri; mDuration;

// all possible internal states private static final int STATE_ERROR 广州优蜜信息科技有限公司 Tel: 020-39341996 = -1; Fax: 020-39340892
35/64

有米手机应用广告(优蜜信息科技)

www.youmi.net
= 0; = 1; = 2; = 3; = 4;

private static final int STATE_IDLE private static final int STATE_PREPARING private static final int STATE_PREPARED private static final int STATE_PLAYING private static final int STATE_PAUSED

private static final int STATE_PLAYBACK_COMPLETED = 5; // mCurrentState is a VideoView object's current state. // mTargetState is the state that a method caller intends to reach. // For instance, regardless the VideoView object's current state, // calling pause() intends to bring the object to a target state // of STATE_PAUSED. private int mCurrentState = STATE_IDLE; private int mTargetState = STATE_IDLE; // All the stuff we need for playing and showing a video private SurfaceHolder mSurfaceHolder = null; private MediaPlayer mMediaPlayer = null; private int private int private int private int mVideoWidth; mVideoHeight; mSurfaceWidth; mSurfaceHeight;

private MediaController mMediaController; private OnCompletionListener mOnCompletionListener; private MediaPlayer.OnPreparedListener mOnPreparedListener; private int private int private boolean private boolean private boolean mCurrentBufferPercentage; mSeekWhenPrepared; // recording the seek position while preparing mCanPause; mCanSeekBack; mCanSeekForward; private OnErrorListener mOnErrorListener;

public VideoView(Context context) { super(context); initVideoView(); } public VideoView(Context context, AttributeSet attrs) { this(context, attrs, 0); initVideoView(); } public VideoView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle);

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

36/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

initVideoView(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //Log.i("@@@@", "onMeasure"); int width = getDefaultSize(mVideoWidth, widthMeasureSpec); int height = getDefaultSize(mVideoHeight, heightMeasureSpec); if (mVideoWidth > 0 && mVideoHeight > 0) { if ( mVideoWidth * height > width * mVideoHeight ) { //Log.i("@@@", "image too tall, correcting"); height = width * mVideoHeight / mVideoWidth; } else if ( mVideoWidth * height < width * mVideoHeight ) { //Log.i("@@@", "image too wide, correcting"); width = height * mVideoWidth / mVideoHeight; } else { //Log.i("@@@", "aspect ratio is correct: " + //width+"/"+height+"="+ //mVideoWidth+"/"+mVideoHeight); } } //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height); setMeasuredDimension(width, height); } public int resolveAdjustedSize(int desiredSize, int measureSpec) { int result = desiredSize; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: /* Parent says we can be as big as we want. Just don't be larger * than max size imposed on ourselves. */ result = desiredSize; break; case MeasureSpec.AT_MOST: /* Parent says we can be as big as we want, up to specSize. * Don't be larger than specSize, and don't be larger than * the max size imposed on ourselves. */ result = Math.min(desiredSize, specSize); break; 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
37/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

case MeasureSpec.EXACTLY: // No choice. Do what we are told. result = specSize; break; } return result; } private void initVideoView() { mVideoWidth = 0; mVideoHeight = 0; getHolder().addCallback(mSHCallback); getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); setFocusable(true); setFocusableInTouchMode(true); requestFocus(); mCurrentState = STATE_IDLE; mTargetState = STATE_IDLE; } public void setVideoPath(String path) { setVideoURI(Uri.parse(path)); } public void setVideoURI(Uri uri) { mUri = uri; mSeekWhenPrepared = 0; openVideo(); requestLayout(); invalidate(); } public void stopPlayback() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; mCurrentState = STATE_IDLE; mTargetState = STATE_IDLE; } } private void openVideo() { if (mUri == null || mSurfaceHolder == null) { 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
38/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

// not ready for playback just yet, will try again later return; } // Tell the music playback service to pause // TODO: these constants need to be published somewhere in the framework. Intent i = new Intent("com.android.music.musicservicecommand"); i.putExtra("command", "pause"); mContext.sendBroadcast(i); // we shouldn't clear the target state, because somebody might have // called start() previously release(false); try { mMediaPlayer = new MediaPlayer(); mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); mDuration = -1; mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); mCurrentBufferPercentage = 0; mMediaPlayer.setDataSource(mContext, mUri); mMediaPlayer.setDisplay(mSurfaceHolder); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setScreenOnWhilePlaying(true); mMediaPlayer.prepareAsync(); // we don't set the target state here either, but preserve the // target state that was there before. mCurrentState = STATE_PREPARING; attachMediaController(); } catch (IOException ex) { Log.w(TAG, "Unable to open content: " + mUri, ex); mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); return; } catch (IllegalArgumentException ex) { Log.w(TAG, "Unable to open content: " + mUri, ex); mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); return; } } 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
39/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

public void setMediaController(MediaController controller) { if (mMediaController != null) { mMediaController.hide(); } mMediaController = controller; attachMediaController(); } private void attachMediaController() { if (mMediaPlayer != null && mMediaController != null) { mMediaController.setMediaPlayer(this); View anchorView = this.getParent() instanceof View ? (View)this.getParent() : this; mMediaController.setAnchorView(anchorView); mMediaController.setEnabled(isInPlaybackState()); } } MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() { public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { mVideoWidth = mp.getVideoWidth(); mVideoHeight = mp.getVideoHeight(); if (mVideoWidth != 0 && mVideoHeight != 0) { getHolder().setFixedSize(mVideoWidth, mVideoHeight); } } }; MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { public void onPrepared(MediaPlayer mp) { mCurrentState = STATE_PREPARED; // Get the capabilities of the player for this stream Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL, MediaPlayer.BYPASS_METADATA_FILTER); if (data != null) { mCanPause = !data.has(Metadata.PAUSE_AVAILABLE) || data.getBoolean(Metadata.PAUSE_AVAILABLE); mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE) || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE); mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE) 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
40/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

|| data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE); } else { mCanPause = mCanSeekForward = mCanSeekForward = true; } if (mOnPreparedListener != null) { mOnPreparedListener.onPrepared(mMediaPlayer); } if (mMediaController != null) { mMediaController.setEnabled(true); } mVideoWidth = mp.getVideoWidth(); mVideoHeight = mp.getVideoHeight(); int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call if (seekToPosition != 0) { seekTo(seekToPosition); } if (mVideoWidth != 0 && mVideoHeight != 0) { //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight); getHolder().setFixedSize(mVideoWidth, mVideoHeight); if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) { // We didn't actually change the size (it was already at the size // we need), so we won't get a "surface changed" callback, so // start the video here instead of in the callback. if (mTargetState == STATE_PLAYING) { start(); if (mMediaController != null) { mMediaController.show(); } } else if (!isPlaying() && (seekToPosition != 0 || getCurrentPosition() > 0)) { if (mMediaController != null) { // Show the media controls when we're paused into a video and make 'em stick. mMediaController.show(0); } } } } else { // We don't know the video size yet, but should start anyway. // The video size might be reported to us later. if (mTargetState == STATE_PLAYING) { start(); 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
41/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

} } } }; private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() { public void onCompletion(MediaPlayer mp) { mCurrentState = STATE_PLAYBACK_COMPLETED; mTargetState = STATE_PLAYBACK_COMPLETED; if (mMediaController != null) { mMediaController.hide(); } if (mOnCompletionListener != null) { mOnCompletionListener.onCompletion(mMediaPlayer); } } }; private MediaPlayer.OnErrorListener mErrorListener = new MediaPlayer.OnErrorListener() { public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { Log.d(TAG, "Error: " + framework_err + "," + impl_err); mCurrentState = STATE_ERROR; mTargetState = STATE_ERROR; if (mMediaController != null) { mMediaController.hide(); } /* If an error handler has been supplied, use it and finish. */ if (mOnErrorListener != null) { if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) { return true; } } /* Otherwise, pop up an error dialog so the user knows that * something bad has happened. Only try and pop up the dialog * if we're attached to a window. When we're going away and no * longer have a window, don't bother showing the user an error. */ if (getWindowToken() != null) { Resources r = mContext.getResources(); int messageId;

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

42/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback; } else { messageId = com.android.internal.R.string.VideoView_error_text_unknown; } new AlertDialog.Builder(mContext) .setTitle(com.android.internal.R.string.VideoView_error_title) .setMessage(messageId) .setPositiveButton(com.android.internal.R.string.VideoView_error_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { /* If we get here, there is no onError listener, so * at least inform them that the video is over. */ if (mOnCompletionListener != null) { mOnCompletionListener.onCompletion(mMediaPlayer); } } }) .setCancelable(false) .show(); } return true; } }; private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { public void onBufferingUpdate(MediaPlayer mp, int percent) { mCurrentBufferPercentage = percent; } }; /** * Register a callback to be invoked when the media file * is loaded and ready to go. * * @param l The callback that will be run */ public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) {

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

43/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

mOnPreparedListener = l; } /** * Register a callback to be invoked when the end of a media file * has been reached during playback. * * @param l The callback that will be run */ public void setOnCompletionListener(OnCompletionListener l) { mOnCompletionListener = l; } /** * Register a callback to be invoked when an error occurs * during playback or setup. If no listener is specified, * or if the listener returned false, VideoView will inform * the user of any errors. * * @param l The callback that will be run */ public void setOnErrorListener(OnErrorListener l) { mOnErrorListener = l; } SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() { public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { mSurfaceWidth = w; mSurfaceHeight = h; boolean isValidState = (mTargetState == STATE_PLAYING); boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h); if (mMediaPlayer != null && isValidState && hasValidSize) { if (mSeekWhenPrepared != 0) { seekTo(mSeekWhenPrepared); } start(); if (mMediaController != null) { mMediaController.show(); }

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

44/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

} } public void surfaceCreated(SurfaceHolder holder) { mSurfaceHolder = holder; openVideo(); } public void surfaceDestroyed(SurfaceHolder holder) { // after we return from this we can't use the surface any more mSurfaceHolder = null; if (mMediaController != null) mMediaController.hide(); release(true); } }; /* * release the media player in any state */ private void release(boolean cleartargetstate) { if (mMediaPlayer != null) { mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; mCurrentState = STATE_IDLE; if (cleartargetstate) { mTargetState = STATE_IDLE; } } } @Override public boolean onTouchEvent(MotionEvent ev) { if (isInPlaybackState() && mMediaController != null) { toggleMediaControlsVisiblity(); } return false; } @Override public boolean onTrackballEvent(MotionEvent ev) { if (isInPlaybackState() && mMediaController != null) { toggleMediaControlsVisiblity(); 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
45/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

} return false; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_VOLUME_UP && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_MENU && keyCode != KeyEvent.KEYCODE_CALL && keyCode != KeyEvent.KEYCODE_ENDCALL; if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { if (mMediaPlayer.isPlaying()) { pause(); mMediaController.show(); } else { start(); mMediaController.hide(); } return true; } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP && mMediaPlayer.isPlaying()) { pause(); mMediaController.show(); } else { toggleMediaControlsVisiblity(); } } return super.onKeyDown(keyCode, event); } private void toggleMediaControlsVisiblity() { if (mMediaController.isShowing()) { mMediaController.hide(); } else { mMediaController.show(); } } public void start() { 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
46/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

if (isInPlaybackState()) { mMediaPlayer.start(); mCurrentState = STATE_PLAYING; } mTargetState = STATE_PLAYING; } public void pause() { if (isInPlaybackState()) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); mCurrentState = STATE_PAUSED; } } mTargetState = STATE_PAUSED; } // cache duration as mDuration for faster access public int getDuration() { if (isInPlaybackState()) { if (mDuration > 0) { return mDuration; } mDuration = mMediaPlayer.getDuration(); return mDuration; } mDuration = -1; return mDuration; } public int getCurrentPosition() { if (isInPlaybackState()) { return mMediaPlayer.getCurrentPosition(); } return 0; } public void seekTo(int msec) { if (isInPlaybackState()) { mMediaPlayer.seekTo(msec); mSeekWhenPrepared = 0; } else { mSeekWhenPrepared = msec; } 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
47/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

} public boolean isPlaying() { return isInPlaybackState() && mMediaPlayer.isPlaying(); } public int getBufferPercentage() { if (mMediaPlayer != null) { return mCurrentBufferPercentage; } return 0; } private boolean isInPlaybackState() { return (mMediaPlayer != null && mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE && mCurrentState != STATE_PREPARING); } public boolean canPause() { return mCanPause; } public boolean canSeekBackward() { return mCanSeekBack; } public boolean canSeekForward() { return mCanSeekForward; } }

Android 游戏开发十 位图旋转
今天有关 Android 游戏开发的基础,我们说下 Bitmap 相关的实用操作,这里我们就说下位图旋 转。在 Android 中图形的旋转和变化提供了方便的矩阵 Maxtrix 类,Maxtrix 类的 setRotate 方法 接受图形的变换角度和缩放, 最终 Bitmap 类的 createBitmap 方法中其中的重载函数, 可以接受 Maxtrix 对象,方法原型如下 public static Bitmap createBitmap (Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) 参数的具体意思 source 源 bitmap 对象 x 源坐标 x 位置 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
48/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

y 源坐标 y 位置 width 宽度 height 高度 m 接受的 maxtrix 对象,如果没有可以设置为 null filter 该参数仅对 maxtrix 包含了超过一个翻转才有效。 下面 Android123 给大家一个比较经典的例子,rotate 方法是静态方法可以直接调用,参数为 源 Bitmap 对象,参数二为旋转的角度,从 0~360,返回值为新的 Bitmap 对象。其中具体的宽 高可以调整。 public static Bitmap rotate(Bitmap b, int degrees) { if (degrees != 0 && b != null) { Matrix m = new Matrix(); m.setRotate(degrees, (float) b.getWidth() / 2, (float) b.getHeight() / 2); try { Bitmap b2 = Bitmap.createBitmap( b, 0, 0, b.getWidth(), b.getHeight(), m, true); if (b != b2) { b.recycle(); //Android 开发网再次提示 Bitmap 操作完应该显示的释放 b = b2; } } catch (OutOfMemoryError ex) { // Android123 建议大家如何出现了内存不足异常, 最好 return 原始的 bitmap 对象。 . } } return b; } 有关 Maxtrix 类的更多实用例子,我们将在以后多次提到。

Android 游戏开发 11 View 中手势识别
有关 Android 平台的游戏开发中我们需要涉及到控制, 在开始的 Android 游戏开发中我们提到了 按键和轨迹球的控制方式,从今天开始 Android123 开始给出大家游戏中其他的一些控制方式, 比如今天的手势操作和未来重力感应。 很多网友发现 Android 中手势识别提供了两个类,由于 Android 1.6 以下的版本比如 cupcake 中无法使用 android.view.GestureDetector,而 android.gesture.Gesture 是 Android 1.6 才开始 支持的,我们考虑到仍然有很多 Android 1.5 固件的网友,就来看下兼容性更强的 android.view.GestureDetector。 android.view.GestureDetector 类中有很多种重载版本, 在 下面 我们仅提到能够自定义在 View 中的两种方法,分别为 GestureDetector(Context context, GestureDetector.OnGestureListener listener) 和 GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler) 和。我们可以看到第一个参数

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

49/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

为 Context,所以我们想附着到某 View 时,最简单的方法就是直接从超类派生传递 Context, 实现 GestureDetector 里中提供一些接口。 下面我们就以实现手势识别的 onFling 动作,在 CwjView 中我们从 View 类继承,当然大家可 以从 TextView 等更高层的界面中实现触控。 class CwjView extends View { private GestureDetector mGD; public CwjView(Context context, AttributeSet attrs) { super(context, attrs); mGD = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { int dx = (int) (e2.getX() - e1.getX()); //计算滑动的距离 if (Math.abs(dx) > MAJOR_MOVE && Math.abs(velocityX) > Math.abs(velocityY)) { //降噪处理,必须有较大的动作才识别 if (velocityX > 0) { //向右边 } else { //向左边 } return true; } else { return false; //当然可以处理 velocityY 处理向上和向下的动作 } } }); } 在上面 Android123 提示大家仅仅探测了 Fling 动作仅仅实现了 onFling 方法, 这里相关的还有 以下几种方法来实现具体的可以参考我们以前的文章有详细的解释: boolean onDoubleTap(MotionEvent e) boolean onDoubleTapEvent(MotionEvent e) boolean onDown(MotionEvent e) void onLongPress(MotionEvent e) boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) void onShowPress(MotionEvent e) boolean onSingleTapConfirmed(MotionEvent e) 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
50/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

boolean onSingleTapUp(MotionEvent e)

接下来是重点,让我们的 View 接受触控,需要使用下面两个方法让 GestureDetector 类去处 理 onTouchEvent 和 onInterceptTouchEvent 方法。 @Override public boolean onTouchEvent(MotionEvent event) { mGD.onTouchEvent(event); return true; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return mGD.onTouchEvent(event); } } 有关重力感应的方向识别, 我们将告诉大家如何通过重力感应来控制我们的 Android 游戏, 目前对于大多数 Android 设备来说仅提供了重力感应器和加速感应器,只有较新或高端的 Android 设备还提供了陀螺仪,可以帮助我们测试角速度,来处理一些复杂的应用。详细的可以 参考我们的 Android 游戏开发 12 重力感应篇

Android 游戏开发 12 Sensor 重力感应
从 Android 手机开始,主流的智能机纷纷加入了感应器 Sensor 硬件,常见的有光线感应器、重 力感应器、加速感应器,而更高级的有磁极方向、陀螺仪、距离感应器、温度感应器等等。对于 Android 游戏开发,我们主要用到重力、加速、磁力和陀螺仪四种,当然部分游戏可能需要 GPS 或 Cellid 定位来修正一些位移信息。从系统中提高的感应器主要在 android.hardware 中,我们 可以看到系统提供了 android.hardware.SensorEventListener、Sensor 和 SensorManager 这三 个类,我们会发现除了可以获取感应器的信息,和感应器的原始数据外,并没有提供相关的逻辑 处理。Android123 将会分 3 篇来详细的介绍不同感应器的作用和逻辑处理,比如自由落体,晃 动,磁极,当前的旋转速度。 未来 Android123 将完成主要是一个基于 OpenGL 3D 的雷电游戏,最终加入联网对战效果可 以团队打怪实现手机 3D 网游充分发挥 Android 手机的娱乐能力。对于大多数新款 Android 手机 可能没有配备轨迹球或导航键的方向控制, 所以重力感应器是这类实时性较强游戏的首选控制方 式。主要有以下几点问题对于 Sensor 1. 降噪处理,如果做过 LBS 软件的大家可能明白偏移修正,在 GPS 无法正常获取数据较间 断时地图不能乱飘,这里 Sensor 也不例外,除了使用采样数据平均值获取外,可以间隔采样的 方法来处理。细节的算法我们将在下节给出示例代码。 2. 感应器的敏感度,在 Android 中提供了四种延迟级别分别为 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
51/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

SENSOR_DELAY_FASTEST 最低延迟,一般不是特别敏感的处理不推荐使用,该种模式可能 造成手机电力大量消耗, 由于传递的为原始数据, 算法不处理好将会影响游戏逻辑和 UI 的性能, 所以 Android 开发网不推荐大家使用。 SENSOR_DELAY_GAME 游戏延迟,一般绝大多数的实时性较高的游戏都使用该级别 int SENSOR_DELAY_NORMAL 标准延迟,对于一般的益智类或 EASY 级别的游戏可以使用, 但过低的采样率可能对一些赛车类游戏有跳帧现象。 int SENSOR_DELAY_UI 用户界面延迟,一般对于屏幕方向自动旋转使用,相对节省电能和逻 辑处理,一般游戏开发中我们不使用。

Android 游戏开发 13 Sensor 感应示例
有关 Android 游戏开发中的 Sensor 感应示例今天我们将一起来讨论,对于目前最新的 Android 2.2 平台而言仍然没有具体的感应判断逻辑,下面我们一起定义下常用的感应动作事件。首先 Android123 提醒大家由于是三轴的立体空间感应所以相对于轨迹球、导航键的上下左右外,还 提供了前后的感应,所以我们定义最基本的六种空间方向。 public static final int CWJ_UP = 0; public static final int CWJ_DOWN = 1; public static final int CWJ_LEFT = 2; public static final int CWJ_RIGHT = 4; public static final int CWJ_FORWARD = 8; //向前 public static final int CWJ_BACKWARD = 16; //向后 下面我们做精确的角度旋转修正值定义,我们用到 yaw、pitch 和 roll,相信学过 3D 开发的网 友不会对这些陌生的,我们就把他们对应为绕 y、x、z 轴的角度好了,如果你们没有学过 3D 相 关的知识这里 Android 开发网推荐大家可以通过 Cube 例子自定义 Render 来观察这三个值对应 立方体的旋转角度。 Yaw 在(0,0,0)中, 以 xOz 的坐标平面中围绕 y 轴旋转,如果是负角则我们定义为 CWJ_YAW_LEFT 即往左边倾斜,同理我们定义如下: public static final int CWJ_YAW_LEFT = 0; public static final int CWJ_YAW_RIGHT = 1; public static final int CWJ_PITCH_UP = 2; public static final int CWJ_PITCH_DOWN = 4; public static final int CWJ_ROLL_LEFT = 8; public static final int CWJ_ROLL_RIGHT = 16; 我们通过加速感应器可以获得 SensorEvent 的四个值, 今天 Android123 给大家一个简单示例, 不考虑其他因素,在 public int accuracy 、public Sensor sensor 、public long timestamp 和 public final float[] values 中,我们获取 values 的浮点数组来判断方向。 int nAndroid123=CWJ_UP //向上

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

52/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

float ax = values[0]; float ay = values[1]; float az = values[2]; float absx = Math.abs(ax); float absy = Math.abs(ay); float absz = Math.abs(az); if (absx > absy && absx > absz) { if (ax > 0) { nAndroid123 = CWJ_RIGHT; } else { nAndroid123 = CWJ_LEFT; } } else if (absy > absx && absy > absz) { if (ay > 0) { nAndroid123= CWJ_FORWARD; } else { nAndroid123= CWJ_BACKWARD; } } else if (absz > absx && absz > absy) { if (az > 0) { nAndroid123 = CWJ_UP; } else { nAndroid123 = CWJ_DOWN; } } else { nAndroid123 = CWJ_UNKNOWN; } 有关偏向角度问题,我们将在下一次详细讲述,对于一般的 2D 游戏,我们可以参考本文来实 现重力控制,所以总体来说 Android 游戏开发比较简单易懂,Android 平台使用的 Java 语言还 是很适合做游戏的。在逻辑表达上更清晰。

Android 游戏开发 14 游戏开发实战一
从今天开始 Android123 将开始带领大家进入 Android 游戏开发实战篇, 本次我们首个游戏为 2D 的基于 SurfaceView 的类似横版卷轴游戏。第一天我们说下需要做哪些准备: 一、游戏地图编辑器,在 J2ME 时代我们可能都是用 GIF 分割多帧或 BMP 上放置多个图片通 过减少文件头来压缩体积,但是在 Android 平台上开发游戏我们不需要那么节省,不过资源的释 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
53/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

放仍然很重要,否则会出现 OutOfMemoryError 这样的悲剧发生。一般简单的 2D 平面游戏地图 都是使用二维数组来标记的。 我们可以想象矩阵中的每个元素对应每个图片资源。 详细的存储方 法我们将在下次具体讲到。 二、控制方式,由于横版过关类游戏不适合重力感应操作,我们这里选择屏幕下方加设一个区 域,放置上、下、左、右按键,同时右侧给出常用的攻击、跳跃按钮,而游戏的暂停可以通过触 控实现继续或暂停。 三、音效处理,常规的一般在攻击比如出拳、发射子弹的过程中有音效,或对手自己中弹(当 然对于 Android 图形开发来说就是碰撞检测)时发出音效,跳跃、过关均会需要一些声音素材文 件,一般的游戏还需要背景音乐配合烘托游戏气氛。 四、游戏逻辑,这是主要的地方,我们将通过实例代码让大家了解游戏开发中是如何的卷轴、 人物的跳跃、攻击有效判断即碰撞检测,电脑智能等算法问题。 五、细节处理,比如计分,等级,游戏计时,关卡档案的存档,读取以及开场设计,关卡过渡 的过场动画处理。 这里 Android 开发网提示大家,目前很多 J2ME 的游戏可以轻松的移植到 Android 平台,主要 的细节只要了解 Google Android 平台的图形相关问题即可,主要是 Bitmap、Drawable 和 View 线程处理问题。

Android 游戏开发 15 按键中断处理
有关 Android 平台上游戏开发中我们需要处理一些特别的按键事件, 对于突发的事情我们需要特 别的考虑,比如突然来电话了和游戏中按下一些特殊的键,比如拍照键 @Override public boolean dispatchKeyEvent(KeyEvent event) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_VOLUME_UP: //音量键+ //音量键case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_CAMERA: //拍照键 case KeyEvent.KEYCODE_FOCUS: // 起时触发 } return true; //这些标记为处理过,则不在往内部传递 default: break; } //拍照键半按的对焦状态 //Android123 提示如果按键按下后弹 event.getAction() == KeyEvent.ACTION_UP

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

54/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

return super.dispatchKeyEvent(event); } 对于游戏突然来电话我们一般采取通过 PhoneStateListener 类提供的 public void onCallStateChanged (int state, String incomingNumber) 回调方法可以获取电话的状态,比如 常规空闲时 CALL_STATE_IDLE、来电时 CALL_STATE_RINGING 和 CALL_STATE_OFFHOOK 摘机通话中, 有关处理的细节网友可以 查看 Android Git 项目中的 Music,在 Android 开源项目中系统自带的音乐播放器可以很好的处 理, 比如在通话结束后恢复音乐播放, 而我们游戏需要做的就是记住当前的游戏状态尽量数据持 久化处理,不能因为长时间的通话,游戏的 Activity 被清理了,这里我们一般通过 onSaveInstanceState 来保存当前窗口的一些记录,通过 Intent 标记来让系统管理好我们游戏的 生命周期。

Android 游戏开发 16 异步音乐播放
在 Android 游戏开发中我们必须考虑背景音乐播放问题,在 Android 平台中提供了 MediaPlayer 类可以播放声音,但是游戏除了播放音乐外还需要考虑画面的流畅性,以及多种音效同时播放, 所以必须用到 Android 多线程机制和异步音效播放。Android SDK 从 1.0 开始就提供了 AsyncPlayer 类,这里我们为了根据我们自己的需要可以派生或修改出更灵活的播放类。 import android.content.Context; import android.net.Uri; import android.os.PowerManager; import android.os.SystemClock; import android.util.Log; import java.io.IOException; import java.lang.IllegalStateException; import java.util.LinkedList;

public class AsyncPlayer { private static final int PLAY = 1; private static final int STOP = 2; private static final boolean mDebug = false; private static final class Command { int code; Context context; Uri uri; boolean looping; int stream; long requestTime;

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

55/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

public String toString() { return "{ code=" + code + " looping=" + looping + " stream=" + stream + " uri=" + uri + " }"; } } private LinkedList<Command> mCmdQueue = new LinkedList(); //用一个链表保存播放参 数队列 private void startSound(Command cmd) { try { MediaPlayer player = new MediaPlayer(); player.setAudioStreamType(cmd.stream); player.setDataSource(cmd.context, cmd.uri); //设置媒体源,这里 Android123 提示大 家本类的 public void play (Context context, Uri uri, boolean looping, int stream) 类第二个参数 Uri 为媒体位置。 player.setLooping(cmd.looping); player.prepare(); player.start(); if (mPlayer != null) { mPlayer.release(); } mPlayer = player; } catch (IOException e) { Log.w(mTag, "error loading sound for " + cmd.uri, e); } catch (IllegalStateException e) { Log.w(mTag, "IllegalStateException (content provider died?) " + cmd.uri, e); } } private final class Thread extends java.lang.Thread { Thread() { super("AsyncPlayer-" + mTag); } public void run() { while (true) { Command cmd = null; synchronized (mCmdQueue) { //同步方式执行 //通过多线程方式不阻塞调用者

cmd = mCmdQueue.removeFirst(); } 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
56/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

switch (cmd.code) { case PLAY: startSound(cmd); break; case STOP: if (mPlayer != null) { mPlayer.stop(); mPlayer.release(); mPlayer = null; } else { Log.w(mTag, "STOP command without a player"); } break; } synchronized (mCmdQueue) { if (mCmdQueue.size() == 0) { mThread = null; releaseWakeLock(); return; } } } } } private String mTag; private Thread mThread; private MediaPlayer mPlayer; private PowerManager.WakeLock mWakeLock;

private int mState = STOP; public AsyncPlayer(String tag) { if (tag != null) { mTag = tag; } else { mTag = "AsyncPlayer"; } }

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

57/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

public void play(Context context, Uri uri, boolean looping, int stream) { Command cmd = new Command(); cmd.requestTime = SystemClock.uptimeMillis(); //这里为了测试性能,传递了开始执行前 的系统 tickcount 计时器值 cmd.code = PLAY; cmd.context = context; cmd.uri = uri; cmd.looping = looping; cmd.stream = stream; synchronized (mCmdQueue) { enqueueLocked(cmd); mState = PLAY; } }

public void stop() { synchronized (mCmdQueue) { if (mState != STOP) { Command cmd = new Command(); cmd.requestTime = SystemClock.uptimeMillis(); cmd.code = STOP; enqueueLocked(cmd); mState = STOP; } } } private void enqueueLocked(Command cmd) { mCmdQueue.add(cmd); if (mThread == null) { acquireWakeLock(); mThread = new Thread(); mThread.start(); } } 一般对于 Android 游戏而言下面的代码不用考虑,一般用户都在交互操作,不会出现屏幕锁问 题 public void setUsesWakeLock(Context context) { //电源管理 wakelock 处理 if (mWakeLock != null || mThread != null) { throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock + " mThread=" + mThread); 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
58/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

} PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag); } private void acquireWakeLock() { if (mWakeLock != null) { mWakeLock.acquire(); } } private void releaseWakeLock() { //解锁 if (mWakeLock != null) { mWakeLock.release(); } } } //加锁

Android 游戏开发 17 图像渐变特效
在 Android 游戏开发中我们不免要涉及到一些图形特效处理, 今天主要看下 Android 平台下实现 渐变效果。 android.graphics 中我们可以找到有关 Gradient 字样的类, 在 比如 LinearGradient 线 性渐变、RadialGradient 径向渐变和 角度渐变 SweepGradient 三种,他们的基类为 android.graphics.Shader。为了显示出效果 android123 使用一个简单的例子来说明。 一、LinearGradient 线性渐变 在 android 平台中提供了两种重载方式来实例化该类分别为,他们的不同之处为参数中第一种 方法可以用颜色数组,和位置来实现更细腻的过渡效果,比如颜色采样 int[] colors 数组中存放 20 种颜色,则渐变将会逐一处理。而第二种方法参数仅为起初颜色 color0 和最终颜色 color1。 LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile) LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile) 使用实例如下 Paint p=new Paint(); LinearGradient lg=new LinearGradient(0,0,100,100,Color.RED,Color.BLUE,Shader.TileMode.MIRROR); //参数一为 渐变起初点坐标 x 位置,参数二为 y 轴位置,参数三和四分辨对应渐变终点,最后参数为平铺方 式,这里设置为镜像

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

59/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

刚才 Android 开发网已经讲到 Gradient 是基于 Shader 类,所以我们通过 Paint 的 setShader 方法来设置这个渐变,代码如下: p.setShader(lg); canvas.drawCicle(0,0,200,p); //参数 3 为画圆的半径,类型为 float 型。 二、 RadialGradient 镜像渐变 有了上面的基础,我们一起来了解下径向渐变。和上面参数唯一不同的是,径向渐变第三个参 数是半径,其他的和线性渐变相同。 RadialGradient(float x, float y, float radius, int[] colors, float[] positions, Shader.TileMode tile) RadialGradient(float x, float y, float radius, int color0, int color1, Shader.TileMode tile) 三、 SweepGradient 角度渐变 对于一些 3D 立体效果的渐变可以尝试用角度渐变来完成一个圆锥形, 相对来说比上面更简单, 前两个参数为中心点,然后通过载入的颜色来平均的渐变渲染。 SweepGradient(float cx, float cy, int[] colors, float[] positions) //对于最后一个参数 SDK 上的描 述为 May be NULL. The relative position of each corresponding color in the colors array, beginning with 0 and ending with 1.0. If the values are not monotonic, the drawing may produce unexpected results. If positions is NULL, then the colors are automatically spaced evenly.,所以 Android123 建议使用下面的重载方法,本方法一般为 NULL 即可。 SweepGradient(float cx, float cy, int color0, int color1) 有关 Android 游戏开发中需要遇到的图形绘制技术, 我们近期仍然需要花费更多的篇幅去介绍, 希望大家有更坚实的基础,有关更多的 Android 开发内容请关注 Android123.

Android 游戏开发 18 SoundPool 类
对于 Android 的游戏音效播放,上次 Android123 已经告诉大家使用 SoundPool 类来实现,由于 本次我们的游戏需要多种音效同时播放所以就选择了 SoundPool 类,它和 Android 提供常规的 MediaPlayer 类有哪些不同呢? 1. SoundPool 载入音乐文件使用了独立的线程, 不会阻塞 UI 主线程的操作。 但是这里 Android 开发网提醒大家如果音效文件过大没有载入完成,我们调用 play 方法时可能产生严重的后果, 这里 Android SDK 提供了一个 SoundPool.OnLoadCompleteListener 类来帮助我们了解媒体文 件是否载入完成,我们重载 onLoadComplete(SoundPool soundPool, int sampleId, int status) 方法即可获得。 2. 从上面的 onLoadComplete 方法可以看出该类有很多参数,比如类似 id,是的 SoundPool 在 load 时可以处理多个媒体一次初始化并放入内存中,这里效率比 MediaPlayer 高了很多。

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

60/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

3. SoundPool 类支持同时播放多个音效,这对于游戏来说是十分必要的,而 MediaPlayer 类 是同步执行的只能一个文件一个文件的播放。 SoundPool 类使用示例代码: SoundPool sp=new SoundPool(8, /*maxStreams*/, AudioManager.STREAM_MUSIC /*streamType*/, 100 /*srcQuality*/) ; 有关载入音效的方法,有以下几种方法 int load(Context context, int resId, int priority) //从 APK 资源载入 int load(FileDescriptor fd, long offset, long length, int priority) //从 FileDescriptor 对象载入 int load(AssetFileDescriptor afd, int priority) //从 Asset 对象载入 int load(String path, int priority) //从完整文件路径名载入 我们看到了每个 load 的重载版本的最后一个参数为优先级,这里用于播放多个文件时,系统 会优先处理不过目前 Android123 提示大家 SDK 提到了目前并没有实现,所以没有实际的效果。 对于播放, 可以使用 play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate) 而停止则可以使用 pause(int streamID) 方法, 这里的 streamID 和 soundID 均在构 造 SoundPool 类的第一个参数中指明了总数量,而 id 从 0 开始。如果您遇到了疑问可以来函至 android123@163.com

Android 游戏开发 19 分辨率大全
对于 Android 游戏开发我们不得不像 iPhone 那样思考兼容 Android 平板电脑,对于苹果要考虑 iPad、iPhone 3GS 和 iPhone 4 等屏幕之间的兼容性,对于几乎所有的分辨率 Android123 总结 了大约超过 20 中粉笔阿女郎的大小和对应关系,对于开发 Android 游戏而言可以考虑到未来的 3.0 以及很多平板电脑的需要。 常规的我们可能只考虑 QVGA,HVGA,WVGA,FWVGA 和 DVGA,但是抛去了手机不谈, 可能平板使用类似 WSVGA 的 1024x576 以及 WXGA 的 1280x768 等等。 QVGA = 320 * 240; WQVGA = 320 * 480; WQVGA2 = 400 * 240; WQVGA3 = 432 * 240; HVGA = 480 * 320; VGA = 640 * 480; WVGA = 800 * 480; WVGA2 = 768 * 480; FWVGA = 854 * 480; DVGA = 960 * 640; PAL = 576 * 520; NTSC = 486 * 440; 广州优蜜信息科技有限公司 Tel: 020-39341996 Fax: 020-39340892
61/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

SVGA = 800 * 600; WSVGA = 1024 * 576; XGA = 1024 * 768; XGAPLUS = 1152 * 864; HD720 = 1280 * 720; WXGA = 1280 * 768; WXGA2 = 1280 * 800; WXGA3 = 1280 * 854; SXGA = 1280 * 1024; WXGA4 = 1366 * 768; SXGAMINUS = 1280 * 960; SXGAPLUS = 1400 * 1050; WXGAPLUS = 1440 * 900; HD900 = 1600 * 900; WSXGA = 1600 * 1024; WSXGAPLUS = 1680 * 1050; UXGA = 1600 * 1200; HD1080 = 1920 * 1080; QWXGA = 2048 * 1152; WUXGA = 1920 * 1200; TXGA = 1920 * 1400; QXGA = 2048 * 1536; WQHD = 2560 * 1440; WQXGA = 2560 * 1600; QSXGA = 2560 * 2048; QSXGAPLUS = 2800 * 2100; WQSXGA = 3200 * 2048; QUXGA = 3200 * 2400; QFHD = 3840 * 2160; WQUXGA = 3840 * 2400; HD4K = 4096 * 2304; HXGA = 4096 * 3072; WHXGA = 5120 * 3200; HSXGA = 5120 * 4096; WHSXGA = 6400 * 4096; HUXGA = 6400 * 4800; SHV = 7680 * 4320; WHUXGA = 7680 * 4800; 对于 Android 游戏中适应高分辨率的平板而言,资源可以考虑一个强制的绝对布局保证全屏显 示,而手机上的多种分辨率使用相对布局更为合理些。

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

62/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

Android 游戏开发 20 双按事件捕获
对于游戏开发中我们可能经常要用到双按屏幕,在 Android 1.6 以前并没有提供完善的手势识别 类, Android 1.5 SDK 中我们可以找到 android.view.GestureDetector.OnDoubleTapListener, 在 但是经过测试仍然无法正常工作, 不知道什么原因, 如果您知道可以联系 android123@163.com 共享下。最终我们使用的解决方法如下 最终我们测试的如下:

public class TouchLayout extends RelativeLayout { public Handler doubleTapHandler = null; protected long lastDown = -1; public final static long DOUBLE_TIME = 500;

public TouchLayout(Context context) { super(context); } public TouchLayout(Context context, AttributeSet attrs) { super(context, attrs); } public TouchLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); }

public boolean onTouchEvent(MotionEvent event) { this.handleEvent(event); if (event.getAction() == MotionEvent.ACTION_DOWN) { long nowDown = System.currentTimeMillis(); if (nowDown - lastDown < DOUBLE_TIME) { if (doubleTapHandler != null) doubleTapHandler.sendEmptyMessage(-1);

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

63/64

有米手机应用广告(优蜜信息科技)

www.youmi.net

} else { lastDown = nowDown; } } return true; }

protected void handleEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //Do sth 这里处理即可 break; case MotionEvent.ACTION_UP: //Do sth break; } }

}

广州优蜜信息科技有限公司

Tel: 020-39341996

Fax: 020-39340892

64/64


相关文章:
很多初学Android游戏开发的朋友,往往会显得有些无所适....txt
很多初学Android游戏开发的朋友,往往会显得有些无所适从,他们_互联网_IT/计算机...项目地址: HYPERLINK "http://code.google.com/p/rokon/%20" \t...
基于Android策略型游戏开发毕业设计.doc
本人授权省级优秀学士学位 设计书评选机构将本设计书全部或部分内容编入有关数据...Android游戏开发20回合 ... 64页 5下载券 android 3D游戏开发教程... 52页...
安卓SDK下载与安装(2.1版) 有米分享.doc
安卓SDK下载与安装(2.1版) 有米分享 - 近日Google发布了Android 2.1 SDK,使开发者能够通过新版SDK开发基于Android 2.1固件的应用程序,以适应Nexus O...
Android游戏应用项目案例介绍.doc
开发技术 规模 基于 Android 的魔钻小子手机游戏 ...在 落下的过程中,任何方向只要有 4 个颜色相同的...20 5 5 1 70 UI+编码+ 测试 3 5 5 5 10 ...
Android游戏开发大全全文阅读_Android游戏开发大全免费....txt
但是纵观这些本来就为数不多的Android书籍,却没有一本是关于Android游戏开发专题...平台下游戏开发的整个流程,同时在游戏开发的介绍过程中还分享了作者多年积累的...
为什么Android才是游戏开发者的乐土_图文攻略_全通关攻略_高分....pdf
的多样化将安卓市场划分为不同的目标群体,让大大小小的游戏开发公司都能有立足...这里为社交类游戏提供诸如排行榜、成就、云端储存(cloud-saving)、回合制策略游戏...
(详细版)iOS开发:如何让游戏APP快速拥有录制分享功能.pdf
(详细版)iOS开发:如何让游戏APP快速拥有录制分享功能...Android、Unity3D、Cocos2d-X、自有引擎 7、支持自...《XXXX》游戏中取得了 XXX 米的好成绩,快来挑战我...
Android游戏开发十一.doc
比较简单但是很实用的一个 Android sdk 自带工具、 这里给大家做一个分享下经 ...Android游戏开发之地图编... 19页 1下载券 Android游戏开发20回合 ... 64页...
基于Android 2048游戏.doc
基于Android2048游戏开发 本科毕业论文(设计)题目:...现有 2048 游戏最大的不足在于过度强调简洁,它是...(); 20 安徽新华学院 15 届本科毕业论文(设计) ...
《Android手机游戏开发》课程教学大纲.pdf
Android 手机游戏开发》 课程教学大纲课程名称: 《Android 手机游戏开发》 先导课程: 《Java 面向对象程序设计》 总学时: 90 学时 教学教材:无 参考教材: ...
Android游戏开发的入门实例.doc
在 Android 系统上开发游戏是 Android 开发学习者所向往的, 有成就感也有乐趣,...Android游戏开发20回合 ... 64页 5下载券 2018 Baidu |由 百度云 提供计算...
android适配各种分辨率的问题.doc
而 Android4.0 的小 米就会使用 drawable-hdpi-v11 及 layout-hdpi-v11 里面...20 中粉笔阿女郎的 大小和对应关系,对于开发 Android 游戏而言可以考虑到未来的...
Android游戏开发之旅.doc
有关详细的实现我们今 天主要说下 Android 的 Canvas 和 Paint 对象的使用实例...Android游戏开发20回合 ... 64页 5下载券 android 3D游戏开发教程 45页 ...
Android开发者资料大全(开发人员必看).xls
Android开发者资料大全(开发人员必看)_计算机软件及应用_IT/计算机_专业资料。Android开发人员资料大全(开发 在移动开发如火如荼的今天,Android开发市场广阔,是目前市场...
ANDROID游戏内测试卷A.pdf
课程名称: 《Android 游戏:Android 平台游戏开发》考试(考查) 题号得分一、...通过 Handler 向系统提交 Canvas 3 20、在 Android 中,有一 SurfaceView 对象...
基于Android的2048游戏开发-毕业设计(论文).doc
毕业设计(论文) 基于 Android 平台 2048 游戏开发 教学单位:计算
毕业论文_android游戏开发.doc
毕业论文_android游戏开发_互联网_IT/计算机_专业...it is to create a one-day water 20 million yuan...到其他程序,这时 activity 处于后台中,再切回 程序...
android手机游戏开发设计说明书_图文.doc
中国象棋 佟乃坤 20091130126 09 软件 李红军 20 ...游戏开发有很大的兴趣, 决定开发一款基于 Android ...以上的走棋规 则,则可以走动,否则棋子强制放回原...
Android_3D游戏开发教程_图文.pdf
Android OpenGL ES开发主讲人:杨丰盛 华章培训网、[www.hztraining.com]华章培训...这篇文档有word格式吗?Android_3D游戏开发教程 2018-06-23 15:02:20 文档...
基于Cocos2dandroid的手机游戏开发.doc
开发工具和方法多种多样,这类的游戏软件大多数 是基于 C++和 java 的,有的不...Android游戏开发20回合 ... 64页 5下载券 基于Android平台的手机游... 65...
更多相关标签: