在本章中,我们将涵盖以下主题:
如今,移动设备充满了传感器,通常包括陀螺仪、磁传感器、重力传感器、压力传感器和/或温度传感器,更不用说触摸屏了。这为与用户互动提供了许多令人兴奋的新选项。通过传感器,您可以确定三维设备位置以及设备本身的使用方式,例如摇动、旋转、倾斜等。甚至触摸屏也提供了许多新的输入方法,从简单的点击到手势和多点触摸。
我们将从探索触摸屏交互开始这一章,从简单的点击和长按开始,然后继续使用SimpleOnGestureListener
类检测常见手势。接下来,我们将使用ScaleGestureDetector
的缩放手势来观察多点触控。
这本书旨在为您自己的应用添加特性和功能提供一个快速指南。因此,它专注于所需的代码。强烈建议您也花一些时间阅读设计指南。
https://www.google.com/design/spec/patterns/gestures.html 谷歌手势设计指南
在本章的后半部分,我们将使用安卓传感器框架来研究安卓系统中的传感器能力。我们将演示如何获取所有可用传感器的列表,以及如何检查特定的传感器。获得传感器后,我们将演示如何设置监听器来读取传感器数据。最后,我们将以如何确定设备方向的演示来结束这一章。
聆听点击和长按事件几乎每个应用都需要识别和响应基本事件,如点击和长按等。这是非常基本的,在大多数食谱中,我们使用了 XML onClick
属性,但是更高级的监听器需要通过代码进行设置。
安卓提供了一个事件监听器接口,用于在某些操作发生时接收单个通知,如下列表所示:
onClick()
:按下视图时调用onLongClick()
:长按视图时调用onFocusChange()
:当用户导航到视图或从视图中导航时调用onKey()
:按下或释放硬件键时调用onTouch()
:触摸事件发生时调用这个食谱将展示如何回应点击事件,以及长按事件。
在 Android Studio 中创建新项目,并将其称为:PressEvents
。使用默认的电话&平板电脑选项,并在提示输入活动类型时选择空活动。
设置接收基本视图事件非常简单。首先我们将创建一个视图;我们将使用一个按钮作为示例,然后在活动的onCreate()
方法中设置事件监听器。以下是步骤:
打开activity_main.xml
,用以下Button
:
java
替换现有的TextView
2. 现在打开MainActivy.java
和在现有的onCreate()
方法中添加以下代码:
java
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Click", Toast.LENGTH_SHORT).show();
}
});
button.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast.makeText(MainActivity.this, "Long Press", Toast.LENGTH_SHORT).show();
return true;
}
});
在设备或模拟器上运行应用,并尝试定期点击和长按。
在本书使用的大多数示例中,我们使用以下属性在 XML 中设置了onClick
侦听器:
android:onClick=""
您可能会注意到,XML onClick()
方法回调需要与setOnClickListener
相同的方法签名。onClick()
回调:
public void onClick(View v) {}
这是因为当我们使用 XML onClick
属性时,安卓会自动为我们设置回调。这个例子还演示了我们可以在一个视图上有多个监听器。
注意的最后一点是onLongClick()
方法返回一个布尔值,其他大多数事件侦听器也是如此。返回true
表示事件已经处理。
虽然按钮通常用于指示用户应该“按”的位置,但是我们可以在任何视图中同时使用setOnClickListener()
和setOnLongClickListener()
,甚至是TextView
。
正如介绍中提到的,还有其他事件侦听器。你可以使用安卓工作室的自动完成功能。首先键入以下命令:
button.setOn
然后按 Ctrl + 空格键查看列表。
识别敲击和其他常见手势与前面介绍的事件监听器不同,手势需要两个步骤:
当用户触摸屏幕时,第一步开始,用发送到MotionEvent
对象中的移动数据触发onTouchEvent()
回调。幸运的是,安卓通过检测以下手势的GestureDetector
类使分析数据的第二步变得更加容易:
onTouchEvent()
onDown()
onFling()
onLongPress()
onScroll()
onShowPress()
onDoubleTap()
onDoubleTapEvent()
onSingleTapConfirmed()
本食谱将演示使用GestureDetector.SimpleOnGestureListener
识别触摸和双击手势。
在 Android Studio 中创建新项目,并将其称为:CommonGestureDetector
。使用默认的电话&平板电脑选项,并在提示输入活动类型时选择空活动。
我们将使用活动本身来检测手势,因此我们不需要向布局添加任何视图。打开MainActivity.java
并按照以下步骤操作:
添加以下全局变量:
java
private GestureDetectorCompat mGestureDetector;
在MainActivity
类中增加以下GestureListener
类:
java
private class GestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Toast.makeText(MainActivity.this, "onSingleTapConfirmed", Toast.LENGTH_SHORT).show();
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(MainActivity.this, "onDoubleTap", Toast.LENGTH_SHORT).show();
return super.onDoubleTap(e);
}
}
覆盖onTouchEvent()
,如下所示:
java
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
最后,在onCreate()
中添加以下一行代码:
java
mGestureDetector = new GestureDetectorCompat(this, new GestureListener());
在设备或模拟器上运行此应用。
我们正在使用 GestureDetectorCompat
,它来自支持库,允许在运行安卓 1.6 及更高版本的设备上支持手势。
正如食谱介绍中提到的,检测手势是一个两步的过程。为了收集运动或手势数据,我们开始用触摸事件跟踪运动。每次调用onTouchEvent()
时,我们都会将该数据发送到GestureDetector
。GestureDetector
处理第二步,分析数据。一旦检测到手势,就会进行适当的回调。我们的示例处理单次和双击手势。
您的应用只需覆盖适当的回调,就可以轻松添加对GestureDetector
检测到的其余手势的支持。
之前的配方使用和SimpleOnGestureListener
来检测简单的单指手势。在本食谱中,我们将使用SimpleOnScaleGestureListener
类演示常见的从捏到缩放手势的多点触摸。
下面的屏幕截图显示了使用以下配方中创建的应用缩小的图标:
以下截图显示了放大后的图标:
在 Android Studio 中创建新项目,并将其称为:MultiTouchZoom
。使用默认的电话&平板电脑选项,并在提示输入活动类型时选择空活动。
为了提供缩放的视觉指示,我们将使用带有应用图标的ImageView
。打开activity_main.xml
并按照以下步骤操作:
将现有的TextView
替换为以下ImageView
:
java
android:layout_
android:layout_
android:src="@mipmap/ic_launcher"
android:layout_centerVertical="true"
android:layout_centerHorizOntal="true" />
现在打开MainActivity.java
并将以下全局变量添加到类中:
java
private ScaleGestureDetector mScaleGestureDetector;
private float mScaleFactor = 1.0f;
private ImageView mImageView;
覆盖onTouchEvent()
,如下所示:
java
public boolean onTouchEvent(MotionEvent motionEvent) {
mScaleGestureDetector.onTouchEvent(motionEvent);
return true;
}
将ScaleListener
类之后的类添加到MainActivity
类中:
java
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
mScaleFactor *= scaleGestureDetector.getScaleFactor();
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
mImageView.setScaleX(mScaleFactor);
mImageView.setScaleY(mScaleFactor);
return true;
}
}
将以下代码添加到现有的onCreate()
方法中:
java
mImageView=(ImageView)findViewById(R.id.imageView);
mScaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());
要尝试缩放功能,请在带有触摸屏的设备上运行该应用。
ScaleGestureDetector
通过分析手势数据并通过onScale()
回调报告最终比例因子来完成所有工作。我们通过调用ScaleGestureDetector
上的getScaleFactor()
获得实际比例因子。
我们使用带有应用图标的ImageView
通过使用从ScaleGestureDetector
返回的比例因子设置ImageView
比例来提供比例的可视化表示。为了防止缩放变得过大或过小,我们添加了以下检查:
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
下拉列表以指示手动刷新被称为滑动刷新手势。这是一个如此常见的特性,以至于这个功能被封装在一个名为SwipeRefreshLayout
的小部件中。
本食谱将展示如何使用小部件添加带有ListView
的滑动刷新功能。以下屏幕截图显示了正在进行的刷新:
在 Android Studio 中创建新项目,并将其称为:SwipeToRefresh
。使用默认的电话&平板电脑选项,并在提示输入活动类型时选择空活动。
首先,我们需要将SwipeRefreshLayout
小部件和ListView
添加到活动布局中,然后我们将在 java 代码中实现刷新监听器。以下是详细的步骤:
打开activity_main.xml
,将现有的
替换为:
java
android:id="@+id/swipeRefresh"
android:layout_
android:layout_>
android:layout_
android:layout_ />
现在打开MainActivity.java
并将以下全局变量添加到类中:
java
SwipeRefreshLayout mSwipeRefreshLayout;
ListView mListView;
List mArrayList = new ArrayList<>();
private int mRefreshCount=0;
添加以下方法来处理刷新:
java
private void refreshList() {
mRefreshCount++;
mArrayList.add("Refresh: " + mRefreshCount);
ListAdapter countryAdapter = new ArrayAdapter
mListView.setAdapter(countryAdapter);
mSwipeRefreshLayout.setRefreshing(false);
}
将以下代码添加到现有的onCreate()
方法中:
```java
mSwipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipeRefresh);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshList();
}
});
mListView = (ListView)findViewById(android.R.id.list);
final String[] countries = new String[]{"China", "France", "Germany", "India", "Russia", "United Kingdom", "United States"};
mArrayList = new ArrayList(Arrays.asList(countries));
ListAdapter countryAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, mArrayList);
mListView.setAdapter(countryAdapter);
```
在设备或模拟器上运行应用。
该配方的大部分代码是通过在每次调用刷新方法时向ListView
添加项目来模拟刷新。实施“刷到刷新”的主要步骤包括:
SwipeRefreshLayout
小部件。ListView
包含在SwipeRefreshLayout
中。OnRefreshListener
来调用你的刷新方法。setRefreshing(false)
。就这样。这个小部件使得添加滑动刷新变得非常容易!
虽然滑动刷新手势是当今应用的常见功能,但包含菜单项仍然是一种很好的做法(尤其是出于辅助功能的原因)。下面是菜单布局的 XML 片段:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/menu_refresh"
android:showAsAction="never"
android:title="@string/menu_refresh"/>
menu>
在onOptionsItemSelected()
回调中调用你的刷新方法。当从代码执行刷新时,例如从菜单项事件,您希望通知SwipeRefreshLayout
刷新,以便它可以更新用户界面。使用以下代码执行此操作:
SwipeRefreshLayout.setRefreshing(true);
这告诉SwipeRefreshLayout
刷新正在开始,因此它可以显示进行中的指示器。
安卓包括使用安卓传感器框架对硬件传感器的支持。该框架包括以下类和接口:
SensorManager
Sensor
SensorEventListener
SensorEvent
大多数安卓设备都包含硬件传感器,但不同制造商和型号之间的差异很大。如果您的应用使用传感器,您有两种选择:
要指定您的应用使用传感器,请在安卓清单中包含
声明。下面是一个需要指南针的例子:
<uses-feature android:name="android.hardware.sensor.compass" android:required="true"/>
如果你的应用利用了指南针,但不要求它起作用,你应该设置android:required="false"
来代替,否则应用将无法通过 Google Play 使用。
传感器分为以下三类:
安卓软件开发工具包支持以下传感器类型:
|
传感器
|
发现
|
使用
|
| --- | --- | --- |
| TYPE_ACCELEROMETER
| 包括重力在内的运动检测 | 用于确定抖动、倾斜等 |
| TYPE_AMBIENT_TEMPERATURE
| 测量室温 | 用于确定局部温度 |
| TYPE_GRAVITY
| 测量三个轴上的重力 | 用于运动检测 |
| TYPE_GYROSCOPE
| 测量所有三个轴上的旋转 | 用于确定转弯、旋转等 |
| TYPE_LIGHT
| 测量光照水平 | 用于设置屏幕亮度 |
| TYPE_LINEAR_ACCELERATION
| 不含重力的运动检测 | 用于确定加速度 |
| TYPE_MAGNETIC_FIELD
| 测量地磁场 | 用于创建罗盘或确定方位 |
| TYPE_PRESSURE
| 测量气压 | 用于气压计 |
| TYPE_PROXIMITY
| 相对于屏幕测量对象 | 用于确定在打电话时设备是否贴着耳朵 |
| TYPE_RELATIVE_HUMIDITY
| 测量相对湿度 | 用于确定露点和湿度 |
| TYPE_ROTATION_VECTOR
| 测量设备方向 | 用于检测运动和旋转 |
有两个额外的传感器:TYPE_ORIENTATION
和TYPE_TEMPERATURE
,由于被更新的传感器取代,它们已经被弃用。
该配方将演示检索可用传感器的列表。下面是一个物理设备的截图:
在 Android Studio 中创建新项目,并将其称为:ListDeviceSensors
。使用默认的电话&平板电脑选项,并在提示输入活动类型时选择空活动。
首先,我们将查询可用传感器的列表,然后在ListView
中显示结果。以下是详细的步骤:
打开activity_main.xml
,将现有的TextView
替换为:
java
android:layout_
android:layout_ />
接下来,打开ActivityMain.java
,将以下代码添加到现有的onCreate()
方法中:
```java
ListView listView = (ListView)findViewById(R.id.list);
List sensorList = new ArrayList();
List sensors = ((SensorManager) getSystemService(Context.SENSOR_SERVICE)).getSensorList(Sensor.TYPE_ALL);
for (Sensor sensor : sensors ) {
sensorList.add(sensor.getName());
}
ListAdapter sensorAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, sensorList);
listView.setAdapter(sensorAdapter);
```
在设备或模拟器上运行程序。
以下行代码负责获取可用传感器列表;剩下的代码是填充ListView
:
List<Sensor> sensors = ((SensorManager) getSystemService(Context.SENSOR_SERVICE)).getSensorList(Sensor.TYPE_ALL);
请注意,我们得到了一个Sensor
对象的列表。我们只让传感器名称显示在ListView
中,但也有其他属性可用。完整列表请参见部分提供的链接。
如 Nexus 9 的介绍截图所示,一个设备可以有多个相同类型的传感器。如果您正在寻找一个特定的传感器,您可以从介绍中显示的表格中输入一个常数。在这种情况下,如果您想查看所有可用的加速度计传感器,您可以使用以下呼叫:
List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
如果您不是在查找传感器列表,而是需要使用特定的传感器,您可以使用以下代码检查默认传感器:
if (sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null){
//Sensor is available - do something here
}
之前的食谱列出了可用的传感器——安卓传感器框架的介绍,提供了安卓传感器框架的介绍。现在我们来看看使用SensorEventListener
读取传感器数据。SensorEventListener
界面只有两个回调:
onSensorChanged
()onAccuracyChanged
()当传感器有新数据要报告时,它用一个SensorEvent
对象调用onSensorChanged()
。本食谱将演示如何读取光传感器,但由于所有传感器都使用相同的框架,因此很容易将此示例应用于任何其他传感器。(参见前面配方介绍中可用的传感器类型列表。)
在 Android Studio 中创建新项目,并将其称为:ReadingSensorData
。使用默认的电话&平板电脑选项,并在提示输入活动类型时选择空活动。
我们将在活动布局中添加一个TextView
来显示传感器数据,然后将SensorEventListener
添加到 java 代码中。我们将使用onResume()
和onPause()
事件来启动和停止我们的事件监听器。首先,打开activity_main.xml
并按照以下步骤操作:
修改现有的TextView
如下:
java
android:layout_
android:layout_
android:layout_centerHorizOntal="true"
android:layout_centerVertical="true"
android:text="0"/>
现在打开MainActivity.java
并添加以下全局变量声明:
java
private SensorManager mSensorManager;
private Sensor mSensor;
private TextView mTextView;
将类添加到MainActivity
类中,如下所示:
java
private SensorEventListener mSensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
mTextView.setText(String.valueOf(event.values[0]));
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
//Nothing to do
}
};
我们将在onResume()
和onPause()
中注册和注销传感器事件,如下所示:
```java
@Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(mSensorListener, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(mSensorListener);
}
```
将以下代码添加到onCreate()
:
java
mTextView = (TextView)findViewById(R.id.textView);
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
现在,您可以在物理设备上运行应用,查看来自光传感器的原始数据。
使用安卓传感器框架从获取传感器开始,这是我们在onCreate()
中做的。在此,我们致电getDefaultSensor(),
请求TYPE_LIGHT
。我们在onResume()
注册监听器,在onPause()
重新注册,减少电池消耗。当我们调用registerListener()
时,我们会传入我们的mSensorListener
对象。
在我们的例子中,我们只寻找传感器数据,该数据在onSensorChanged()
回调中发送。当传感器发生变化时,我们用传感器数据更新TextView
。
现在您已经使用了一个传感器,您知道如何使用所有传感器,因为它们都使用相同的框架。当然,你对数据的处理会有很大的不同,这取决于你正在阅读的数据类型。如图所示,环境传感器返回单个值,但是位置和运动传感器也可以返回附加元素,如下所示。
安卓支持以下四种环境传感器:
环境传感器通常更容易操作,因为返回的数据在单个元素中,通常不需要校准或过滤。我们在本演示中使用了光传感器,因为大多数设备都包括一个光传感器来控制屏幕亮度。
位置传感器包括:
以下传感器类型使用地磁场:
TYPE_GAME_ROTATION_VECTOR
TYPE_GEOMAGNETIC_ROTATION_VECTOR
TYPE_MAGNETIC_FIELD
TYPE_MAGNETIC_FIELD_UNCALIBRATED
这些传感器在onSensorChanged()
事件中返回三个值,除了发送六个值的TYPE_MAGNETIC_FIELD_UNCALIBRATED
。
第三个传感器,方位传感器,已经被弃用,现在建议使用getRotation()
和getRotationMatrix()
来计算方位变化。(关于设备方向,如纵向和横向模式,见下一个食谱:读取设备方向。)
运动传感器包括以下内容:
这些包括以下传感器类型:
TYPE_ACCELEROMETE
TYPE_GRAVITY
TYPE_GYROSCOPE
TYPE_GYROSCOPE_UNCALIBRATED
TYPE_LINEAR_ACCELERATION
TYPE_ROTATION_VECTOR
TYPE_SIGNIFICANT_MOTION
TYPE_STEP_COUNTER
TYPE_STEP_DETECTOR
除了最后三个,这些传感器还包括三个数据元素。TYPE_SIGNIFICANT_MOTION
和TYPE_STEP_DETECTOR
表示一个事件,而TYPE_STEP_COUNTER
返回自上次引导以来的步数(当传感器激活时)。
虽然安卓框架会在方向改变时自动加载新的资源(比如布局),但有时你可能希望禁用这种行为。如果您希望收到方向更改的通知,而不是安卓自动处理,请在安卓清单的活动中添加以下属性:
android:configChanges="keyboardHidden|orientation|screenSize"
当出现以下配置变化时,系统会通过onConfigurationChanged()
方式通知您,而不是自动处理:
keyboardHidden
orientation
screenSize
onConfigurationChanged()
签名如下:
onConfigurationChanged (Configuration newConfig)
你会在newConfig.orientation
找到新的方位。
禁用自动配置更改(这将导致重新加载布局并重置状态信息)不应用作正确保存状态信息的替代。您的应用仍然可以在任何时候被中断或完全停止,并被系统终止。(参见第一章、活动中保存活动状态,正确保存状态。)
该配方将演示如何确定当前设备方向。
在 Android Studio 中创建新项目,并将其称为:GetDeviceOrientation
。使用默认的电话&平板电脑选项,并在提示输入活动类型时选择空活动。
我们将在布局中添加一个按钮,根据需要检查方向。首先打开activity_main.xml
,按照以下步骤操作:
将现有的TextView
替换为以下Button
:
java
添加以下方法处理按钮点击:
java
public void checkOrientation(View view){
int orientation = getResources().getConfiguration().orientation;
switch (orientation) {
case Configuration.ORIENTATION_LANDSCAPE:
Toast.makeText(MainActivity.this, "ORIENTATION_LANDSCAPE", Toast.LENGTH_SHORT).show();
break;
case Configuration.ORIENTATION_PORTRAIT:
Toast.makeText(MainActivity.this, "ORIENTATION_PORTRAIT", Toast.LENGTH_SHORT).show();
break;
case Configuration.ORIENTATION_UNDEFINED:
Toast.makeText(MainActivity.this, "ORIENTATION_UNDEFINED", Toast.LENGTH_SHORT).show();
break;
}
}
Run the application on a device or emulator.
使用 Ctrl + F11 旋转模拟器。
我们需要做的就是调用这一行代码来获得当前方向:
getResources().getConfiguration().orientation
方向作为int
返回,我们将其与三个可能的值之一进行比较,如图所示。
您可能需要知道当前方向的另一种情况是在处理相机数据(图片和/或视频)时。通常,可以根据设备方向旋转图像,或者补偿当前方向。在这种情况下,还有另一个选项可以获得循环:
int rotation = getWindowManager().getDefaultDisplay().getRotation();
在前一行代码中,rotation
将是以下值之一:
Surface.ROTATION_0
Surface.ROTATION_90
Surface.ROTATION_180
Surface.ROTATION_270
旋转值将从其正常方向开始。例如,当使用横向正常方向的桌子时,如果照片是纵向拍摄的,则该值将为ROTATION_90
或ROTATION_270
。