《移动互联网技术》第九章 感知与多媒体: 了解质感设计的基本原则和设计方法

news2024/10/7 19:31:01

在这里插入图片描述

🌷🍁 博主 libin9iOak带您 Go to New World.✨🍁
🦄 个人主页——libin9iOak的博客🎐
🐳 《面试题大全》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺
🌊 《IDEA开发秘籍》学会IDEA常用操作,工作效率翻倍~💐
🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🍁🐥

文章目录

  • 《移动互联网技术》课程简介
  • 第九章 感知与多媒体
    • 本章小结:
      • **1****、本单元学习目的**
      • **2****、本单元学习要求**
      • **3****、本单元学习方法**
      • **4****、本单元重点难点分析**
    • **重点**
      • **(1)** **传感器**
      • **(2)** **GPS****定位和位置服务**
      • **(3)** **视频播放**
    • **难点**
      • **(1)** **摄像头拍照**
      • **(2)** **音乐播放器**
      • **(****3****)质感界面设计**
    • 本章习题:
    • 参考资源:
  • 原创声明

《移动互联网技术》课程简介

《移动互联网技术》课程是软件工程、电子信息等专业的专业课,主要介绍移动互联网系统及应用开发技术。课程内容主要包括移动互联网概述、无线网络技术、无线定位技术、Android应用开发和移动应用项目实践等五个部分。移动互联网概述主要介绍移动互联网的概况和发展,以及移动计算的特点。无线网络技术部分主要介绍移动通信网络(包括2G/3G/4G/5G技术)、无线传感器网络、Ad hoc网络、各种移动通信协议,以及移动IP技术。无线定位技术部分主要介绍无线定位的基本原理、定位方法、定位业务、数据采集等相关技术。Android应用开发部分主要介绍移动应用的开发环境、应用开发框架和各种功能组件以及常用的开发工具。移动应用项目实践部分主要介绍移动应用开发过程、移动应用客户端开发、以及应用开发实例。
课程的教学培养目标如下:
1.培养学生综合运用多门课程知识以解决工程领域问题的能力,能够理解各种移动通信方法,完成移动定位算法的设计。
2.培养学生移动应用编程能力,能够编写Andorid应用的主要功能模块,并掌握移动应用的开发流程。
3. 培养工程实践能力和创新能力。
 通过本课程的学习应达到以下目的:
1.掌握移动互联网的基本概念和原理;
2.掌握移动应用系统的设计原则;
3.掌握Android应用软件的基本编程方法;
4.能正确使用常用的移动应用开发工具和测试工具。

第九章 感知与多媒体

本章小结:

1**、本单元学习目的**

通过学习如何使用移动设备的各种传感器和硬件设备来获取环境信息,掌握如何使用GPS实现定位功能,音视频播放功能,摄像头拍照功能;掌握界面设计原则、用户体验设计和质感设计。

2**、本单元学习要求**

(1) 掌握各种感知处理方法;

(2) 了解质感设计的基本原则和设计方法,并且通过不断的实践从复杂的事务中提炼出简洁、舒适的设计。

3**、本单元学习方法**

结合教材以及Android Studio开发软件,对传感器、摄像头、蓝牙等模块进行编程练习,运行调试,并在模拟器中观察运行情况。

4**、本单元重点难点分析**

重点

(1) 传感器

Android系统中包括两类传感器,分别是物理传感器和虚拟传感器。物理传感器可以直接采集各种物理特性,包括温度计、气压计、光传感器、心率计、加速度计、陀螺仪、指南针等等。虚拟传感器根据物理传感器采集的数据,通过融合算法计算出各种特性,比如:旋转矢量、重力、线性加速度等等。手机上的计步器也是一种虚拟传感器,它可以根据加速度计计算步数。另外,按照传感器的用途,可划分为:运动传感器、环境传感器和位置传感器。运动传感器测量加速度以及沿三个轴的旋转速度,包括加速度计,重力感应器,陀螺仪等等。环境传感器测量各种环境参数,例如:空气温度、照明等,包括气压计、光传感器、温度计等。位置传感器测量设备的物理位置,包括:GPS、方向传感器和磁力计等。

传感器的数据采集有不同的方式:第一、可以持续不断的采集数据,通常实时的连续获取数据常用于加速度计、陀螺仪等传感器;第二、在一段时间内,当传感器数据发生变化时采集数据,比如:心率计和计步器;第三、当传感器检测到某种特定事件时,开始采集数据,比如:红外传感器检测到人靠近时会触发相应的事件;第四、某些特定需求的数据采集。

Android通过SensorManager来管理传感器。首先创建SensorActivity,在onCreate函数中使用Context.getSystemService(String)来获取SensorManager。

public class SensorActivity extends AppCompatActivity {
public static String TAG = “MainTAG”;
private SensorManager sensorManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sensor);

manager=(SensorManager) getSystemService(SENSOR_SERVICE);
sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);

List sensorList;
// 获取设备支持的所有传感器
sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
List sensorNameList = new ArrayList();

for (Sensor sensor : sensorList) {
Log.d(TAG, "传感器: " + sensor.getName());
}

}

}

如果要使用特定的传感器,需要从SensorManager中获取指定类型的传感器。使用SensorManager.getDefaultSensor(int type)得到指定的Sensor。type参数用来指定要获取的传感器类型,比如:

Sensor.TYPE_ORIENTATION:方向传感器;

Sensor.TYPE_ACCELEROMETER:重力传感器;

Sensor.TYPE_LIGHT:光线传感器;

Sensor.TYPE_MAGNETIC_FIELD:磁场传感器。

当外部环境发生变化时,Android系统首先通过传感器获取外部环境数据,然后将数据传递给监听器的监听回调函数。为了采集传感器数据,通过SensorManager为Sensor添加监听器。在使用完后,还要注销监听器。

sensorManager.registerListener(sensorListener,

sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),

SensorManager.SENSOR_DELAY_NORMAL);

第一个参数是监听器listener,第二个参数是传感器sensor,第三个参数是传感器的采样率rateUs,表示从传感器获取值的频率,它包括以下几个选项:

SensorManager.SENSOR_DELAY_FASTEST:最快,延迟最小;

SensorManager.SENSOR_DELAY_GAME:适合游戏的频率;

SensorManager.SENSOR_DELAY_NORMAL:正常频率;

SensorManager.SENSOR_DELAY_UI:最慢,适合界面UI变化的频率。

采样频率根据实际应用的需要来确定说。通常采用SENSOR_DELAY_NORMAL 或SENSOR_DELAY_GAME。如果采用更高的采样率,将耗费更多的资源,包括电量、CPU等。

接下来实现监听器。

private SensorEventListener sensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x_lateral = sensorEvent.values[0];
float y_longitudinal = sensorEvent.values[1];
float z_vertical = sensorEvent.values[2];

   String info = "加速度传感器xyz轴的加速度分别为:\n" + x_lateral +
       "\n" + y_longitudinal + "\n" + z_vertical + "\n";

   sView.setText(info);
 }

}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}

};

当传感器采集的值发生变化时,触发调用函数onSensorChanged(SensorEvent event);当传感器精度发生变化时,触发调用函数onAccuracyChanged(Sensor sensor,int accuracy)。

关闭应用后,传感器的监听器不会自动释放资源,因此需要开发人员在适当的时候注销监听器。

@Override
protected void onStop() {
sensorManager.unregisterListener(sensorListener);
super.onStop();
}

(2) GPS****定位和位置服务

下面利用移动设备的GPS芯片来定位经纬度坐标。在界面上用TextView控件显示定位的经纬度信息。

<TextView

android:id=“@+id/location_text_view”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:textSize=“28sp”/>

使用设备的定位功能需要授予权限,考虑一下是使用动态授权还是静态授权?静态权限如下:

注意:Andriod 6.0以后要使用动态权限。如果已经授权,就直接调用定位程序。

int checkPermission = ContextCompat.checkSelfPermission(LocationActivity.this,

Manifest.permission.ACCESS_FINE_LOCATION);

if (checkPermission != PackageManager.PERMISSION_GRANTED) {

ActivityCompat.requestPermissions(LocationActivity.this, new String[]{

Manifest.permission.ACCESS_FINE_LOCATION }, 1);

} else {

position();

}

通过getSystemService得到位置管理器对象。调用LocationManager的getProviders 函数获取所有可用的位置提供器,然后判断GPS是否打开,如果无法使用GPS,则看看是否能通过网络来定位。

locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

List providerList = locationManager.getProviders(true);

if (providerList.contains(LocationManager.GPS_PROVIDER)) {

provider = LocationManager.GPS_PROVIDER;

} else if (providerList.contains(LocationManager.NETWORK_PROVIDER)) {

provider = locationManager.NETWORK_PROVIDER;

} else {

Toast.makeText(this, “无法提供位置信息”, Toast.LENGTH_SHORT).show();

return;

}

通过LocationManager的getLastKnownLocation函数获得最近的位置信息,同时在界面上更新当前的位置。虽然获取了当前的位置信息,但是用户可能会随时移动,怎样才能在位置改变的时候获取最新的位置信息呢?LocationManager 提供了请求定位更新函数requestLocationUpdates,它的第二个参数表示监听位置变化的时间间隔;第三个参数表示监听位置变化的距离间隔;第四个参数是位置监听器对象。

Location location = locationManager.getLastKnownLocation(provider);

if (location != null) {

​ updateLocation(location);

}

locationManager.requestLocationUpdates(provider, 1000, 1, locationListener);

其中,“1000”表示监听位置变化的时间间隔以毫秒为单位,“1”表示监听位置变化的距离间隔以米为单位。

位置更新代码是在界面上显示经纬度信息。

private void updateLocation(Location location) {

if (location == null) {

​ return;

}

String currentPosition = “GPS 定位:” + “\n”

​ + " 经度: " + location.getLongitude() + “\n”

​ + " 纬度: " + location.getLatitude();

locationTextView.setText(currentPosition);

}

创建位置监听器,监听位置的变化,一旦监听时间间隔和距离间隔发生改变就调用updateLocation函数,来更新位置。

LocationListener locationListener = new LocationListener() {

@Override

public void onStatusChanged(String provider, int status, Bundle extras) { }

@Override

public void onProviderEnabled(String provider) { }

@Override

public void onProviderDisabled(String provider) { }

@Override

public void onLocationChanged(Location location) {

​ updateLocation(location);

}

};

注意:在销毁函数中,要移除位置监听器。

@Override

protected void onDestroy() {

super.onDestroy();

if (locationManager != null) {

​ locationManager.removeUpdates(locationListener);

}

}

通过GPS确定经纬度以后,还需要结合电子地图才能知道自己当前所在的位置。很多电子地图软件提供了定位和导航功能,比如百度地图就提供了Android定位的SDK库。通常第三方的定位库还提供基站、WiFi、地磁、蓝牙、传感器等多种定位方式,适用于室内、室外等多种定位场景;并且它们都有出色的定位性能,具有定位精度高、覆盖范围广、定位流量小、定位速度快等特点。如果要使用第三方定位服务,还需要申请定位API Key,很多公司提供了定位API的接口说明,可以直接在网上查阅相关的资料。

在MapActivity中,放置多个控件显示当前位置的经度和纬度,可以选择手工定位和GPS定位,设置目标地以后,点击按钮可以实现路径规划功能,地图上是一个切换按钮可以切换显示普通地图和卫星地图。在应用中,使用高德地图实现位置服务功能。

首先要去高德开放平台注册成为开发者(http://lbs.amap.com/), 注册成为高德开发者需要分三步:第一步,注册高德开发者;第二步,去控制台创建应用;第三步,获取使用API函数的Key。

MapActivity实现OnClickListener监听器,用来处理按钮的点击事件,OnGeocodeSearchListener是地理编码搜索监听器,OnRouteSearchListener是路由搜索监听器,它们用来定位和路径规划;接下来,定义位置管理器等多个对象。

public class MapActivity extends AppCompatActivity
implements View.OnClickListener,
OnGeocodeSearchListener, OnRouteSearchListener {

   private MapView mapView;
   private AMap aMap;
   private LocationManager locationManager;

   *//* *导航**

* private Button btnNavigation;
private GeocodeSearch geocodeSearch;
private RouteSearch routeSearch;
private EditText editTextAddress;
private String province = “四川”;

@Override
protected void onCreate(Bundle savedInstanceState) {

​ … …
​ mapView = findViewById(R.id.map);
// *必须回调MapView的**onCreate()方法
* mapView.onCreate(savedInstanceState);
​ init();

​ ToggleButton tb = findViewById(R.id.tb);
​ tb.setOnCheckedChangeListener(

new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,

​ boolean isChecked) {
​ if (isChecked) { *
* aMap.setMapType(AMap.MAP_TYPE_SATELLITE);
​ } else {
​ aMap.setMapType(AMap.MAP_TYPE_NORMAL);

} } });

onCreate函数,获取定位管理器,为GPS单选按钮设置监听器,如果RadioButton选择GPS定位,则通过监听器监听GPS提供的定位信息的改变。requestLocationUpdates函数的第一个参数是定位方式,第二个参数是定位更新的最小时间间隔(毫秒),第三个参数是定位更新的最小距离(米),第四个参数是定位监听器,接下来实现监听代码。

locationManager = (LocationManager) getSystemService(
Context.LOCATION_SERVICE);

RadioButton rb = findViewById(R.id.radio_btn_gps);*
* rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView
, boolean isChecked) {
*
* if (isChecked) { *
* locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
300, 8, new LocationListener() {

@Override
public void onLocationChanged(Location loc) { *
* updatePosition(loc);
}

@Override
public void onStatusChanged(String provider, int status, Bundle extras) { }

@Override
public void onProviderEnabled(String provider) {*

* updatePosition(locationManager.getLastKnownLocation(provider));
}

@Override
public void onProviderDisabled(String provider) { }

});

当位置改变时,将触发onLocationChanged函数,调用updatePosition函数,根据GPS提供的定位信息来更新位置

(3) 视频播放

在Android系统中,有三种实现视频播放的方式:(1)使用系统自带的播放器,并且将intent的action指定为ACTION_VIEW,Data指定为Uri,Type指定为媒体的MIME类型。(2)使用VideoView控件来播放视频。在布局文件中设置VideoView控件,然后编写视频播放控制函数来控制播放。(3)使用系统的MediaPlayer类和SurfaceView控件来播放视频。

下面用VideoView控件来实现一个简易的视频播放器。首先,创建视频播放界面的布局文件,视频播放要用到VideoView控件。注意:读写文件要申请授权。

<VideoView

android:id=“@+id/video_view”

android:layout_width=“match_parent”

android:layout_height=“wrap_content” />

<LinearLayout

<Button

​ android:text=“播放”

​ android:onClick=“play”/>

可以在SD卡的根目录下存放要播放的视频文件。因为视频文件存放在SD卡上,在MediaActivity中,要用getExternalStorageDirectory获取外部存储目录。注意设置访问权限:

如果需要设置动态权限,需要在MediaActivity中编写运行时权限检查代码:

int checkPermission = ContextCompat.checkSelfPermission(MediaActivity.this,

​ Manifest.permission.WRITE_EXTERNAL_STORAGE);

if (checkPermission != PackageManager.PERMISSION_GRANTED) {

ActivityCompat.requestPermissions(MediaActivity.this, new String[]{

​ Manifest.permission.WRITE_EXTERNAL_STORAGE }, 1);

} else {

// 通过视频文件名(绝对路径)创建一个文件

File file = new File(Environment.getExternalStorageDirectory(), “Androidstudio.3gp”);

}

videoView.setVideoPath(file.getPath());

播放功能很简单,在三个函数中分别调用控件的开始、暂停和恢复功能。

public void play(View view) {

if (!videoView.isPlaying()) {

​ videoView.start();

}

}

public void pause(View view) {

if (videoView.isPlaying()) {

​ videoView.pause();

}

}

public void resume(View view) {

if (videoView.isPlaying()) {

​ videoView.resume();

}

}

难点

(1) 摄像头拍照

Android智能手机都会提供照相功能,大部分手机的摄像头都会支持光学变焦、曝光以及快门等功能。下面通过摄像头实现拍照功能,并将拍摄的相片显示在界面上。首先定义拍照方法,在启动拍照之前先判断内存是否可用;然后通过重写onActivityResult()方法,获取拍好的图片。

创建一个拍照的界面。包括一个按钮和一个图片视图。

<LinearLayout

<Button

​ android:id=“@+id/take_picture”

​ android:text=“拍照”

​ android:textSize=“28sp”/>

<ImageView

​ android:id=“@+id/picture”

/>

在拍照之前,先创建照片的存储文件。在缓存目录中存储。

takePicture.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

​ File savePicture = new File(getExternalCacheDir(), “MyPicture.jpg”);

​ try {

​ if (savePicture.exists()) {

​ savePicture.delete();

​ }

​ savePicture.createNewFile();

​ } catch (IOException e) {

​ e.printStackTrace();

​ }

}

如果SDK的版本是Android 7.0以上,调用FileProvider的getUriForFile 函数将File对象转换成一个封装的Uri 对象。FileProvider的第二个参数是一个唯一性字符串,第三个参数是刚刚创建的用来存储照片的文件对象。

如果版本低于Android 7.0,调用Uri的fromFile 函数将直接将文件对象转成Uri对象,Uri指示照片的本地路径。然后用Intent启动摄像头,拍照的action为:android.media.action.IMAGE_CAPTURE。把拍照后的输出地址也存入Intent,然后打开拍摄界面。用户在拍完照片后,会把照片输出到指定的MyPicture.jpg中。

if (Build.VERSION.SDK_INT >= 24) {

picUri = FileProvider.getUriForFile(CameraActivity.this,

​ “pers.cnzdy.tutorial.fileprovider”, savePicture);

} else {

picUri = Uri.fromFile(savePicture);

}

Intent Intent = new Intent(“android.media.action.IMAGE_CAPTURE”);

Intent.putExtra(MediaStore.EXTRA_OUTPUT, picUri);

startActivityForResult(Intent, TAKE_PICTURE);

拍照完成以后,结果将返回onActivityResult函数。如果成功(resultCode = RESULT_OK),就解析出图片,显示在界面上。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case TAKE_PICTURE:
if (resultCode == RESULT_OK) {
try {
Bitmap bitmap = BitmapFactory.decodeStream(
getContentResolver().openInputStream(picUri));
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}

FileProvider是 Android 7.0 新增的一个类,继承自ContentProvider,因此需要在配置文件AndroidManifest中进行注册。注意android:authorities的属性值和调用FileProvider的getUriForFile函数的第二个参数要一致。

<provider

android:name=“android.support.v4.content.FileProvider”

android:authorities=“pers.cnzdy.tutorial.fileprovider”

android:exported=“false”

android:grantUriPermissions=“true”>

<meta-data

​ android:name=“android.support.FILE_PROVIDER_PATHS”

​ android:resource=“@xml/file_provider_paths” />

用来指定Uri的共享路径,file_provider_paths是一个xml文件,需要创建。在res目录下,创建一个xml目录,然后在目录上点击鼠标右键,选择New—File,创建file_provider_paths.xml文件,xml文件的内容如下:

<?xml version="1.0" encoding="utf-8"?>

​ ](http://schemas.android.com/apk/res/android)

file_provider_paths.xml文件的标签用来指定Uri共享,name属性设定为“my_pictures”,path属性表示共享的路径位置,设置空值就表示将整个SD卡进行共享,也可以设置为只共享存放MyPicture.jpg这张照片的路径。

(2) 音乐播放器

在Android系统中,提供了多种播放音频的方式,包括:SoundPool、MediaPlayer、AudioTrack、Ringtone等等。

SoundPool用于管理和播放应用程序的音频资源,主要用于播放时间短,延迟小的声音。它支持多个音频文件同时播放,占用的资源较少,适合播放按键音、消息提示音等短促音效的场景。

MediaPlayer是Android内置的多媒体播放类,在android.media.MediaPlayer包中,它包含了音频和视频播放功能。MediaPlayer适用于播放时间较长,延迟要求不高,能全面控制和操作播放过程的情况。MediaPlayer能播放多种格式的声音文件,比如MP3、AAC、WAV、OGG、MIDI等等。

AudioTrack实现PCM(Pulse Code Modulation)音频流的回放,是更底层的音频播放方式。AudioTrack支持流式播放,可读取本地和网络音频流。相比于MediaPlayer,它更加高效,适用于实时播放音频的场景,如加密音频播放。AudioTrack只能播放已经解码的PCM流,如果要播放其它格式的音频文件,需要相应的解码器。

AsyncPlayer对MediaPlayer进行封装,提供了异步音频播放功能。由于播放等操作都在新线程中执行,不会阻塞UI线程。AsyncPlayer适用于异步播放,不需要复杂控制。

Ringtone提供铃声、提示音等系统类声音的播放功能。另外,RingtoneManager管理铃声数据库,包括:来电铃声(TYPE_RINGTONE)、提示音(TYPE_NOTIFICATION)、闹钟铃声(TYPE_ALARM)等。通常Ringtone类和RingtoneManager类在一起使用。

下面构造一个音乐播放器,实现音乐播放、上一曲、下一曲、开始/暂停、拖动进度条实现快进和快退等功能。

布局文件activity_music_player.xml采用LinearLayer布局,加入ListView,随后再加入进度条控件SeekBar。

<SeekBar

​ android:id=“@+id/sb”

​ android:layout_width=“match_parent”

​ android:layout_height=“40dp”

​ android:maxHeight=“4dp”

​ android:minHeight=“4dp”

​ android:paddingBottom=“4dp”

​ android:paddingLeft=“14dp”

​ android:max=“240”

​ android:paddingRight=“14dp”

​ android:paddingTop=“4dp” />

接下来添加四个按钮:上一首、开始播放、暂停和下一首。

<Button

android:id=“@+id/btn_last”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:text=“下一首”/>

… … … …

在MusicPlayerActivity类中声明对应的变量,并完成初始化。

public class MusicPlayerActivity extends AppCompatActivity implements Runnable {

boolean firstplay = true;

private Button btnStart, btnStop, btnNext, btnLast;

private ListView listView;

// 显示当前播放音乐的名称、播放的时间、以及歌曲长度

private TextView music_Info;

private SeekBar seekBar;

private MusicPlayerService musicService = new MusicPlayerService();

​ @Override

protected void onCreate(Bundle savedInstanceState) {

​ super.onCreate(savedInstanceState);

​ setContentView(R.layout.activity_music_player);

btnStart = (Button) findViewById(R.id.btn_star);

​ btnStart.setOnClickListener(new View.OnClickListener() {

​ @Override

​ public void onClick(View view) {

​ try {

​ if (firstplay) {

​ musicService.play();

​ firstplay = false;

​ } else {

​ if (!musicService.player.isPlaying()) {

​ musicService.goPlay();

​ } else if (musicService.player.isPlaying()) {

​ musicService.pause();

​ }

​ }

​ } catch (Exception e) {

​ Log.i(“TAG”, “音乐播放异常!”);

​ }

​ }

​ });

btnStop = (Button) findViewById(R.id.btn_stop);

​ btnStop.setOnClickListener(new View.OnClickListener() {

​ @Override

​ public void onClick(View view) {

​ try {

​ musicService.stop();

​ firstplay = true;

​ seekBar.setProgress(0);

​ txtInfo.setText(“暂停已经停止”);

​ } catch (Exception e) {

​ Log.i(“LAT”, “暂停异常!”);

​ }

​ }

​ });

​ … …

}

}

“上一首”和“下一首”功能的代码类似,都是调用MusicService中的对应函数,具体实现可自行补全。接下来实现进度条功能:

(1)当拖动进度条时,从拖动位置开始播放音乐;

(2)根据音乐的播放进度显示当前已播放时间。

music_Info = (TextView) findViewById(R.id.textView_music_info);

seekBar = (SeekBar) findViewById(R.id.sb);

seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

@Override

// SeekBar进度值发送改变

public void onProgressChanged(SeekBar seekBar, int i, boolean b) { }

@Override

// 开始拖动SeekBar

public void onStartTrackingTouch(SeekBar seekBar) { }

@Override

// SeekBar停止拖动

public void onStopTrackingTouch(SeekBar seekBar) {

​ int progress = seekBar.getProgress();

​ int musicMax = musicService.player.getDuration(); //歌曲播放时间

​ int seekBarMax = seekBar.getMax();

​ // 从停止处开始播放音乐

​ musicService.player.seekTo(musicMax * progress / seekBarMax);

}

});

MusicPlayerService类还要实现播放、恢复播放、获取当前进度、上一首、下一首、暂停和停止功能。

public void play() {
try {
player.reset();
String dataSource = musicList.get(musicId); // 获取当前播放音乐的路径
setPlayName(dataSource); // 设置 musicName

 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
 player.setDataSource(dataSource); // 设置播放路径
 player.prepare();
 player.start();

 player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
   public void onCompletion(MediaPlayer arg0) {
     next(); // 歌曲播放完毕,自动播放下一首
   }
 });

} catch (Exception e) {
Log.v(“TAG”, e.getMessage());
}
}
// 继续播放
public void resume(){
int position = getCurrentProgress();
player.seekTo(position); // 恢复当前播放位置
try {
player.prepare();
} catch (Exception e) {
e.printStackTrace();
}
player.start();
}

public int getCurrentProgress() {
if (player != null & player.isPlaying()) {
return player.getCurrentPosition();
} else if (player != null & (!player.isPlaying())) {
return player.getCurrentPosition();
}
return 0;
}

public void next() {
musicId = musicId == musicList.size() - 1 ? 0 : musicId + 1;
play();
}

public void last() {
musicId = musicId == 0 ? musicList.size() - 1 : musicId - 1;
play();
}

// 暂停播放
public void pause() {
if (player != null && player.isPlaying()){
player.pause();
}
}

public void stop() {
if (player != null && player.isPlaying()) {
player.stop();
player.reset();
}
}

在AndroidManifest文件中配置服务。

3)质感界面设计

“置于用户控制之下”、“保持界面的一致性”和“减轻用户的记忆负担”,通常称之为界面设计的“黄金三原则”。置于用户控制之下要求不强迫用户完成操作步骤,允许交互的中断和撤消。界面要保持清晰一致便于用户理解和使用。另外,由于人的短期记忆非常不稳定,因此对用户来说浏览比记忆更容易。

现在手机已经不仅仅是一个通话设备,它能够感知环境,提供各种智能化的服务。移动设备能够持续收集来自GPS、摄像头、麦克风和其它传感器的数据,并且通过这些数据感知环境的变化,然后作出反应,比如手机上的GPS、陀螺仪、气压计、麦克风,能跟踪用户的位置、方向,了解用户的各种信息,从而识别当前用户的状态。

2014年6月25日在Google I/O大会上宣布了新的Android界面设计——Material Design(质感设计)。Material Design要求交互和界面视觉更符合现实世界的物理反馈法则,比如一个小球下落,在真实世界中是一个加速的过程,如果在Android界面上显示小球下落的动画,也要有类似现实世界的感觉。质感设计关心界面上实体的光效、表面质感、运动感、实体感、层次、深度、与其他物体的叠放逻辑、动态效果、以及空间合理化利用等等。质感设计就像把交互界面变成了一张张的卡片。利用质感设计的API 接口,可以用来设计自己的具有Material Design的交互界面。

在界面上,菜单选项不显示在主屏幕上,而是通过滑动的方式将隐藏的菜单显示出来。滑动菜单只在需要的时候显示,节省了屏幕空间。实现滑动菜单需要用到DrawerLayout布局。DrawerLayout分为侧边菜单和主内容区两部分,侧边菜单提供滑动的展开与隐藏功能;主内容区用来设置菜单项,比如用ListView显示菜单项,它由开发者实现。

创建MaterialDesignActivity,在它的布局文件activity_material_design.xml中使用DrawerLayout布局。

<?xml version="1.0" encoding="utf-8"?>
 <android.support.v4.widget.DrawerLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@+id/slide_menu_drawer_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
 
   <FrameLayout
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
     <android.support.v7.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="?attr/actionBarSize"
       android:background="?attr/colorPrimary"
       android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
       app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
     </android.support.v7.widget.Toolbar>
   </FrameLayout>  
<ImageView
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:layout_centerInParent="true"
   android:layout_gravity="start"
   android:src="@drawable/slide_bird"
   android:background="#FFF"/>


 </android.support.v4.widget.DrawerLayout>

在DrawerLayout中放置两个控件。第一个控件是Toolbar,它放在FrameLayout布局中,作为主屏幕中显示的内容(主内容区)。第二个控件放置一个ImageView控件,作为滑动菜单(侧边菜单)显示的内容,当然也可以使用其他控件。注意:主内容区的布局代码要放在侧滑菜单布局代码的前面,以便DrawerLayout能够判断哪个控件是侧滑菜单,哪个控件是主内容区。

在设置侧边菜单时,要注意设置控件的layout_gravity属性,也就是必须告诉DrawerLayout滑动菜单是在屏幕的左边还是右边,指定left表示在左边,指定right表示在右边,如果指定了start,表示根据系统语言自动判断。英语、汉语等从左到右显示的语言,滑动菜单在左边;阿拉伯语等从右到左的语言,滑动菜单就在右边。

DrawerLayout侧边菜单的展开与隐藏事件通过DrawerLayout.DrawerListener来监听。当触发菜单展开与隐藏事件,可以更新Toolbar菜单或进行其他操作。

下面在MaterialDesignActivity中添加代码实现滑动菜单:

public class MaterialDesignActivity extends AppCompatActivity {
private DrawerLayout slideMenuDrawerLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_material_design);

 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
 setSupportActionBar(toolbar);

 slideMenuDrawerLayout = (DrawerLayout)

findViewById(R.id.slide_menu_drawer_layout);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {

// 显示actionBar上的导航按钮
actionBar.setDisplayHomeAsUpEnabled(true);

// 在actionBar上设置导航按钮图标
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
}

public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
slideMenuDrawerLayout.openDrawer(GravityCompat.START);
case R.id.search:
Toast.makeText(this, “搜索”, Toast.LENGTH_SHORT).show();
break;
case R.id.delete:
Toast.makeText(this, “删除”, Toast.LENGTH_SHORT).show();
break;
case R.id.add:
Toast.makeText(this, “增加”, Toast.LENGTH_SHORT).show();
default:
}
return true;
}
}

通常情况下滑动菜单隐藏起来,为了让用户知道应用软件有滑动菜单功能,需要提供一个提示让用户知道。在界面上,通过ActionBar的导航按钮来提示用户。ActionBar由Toolbar实现。

通过以上代码实现了滑动菜单功能。作为Material Design的一种设计,滑动菜单为移动应用的开发者提供了很好的设计理念。只要遵循Material Design的各种规范和建议来构造应用系统,最终将创建统一、美观的应用界面。

(4) 蓝牙

Android系统中使用蓝牙设备的基本工作流程,首先,要申请蓝牙设备的使用权限,获得BluetoothAdapter对象,判断当前设备中是否拥有蓝牙设备;判断当前设备中的蓝牙设备是否已经打开;通过扫描获取周围的蓝牙设备对象。

BluetoothActivity实现了多个接口包括:视图监听器,AdapterView,CompoundButton改变状态按钮的监听器,checkBox控件用来开启和关闭蓝牙设备的事件监听器,蓝牙连接监听器,蓝牙接收监听器。BluetoothAdapter类可以对蓝牙进行基本操作,比如:启动设备发现(startDiscovery), 获取已配对设备(getBoundedDevices), 通过mac蓝牙地址获取蓝牙设备(getRemoteDevice), 从其它设备创建一个监听连接等等。最后,蓝牙列表对象用来保存扫描到的蓝牙设备。

public class BluetoothActivity extends AppCompatActivity implements
View.OnClickListener, AdapterView.OnItemClickListener,
CompoundButton.OnCheckedChangeListener,
BluetoothConnectTask.BlueConnectListener,
InputDialogFragment.InputCallbacks,
BluetoothAcceptTask.BlueAcceptListener {
private static final String TAG = “BluetoothActivity”;

private CheckBox checkBox;
private TextView textView;
private ListView listView;
private BluetoothAdapter bluetoothAdapter;
private ArrayList bluetoothList =

​ new ArrayList();

在onCreate函数中,首先,调用自定义BluetoothTool类的getBlueToothStatus函数判断蓝牙设备是否开启,同时设置checkbox的状态,接着,调用蓝牙适配器的getDefaultAdapter函数获取本机蓝牙设备。

@Override
protected void onCreate(Bundle savedInstanceState) {
… …
if (BluetoothTool.getBlueToothStatus(this) == true) {
checkBox.setChecked(true); }

checkBox.setOnCheckedChangeListener(this);
textView.setOnClickListener(this);

bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
Toast.makeText(this, “本机未找到蓝牙功能”,

​ Toast.LENGTH_SHORT).show();
​ finish();
} }

实现checkBox的onCheckedChanged方法,如果checkbox开启,则调用beginDiscovery函数开始扫描蓝牙设备,并通过intent启动蓝牙设备设置界面,修改蓝牙设备的可见性,Intent的动作ACTION_REQUEST_DISCOVERABLE 表示请求用户选择是否使该蓝牙设备能被发现(扫描);如果checkbox关闭,则取消扫描,并且设置蓝牙的状态,清理蓝牙列表,同时清理界面上ListView控件显示的蓝牙设备。

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView.getId() == R.id.check_box_bluetooth) {
if (isChecked == true) {
beginDiscovery();
Intent intent = new Intent(

BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(intent, 1);*
* } else {
cancelDiscovery();
BluetoothTool.setBlueToothStatus(this, false);
bluetoothList.clear();
BluetoothListAdapter adapter = new BluetoothListAdapter(this, bluetoothList);
listView.setAdapter(adapter);
}
}
}

发现(扫描)蓝牙函数,首先清理蓝牙列表,通过蓝牙列表适配器将蓝牙设备列表与ListView控件关联起来,然后,调用bluetoothAdapter的startDiscovery开始扫描设备。取消发现(扫描)函数,从handler中取消刷新回调,并通过bluetoothAdapter对象取消发现操作。

private void beginDiscovery() {
if (bluetoothAdapter.isDiscovering() != true) {
bluetoothList.clear();
BluetoothListAdapter adapter = new BluetoothListAdapter(
BluetoothActivity.this, bluetoothList);
listView.setAdapter(adapter);
textView.setText(“正在搜索蓝牙设备”);
bluetoothAdapter.startDiscovery();
}
}

private void cancelDiscovery() {
handler.removeCallbacks(refresh);
textView.setText(“取消搜索蓝牙设备”);
if (bluetoothAdapter.isDiscovering() == true) {
bluetoothAdapter.cancelDiscovery();
}
}

本章习题:

1**、本单元考核点**

各种传感器的基本概念和使用方法。

Android系统中GPS的定位方法。

Android系统音视频播放的使用方法。

使用摄像头实现拍照功能。

界面设计原则、用户体验设计和质感设计(Material Design)。

2**、本单元课后习题**

1、说明SoundPool与MediaPlayer的区别,以及在什么情况下使用SoundPool。
答案:在Android开发中经常使用MediaPlayer来播放音频文件,但是MediaPlayer存在一些不足:资源占用量较高、延迟时间较长、不支持多个音频同时播放等。这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,例如在对时间精准度要求相对较高的游戏开发中。在游戏开发中,经常需要播放一些游戏音效(比如:子弹爆炸,物体撞击等),这些音效的共同特点是短促、密集、延迟程度小。在这样的场景下,可以使用SoundPool代替MediaPlayer来播放这些音效。
MediaPlayer:占用资源较高,不支持同时播放多个音频。
SoundPool:可以同时播放多个短促的音频,而且占用的资源较少。适合在程序中播放按键音,或者消息提示音等。
3、。什么是ANR,如何避免它?
答案:ANR(Application Not Responding)是指程序不响应,在用户使用过程中,应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应的对话框。
避免ANR: Android应用程序通常运行在一个单独的线程里面,称谓主线程,所以在主线程里面少做一些耗时长的程序,而是利用子线程来操作一些繁琐的事情,用Handler来把子线程处理的消息返回给主线程。

参考资源:

1、GitHub:https://github.com

2、刘望舒著.Android进阶之光.北京:电子工业出版社,2017.

原创声明

=======

作者: [ libin9iOak ]


本文为原创文章,版权归作者所有。未经许可,禁止转载、复制或引用。

作者保证信息真实可靠,但不对准确性和完整性承担责任。

未经许可,禁止商业用途。

如有疑问或建议,请联系作者。

感谢您的支持与尊重。

点击下方名片,加入IT技术核心学习团队。一起探索科技的未来,共同成长。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/704062.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Tomcat 部署优化

目录 一.Tomcat介绍 二.了解Tomcat里面里面是放什么的 三. Tomcat&#xff1a;是一个特殊的服务 有两个领域 四.tomcat概述 五.再加上那个扩展java虚拟机&#xff08;JVM&#xff09; 调优 tomcat 优化分两种 六.Tomcat核心组件 ​编辑 容器&#xff1a;什么是容器 …

Dart语法01-变量、内置类型、操作符、方法与异常

Dart基础 文章目录 Dart基础变量final与const 内置的类型Numbers&#xff08;数值&#xff09;Strings&#xff08;字符串&#xff09;Booleans&#xff08;布尔值&#xff09;Lists&#xff08;列表&#xff09;Maps&#xff08;映射集合&#xff09;Runes&#xff08;用于在字…

用八叉树检测点云是否发生变化

检测点云数据集之间的空间变化有以下几个用处&#xff1a; 目标跟踪与物体识别&#xff1a;空间变化检测可以用于实时更新点云数据中的物体位置、姿态、形状等信息。这对于目标跟踪和物体识别非常重要&#xff0c;可以帮助我们在动态场景中准确地识别和跟踪物体&#xff0c;从而…

Maven下载安装及其配置

Maven下载安装及其配置 文章目录 Maven下载安装及其配置1、Maven介绍2、Maven下载安装2.1、最新2.2、旧版3.8 3、Maven环境配置3.1、配置3.2、验证3.3、 配置本地仓库 4、Maven使用4.1、Maven常用命令4.2、IDEA中的使用 1、Maven介绍 Maven 是一个流行的构建工具和依赖管理工具…

使用Excel和PowerPoint设计24小时甘特图

1.前期调研 可以使用Project软件、在线甘特图MindsUP等来设计&#xff0c;可是我的需求是记录周边几个单位每天的开闭馆时间&#xff0c;而不是按照天、月等来记录项目周期。因此&#xff0c;这些软件不符合我的需求&#xff0c;必须用excel来设计。 2.excel录入数据 &#xf…

云时代已至,新一代数据分析平台是如何实现的?

2023 年 5 月&#xff0c;由 Stackoverflow 发起的 2023 年度开发者调查数据显示&#xff0c;PostgreSQL 已经超越 MySQL 位居第一&#xff0c;成为开发人员首选。PostgreSQL 在国内的热度也越来越高。6 月 17 日&#xff0c;PostgreSQL 数据库技术峰会在成都顺利召开。本次大会…

深入理解 Golang: 锁

本文通过对 Go 中源码层面的加锁、解锁实现细则来介绍锁的操作&#xff0c;包括 Mutex 互斥锁、RWMutex 读写锁&#xff0c;以及它们底层依赖的 sema 信号锁。 atomic 原子操作 正常情况下&#xff0c;多个协程同时操作 num 时&#xff0c;不能保证 num 值得最终一致性&#x…

锈湖新作地铁繁花试玩版正式上线啦

地铁繁花是锈湖厂商新作点击式解谜冒险解谜游戏&#xff0c;英文名称为“Underground Blossom”&#xff0c;在游戏中你将深入锈湖的地下&#xff0c;扮演并追溯Laura Vanderboom的人生和记忆吧&#xff01;从一个车站到另一个车站&#xff0c;每个地铁站都象征着劳拉的一段过去…

语音芯片WT2003H-B003,集成压力传感与语音提示的按摩器创新方案

​在如今追求健康、舒适生活方式的时代&#xff0c;压力传感技术与语音提示功能的结合正引领着按摩器行业的创新浪潮。WT2003H-B003语音芯片IC作为一款独具价值的语音芯片&#xff0c;以其集成了先进的压力传感算法和语音提示功能&#xff0c;为按摩器压感方案带来了全新的体验…

Selenium修改HTTP请求头三种方式

目录 什么是HTTP请求头 需要更改HTTP请求请求头 Selenium修改请求头 Java HTTP请求框架 代码实战 使用反向代理 使用 Firefox 扩展 下载火狐浏览器扩展 加载火狐扩展 设置扩展首选项 设置所需的功能 完整自动化用例 总结&#xff1a; 什么是HTTP请求头 HTTP请求头…

科普 | 什么是5G消息平台功能完备性认证,怎么才能获得5G消息平台功能完备性证书

5G消息平台功能完备性测试是由中国信息通信研究院同中国通信企业协会在5G消息工作组共同发起&#xff0c;旨在提升CSP的5G消息平台质量&#xff0c;促进5G消息业务发展。 测试针对5G消息平台的Chatbot下行消息交互、Chatbot接收消息、消息平台业务配置管理、消息平台业务统计管…

智能故障诊断的深度学习模型复杂度指标计算(MACs、Params)

引言: 对于智能故障诊断任务而言,受限于现场工业设备设施的算力,模型在轻量化上具有典型需求。因此,在保证模型精准性的同时尽量降低模型的复杂度是必要的,本博客对模型的复杂度概念进行了剖析,并在pytorch框架下对相关热门轻量级模型的复杂度评估进行了分析。 深度学习…

容智信息荣获2023第三届中国RPA+AI开发者大赛多项大奖

近日&#xff0c;历时数月的「2023第三届中国RPAAI开发者大赛」在苏州圆满收官。本次大赛由RPA中国联合全球人工智能产品应用博览会主办&#xff0c;容智信息作为顶级联合主办单位&#xff0c;主旨挖掘人才&#xff0c;促进RPA和AI技术在社会各领域的融合性应用。 这次大赛的主…

计算机网络————应用层

文章目录 概述域名系统DNS域名结构域名服务器解析过程常见的DNS记录DNS报文格式基础结构部分问题部分资源记录(RR, Resource Record)部分 万维网WWWURLHTTPHTTP发展HTTP报文结构请求报文响应报文 cookie 内容分发网络CDN 概述 应用层的具体内容就是规定应用进程在通信时所遵循的…

JS中常用内置对象

真正原创的东西很少&#xff0c;能抄明白就很不容易了 文章目录 数组常用方法❗push 数据增加到尾部并返回unshift 数据增加到头部并返回pop 删除最后一个数据并返回shift 删除第一个数据并返回sort 数组排序reverse 数组逆序concat 合并多个数组的数据并返回join 数据连接成字…

SpringBoot Thymeleaf企业级真实应用:使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出(四) 表格中断问题

接上一篇 SpringBoot Thymeleaf企业级真实应用&#xff1a;使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出(三) 给pdf加水印、页眉页脚、页眉logo 设置表格的css样式 table {/*分页时表格换行, 可不用, 使用表格行换行即可*//*page-break-before: always;*/border-…

QT简易加法计算器项目实现

完整代码见GitHub&#xff1a;点击进入 在该项目中&#xff0c;使用了三个文件&#xff0c;分别是CalculatorDialog.h, CalculatorDialog.cpp, main.cpp CalculatorDialog.h&#xff1a;在该头文件里定义了一些成员变量和槽函数&#xff0c;用于实现计算器基本功能。Calculator…

Springboot的自动装配解读

目录 1.Springboot的自动装配 1.1 组件装配 1.1.1 组件 1.2 Spring Framework 的模块装配 1.2.1 Import注解 1.2.2 BeanDefinition 1.3 Spring Framework 的条件装配 1.3.1 Profile 1.3.2 Conditional 1.3.3 MetaData元数据接口&#xff08;补充&#xff09; Annot…

4、离线数仓数据同步策略(全量表数据同步、增量表数据同步、首日同步、采集通道脚本)

1、离线数仓同步数据 1.1 用户行为数据同步 1.1.1 数据通道 用户行为数据由Flume从Kafka直接同步到HDFS&#xff0c;由于离线数仓采用Hive的分区表按天统计&#xff0c;所以目标路径要包含一层日期。具体数据流向如下图所示。 1.1.2 日志消费Flume配置概述 按照规划&…

【选择排序】手撕八大排序之直接选择排序和堆排序

目录 一.选择排序 1.直接选择排序 2.堆排序 一.选择排序 1.直接选择排序 选择排序&#xff08;Selection Sort&#xff09;是一种简单直观的排序算法。它的基本思想是每次遍历找到最小&#xff08;或最大&#xff09;的元素&#xff0c;然后将其放置在已排序序列的末尾。在…