Tilt Ball 演练
使用加速度计为 Android OS 创建一个简单的球形应用。
引言
本教程将指导您创建一个简单的 Android 手机应用程序。该应用程序演示了如何使用加速度计在手机倾斜时使球在屏幕上移动。该项目使用 Eclipse 和 Android SDK 构建。
背景
本教程假定您已经安装并运行了 Eclipse 环境。如果您是 Eclipse 和 Android 开发的新手,我建议您阅读温度转换器教程,该教程可以在这里找到。
Using the Code
您可以通过以下步骤创建项目。如果您喜欢加载整个项目,请下载/解压缩项目文件,然后打开 Eclipse 并选择“文件”->“导入...”->“常规”->“现有项目”,然后选择 TiltBall
项目的根文件夹。
开始吧
启动 Eclipse(我使用的是 Eclipse Classic 版本 3.6.2)。
选择文件 -> 新建 -> 项目 -> Android -> Android 项目。
单击“下一步”。
填写字段,如下所示。您可以使用 Android 1.5 或更高版本。
单击“完成”。
项目创建后,打开 AndroidManifest.xml。
单击源代码编辑器中的最后一个选项卡以查看实际的 XML。
如果您使用的是 Android 1.6 或更高版本,请将 SDK 版本更新为 4。在版本 3 中,安装此应用程序将需要手机和存储权限。Android 1.5 需要 SDK 版本 3(否则应用程序将无法启动)。此值也可以在“新建 Android 项目”对话框中设置,但两种方式都有效。
<uses-sdk android:minSdkVersion="4" />
插入以下标签以在应用程序运行时保持手机唤醒
.......
</application>
<uses-permission android:name="android.permission.WAKE_LOCK" />
</manifest>
更新 activity 标签以将应用程序设置为纵向模式
<activity android:name=".TiltBallActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:configChanges="keyboardHidden|orientation">
打开 main.xml 并单击第二个选项卡以查看实际的 XML。
用此文本替换现有 XML。我们将使用 Framelayout
,以便单个子对象(球)将填充整个父级。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/main_view">
</FrameLayout>
右键单击 TiltBall
->src->droid.pkg 并选择新建->类。
输入 BallView
作为类名。其他设置可以保持不变。
单击“完成”。
在 BallView.java 类中,用此代码替换现有代码
package droid.pkg;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
public class BallView extends View {
public float x;
public float y;
private final int r;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//construct new ball object
public BallView(Context context, float x, float y, int r) {
super(context);
//color hex is [transparency][red][green][blue]
mPaint.setColor(0xFF00FF00); //not transparent. color is green
this.x = x;
this.y = y;
this.r = r; //radius
}
//called by invalidate()
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(x, y, r, mPaint);
}
}
在 TiltBallActivity.java 中,删除现有代码。
输入我们应用程序所需的 package
名称和 import
package droid.pkg;
import java.util.Timer;
import java.util.TimerTask;
import droid.pkg.R;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.SensorEventListener;
创建主活动和类级别变量。
public class TiltBallActivity extends Activity {
BallView mBallView = null;
Handler RedrawHandler = new Handler(); //so redraw occurs in main thread
Timer mTmr = null;
TimerTask mTsk = null;
int mScrWidth, mScrHeight;
android.graphics.PointF mBallPos, mBallSpd;
为主活动添加 onCreate
处理程序。
@Override
public void onCreate(Bundle savedInstanceState) {
设置窗口属性,使我们的应用程序全屏运行,并且手机保持唤醒状态。
requestWindowFeature(Window.FEATURE_NO_TITLE); //hide title bar
//set app to full screen and keep screen on
getWindow().setFlags(0xFFFFFFFF,
LayoutParams.FLAG_FULLSCREEN|LayoutParams.FLAG_KEEP_SCREEN_ON);
添加其他内务处理任务并创建指向主活动的指针。
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//create pointer to main screen
final FrameLayout mainView =
(android.widget.FrameLayout)findViewById(R.id.main_view);
设置初始变量值。球将从屏幕中心开始,速度为零。
//get screen dimensions
Display display = getWindowManager().getDefaultDisplay();
mScrWidth = display.getWidth();
mScrHeight = display.getHeight();
mBallPos = new android.graphics.PointF();
mBallSpd = new android.graphics.PointF();
//create variables for ball position and speed
mBallPos.x = mScrWidth/2;
mBallPos.y = mScrHeight/2;
mBallSpd.x = 0;
mBallSpd.y = 0;
创建球对象并将其添加到主屏幕。
//create initial ball
mBallView = new BallView(this, mBallPos.x, mBallPos.y, 5);
mainView.addView(mBallView); //add ball to main screen
mBallView.invalidate(); //call onDraw in BallView
创建加速度计传感器的处理程序。这将根据传感器值调整球速。
//listener for accelerometer, use anonymous class for simplicity
((SensorManager)getSystemService(Context.SENSOR_SERVICE)).registerListener(
new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
//set ball speed based on phone tilt (ignore Z axis)
mBallSpd.x = -event.values[0];
mBallSpd.y = event.values[1];
//timer event will redraw ball
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {} //ignore
},
((SensorManager)getSystemService(Context.SENSOR_SERVICE))
.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0),
SensorManager.SENSOR_DELAY_NORMAL);
创建触摸传感器的处理程序。这将把球移动到触摸点。
//listener for touch event
mainView.setOnTouchListener(new android.view.View.OnTouchListener() {
public boolean onTouch(android.view.View v, android.view.MotionEvent e) {
//set ball position based on screen touch
mBallPos.x = e.getX();
mBallPos.y = e.getY();
//timer event will redraw ball
return true;
}});
} //OnCreate
创建用户菜单的处理程序。只有一个菜单条目(退出),这并不是真正需要的。当用户切换到另一个应用程序时,应用程序将退出。
//listener for menu button on phone
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add("Exit"); //only one menu item
return super.onCreateOptionsMenu(menu);
}
创建菜单选择的处理程序。目前只有一个选择。
//listener for menu item clicked
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
// Handle item selection
if (item.getTitle() == "Exit") //user clicked Exit
finish(); //will call onPause
return super.onOptionsItemSelected(item);
}
创建 onPause
事件的处理程序。当我们的应用程序移到后台(用户按下 Home/Back 等)时,会发生此事件。默认情况下,主活动线程将暂停,但后台线程(即 Timer
)将继续运行。要完全暂停我们的应用程序,我们将终止 Timer
线程。
//For state flow see https://developer.android.com.cn/reference/android/app/Activity.html
@Override
public void onPause() //app moved to background, stop background threads
{
mTmr.cancel(); //kill\release timer (our only background thread)
mTmr = null;
mTsk = null;
super.onPause();
}
创建 onResume
的处理程序。这意味着应用程序刚刚启动,或者用户在操作系统将其从内存中移除之前返回了我们的应用程序。
@Override
public void onResume() //app moved to foreground (also occurs at app startup)
{
//create timer to move ball to new position
mTmr = new Timer();
mTsk = new TimerTask() {
public void run() {
您可以记录 Timer
事件以确认 Timer
线程何时结束。如果您使用手机(而不是虚拟设备)进行调试,则需要从 Android Market 获取一个(免费的)LogCat 查看器。除非您使用虚拟设备,否则日志消息不会显示在 Eclipse 调试器中。
//if debugging with external device,
// a log cat viewer will be needed on the device
android.util.Log.d("TiltBall","Timer Hit - " + mBallPos.x + ":" + mBallPos.y);
根据当前球速(在加速度计处理程序中设置)移动球。
//move ball based on current speed
mBallPos.x += mBallSpd.x;
mBallPos.y += mBallSpd.y;
如果球到达屏幕边缘,则将球位置设置为屏幕的另一侧。如果没有此设置,球将消失。您可以修改此代码,使球在边缘停止,而不是环绕到另一侧。
//if ball goes off screen, reposition to opposite side of screen
if (mBallPos.x > mScrWidth) mBallPos.x=0;
if (mBallPos.y > mScrHeight) mBallPos.y=0;
if (mBallPos.x < 0) mBallPos.x=mScrWidth;
if (mBallPos.y < 0) mBallPos.y=mScrHeight;
使用新位置更新球对象并重新绘制。请注意,我们需要在后台线程中重新绘制球,以防止主线程锁定(死锁)。
//update ball class instance
mBallView.mX = mBallPos.x;
mBallView.mY = mBallPos.y;
//redraw ball. Must run in background thread to prevent thread lock.
RedrawHandler.post(new Runnable() {
public void run() {
mBallView.invalidate();
}});
}}; // TimerTask
onResume
处理程序的末尾。以 10 毫秒的间隔启动计时器。
mTmr.schedule(mTsk,10,10); //start timer
super.onResume();
} // onResume
添加 onDestroy
处理程序。当应用程序停止(而不仅仅是暂停)时,会调用此函数。调用 killProcess
将从内存中删除应用程序,但这并非必需,因为当内存不足时,操作系统将回收内存。
@Override
public void onDestroy() //main thread stopped
{
super.onDestroy();
//wait for threads to exit before clearing app
System.runFinalizersOnExit(true);
//remove app from memory
android.os.Process.killProcess(android.os.Process.myPid());
}
添加 onConfigurationChanged
处理程序。当用户将手机侧向倾斜时,默认情况下,应用程序将更改为横向视图并调用 onCreate
。我们希望保持纵向视图,因此我们捕获并忽略此事件。
//listener for config change.
//This is called when user tilts phone enough to trigger landscape view
//we want our app to stay in portrait view, so bypass event
@Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
}
} //TiltBallActivity
构建项目(项目->全部构建)。如果已设置自动构建,则项目将已构建。
运行应用程序
要查看加速度计实际工作,您需要将手机连接到计算机
- 使用 USB 线连接手机(您可能需要安装手机的 USB 驱动程序)
- 在手机上,在“设置”->“应用程序”->“开发”中,启用 USB 调试
- 手机上的 USB 存储应禁用
在 Eclipse 中,按 F11 开始调试。
几秒钟后(如果一切顺利),应用程序应在您的手机上启动。
如果 Eclipse 启动了虚拟设备
- 关闭虚拟设备
- 转到运行->调试配置
- 在目标下的配置设置中,选择手动。这将允许您选择要在哪个设备上进行调试
单击调试,然后在下一个屏幕中选择“选择正在运行的 Android 设备”,然后单击“确定”开始调试
如果您没有看到任何正在运行的设备,请确认您的手机已连接到 PC 并且您有可用的 USB 线。您还可以下载 USBDeview(免费软件),它会列出连接到 PC 的 USB 设备。
要退出应用程序,请使用手机上的菜单或在 Eclipse 中选择“运行”->“终止”。
使用 APK 文件将应用程序安装到手机
在手机上,在“设置”->“应用程序”中,启用“未知来源”以允许在手机上安装非市场应用程序
在 Eclipse 中,选择 File->Export..->Android-> Export Android Application。
单击“下一步”。
输入 TiltBall
作为项目名称。
单击“下一步”。
如果您已经有密钥库,请选择“使用现有密钥库”。如果没有,以下是创建密钥库的步骤
选择“创建新密钥库”。输入文件名(不需要扩展名)和密码
单击“下一步”。
对于 Alias 和 Password,您可以使用在上一屏幕中输入的值。将有效期设置为 100 年。在 Name 字段中输入任何名称。如果您计划使用此密钥库发布任何应用程序,您应该使用您的真实信息。
输入您的 apk 文件的文件名。
单击“完成”。
要将 apk 文件安装到手机上,请使用 android-sdk\platform-tools 文件夹中的 adb 工具。如果您不知道文件夹位置,只需在计算机上搜索 adb.exe。
要安装 apk 文件,请使用此命令行
adb install C:\TiltBall.apk
安装完成后,TiltBall
应在您的手机应用程序列表中可用。
关注点
一些值得注意的事情
- 编写教程比看起来要难 :)
- 在我的测试中,每当应用程序进入后台时,
onDestroy
总是会被调用。根据 Android 状态图,onDestroy
仅在操作系统需要释放内存时才会被调用。我对此问题不太清楚。 - 为了使球的运动更逼真,加速度计应该用于设置加速度,而不是速度。这也可以在球碰到屏幕边缘时产生弹跳效果。
- adb 工具还可以用于列出连接到 PC 的 Android 设备。
- 在我的测试中,我使用了运行良好的华为 u8150(Android 2.2)。
- 如果您发现任何问题或有改进本教程的方法,请给我留言。