手机桌面管理的技巧
如何实现实时壁纸
如何创建快捷方式
如何创建桌面小控件
如何创建文件夹
能力目标
了解手机桌面管理的技巧
熟练掌握如何实现实时壁纸
熟练掌握如何创建快捷方式
熟练掌握如何创建桌面小控件
熟练掌握如何创建文件夹
本章简介
Android被认为是新一代的移动操作系统,它与传统的移动操作系统存在很大的区别。传统的移动操作系统无论在功能还是外观上,都与PC操作系统有很大的区别,而Android是目前比较接近PC操作系统的移动操作系统。Android最吸引人的功能恐怕就是可以直接显示在屏幕上的“小玩意”:窗口小部件、快捷方式、实时文件夹。这些组件都充分体现了新一代移动操作系统的特征。在本节中,我们将结合具体的案例深入学习Android中这些与桌面组件相关的知识。
核心技能部分
当拿到Android手机后我们第一眼看到的就是桌面,这个手机桌面和PC机中Windows的桌面类似。桌面上通常用来放置一些常用的程序和功能组件。如图7.1.1所示。
图7.1.1 手机桌面
在手机桌面上我们首先看到的是壁纸,也就是手机桌面上的那张图片,在桌面上排列着多个图标,这些图标分为三大类:快捷方式、实时文件夹、桌面控件。其中快捷方式与实时文件夹只占用桌面的一个摆放位置,而桌面控件则可以占据多个摆放位置。
我们可以通过程序管理Android桌面,包括改变系统壁纸、快捷方式、实时文件夹与桌面控件等。这也正是本章中我们要重点学习的内容。
默认情况下,原生的Android系统在初始状态下只会显示一个壁纸以及Google Search和Home screen tips在桌面上。其中Google Search是屏幕最上方的搜索条;Home screen tips是搜索条下面的提示框,如下图7.1.2所示 。
图7.1.2 默认桌面
桌面上显示的图标是可以进行删除和天机的删除桌面组件可以通过以下两步来完成:
(1) 在屏幕上长按指定组件,直到桌面下方出现垃圾桶。
(2) 将指定组件拖到桌面下方的垃圾桶中即可删除,如下图7.1.3所示。
图7.1.3 删除桌面图标
添加桌面组件可以通过以下几步来完成:
(1) 当系统显示手机桌面时,长按手机屏幕空白区域,在桌面上显示如图7.1.4所示的菜单。
图7.1.4 桌面菜单
(2) 以添加“快捷方式”为例,单击图7.1.4所示列表的“Shortcuts”菜单项,系统会显示图7.1.5的所示的界面。
图7.1.5 选择添加桌面快捷方式
(3) 以添加“ApiDemos”为例,选中图7.1.5所示列表中的“ApiDemos”列表项,然后返回桌面将会看到图7.1.6所示的界面,ApiDemos的快捷方式已经出现在桌面上了。
图7.1.6 桌面快捷方式
手机桌面壁纸总是只显示静态图片会略显单调,那么其实我们可以通过实时壁纸技术使手机桌面“动起来”。所谓实时壁纸就是指手机桌面不再是简单的图片,而是运行中的动画,这个动画是由程序实时绘制的,因此被称为实时壁纸。
在Android中开发实时壁纸需要继承名为WallpaperService的类,并重写相关方法。具体步骤如下:
(1) 创建一个继承自WallpaperService的类。
(2) 重写WallpaperService的onCreateEngine()方法,该方法返回一个WallpaperService.Engine类的对象。
(3) 实现WallpaperService.Engine类,重写onVisibilityChanged()、onOffsetsChanged()方法。另外,由于WallpaperService.Engine类采用了与SurfaceView相同的绘图机制,因此可以选择性地重写SurfaceHolder.Callback中的三个方法,重写这些方法时可通过SurfaceHolder动态地绘制图形。
按照7.1节所示的内容,依次长按桌面—>Wallpapers—>Live wallpapers—>Cube,可以选择Android系统默认提供的一个实时壁纸作为当前壁纸,设置完成后桌面效果如下图7.1.7所示,整个立方体会沿着x轴的方向旋转。
图7.1.7 Cube效果
示例7.1
创建实时壁纸。
要求当系统进入预览实时壁纸界面时,可以看到一个圆圈在不停地移动,如果7.1.8所示。单击【Set Wallpaper】按钮时,即可应用这个实时壁纸程序。然后当再次切换到Android系统界面,将可在桌面上看到图7.1.9所示的效果。
图7.1.8 实时壁纸预览界面
图7.1.9 自制实时壁纸效果图
首先编写一个继承自WallpaperService的类来实现实时壁纸的相关服务,详细代码如下:
public class LiveWallpaperService extends WallpaperService {
// 实现WallpaperService必须实现的抽象方法
@Override
public Engine onCreateEngine() {
// 返回自定义的Engine
return new MyEngine();
}
class MyEngine extends Engine {
// 记录程序界面是否可见
private boolean isVisibile;
// 记录当前当前用户动作事件的发生位置
private float touchX = -1;
private float touchY = -1;
// 记录当前圆圈的绘制位置
private float cx = 15;
private float cy = 20;
// 定义画笔
private Paint mPaint = new Paint();
// 定义一个Handler
Handler mHandler = new Handler();
// 定义一个周期性执行的任务
private final Runnable drawTarget = new Runnable() {
public void run() {
drawFrame();
}
};
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
// 初始化画笔
mPaint.setColor(0xffffffff);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(2);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStyle(Paint.Style.STROKE);
// 设置处理触摸事件
setTouchEventsEnabled(true);
}
@Override
public void onDestroy() {
super.onDestroy();
// 删除回调
mHandler.removeCallbacks(drawTarget);
}
@Override
public void onVisibilityChanged(boolean visible) {
isVisibile = visible;
// 当界面可见时候,执行drawFrame()方法。
if (visible) {
// 动态地绘制图形
drawFrame();
} else {
// 如果界面不可见,删除回调
mHandler.removeCallbacks(drawTarget);
}
}
@Override
public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) {
drawFrame();
}
@Override
public void onTouchEvent(MotionEvent event) {
// 如果检测到滑动操作
if (event.getAction() == MotionEvent.ACTION_MOVE) {
touchX = event.getX();
touchY = event.getY();
} else {
touchX = -1;
touchY = -1;
}
super.onTouchEvent(event);
}
// 定义绘制图形的工具方法
private void drawFrame() {
// 获取该壁纸的SurfaceHolder
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
// 对画布加锁
c = holder.lockCanvas();
if (c != null) {
c.save();
// 绘制背景色
c.drawColor(0xff000000);
// 在触碰点绘制圆圈
drawTouchPoint(c);
// 绘制圆圈
c.drawCircle(cx, cy, 80, mPaint);
c.restore();
}
} finally {
if (c != null)
holder.unlockCanvasAndPost(c);
}
mHandler.removeCallbacks(drawTarget);
// 调度下一次重绘
if (isVisibile) {
cx += 15;
cy += 20;
// 如果cx、cy移出屏幕后从左上角重新开始
if (cx > 320)
cx = 15;
if (cy > 400)
cy = 20;
// 指定0.1秒后重新执行mDrawCube一次
mHandler.postDelayed(drawTarget, 100);
}
}
// 在屏幕触碰点绘制圆圈
private void drawTouchPoint(Canvas c) {
if (touchX >= 0 && touchY >= 0) {
c.drawCircle(touchX, touchY, 40, mPaint);
}
}
}
}
在本Service类中重写了WallpaperService.Engine的onVisibilityChanged()、onOffsetsChanged()方法,并指定了当桌面显示时调用drawFrame()方法进行重绘,在drawFrame()方法中绘制完成后利用Handler对象指定0.1秒后重绘。
完成了Service类的编写之后,接下来需要在功能清单文件中配置该Service,配置实时壁纸的Service时,需要指定运行实时壁纸所需要的权限及的编写meta-data两项内容。详细代码如下所示:
<service
android:name=".LiveWallpaperService"
android:label="@string/app_name"
android:permission="android.permission.BIND_WALLPAPER" >
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService"/>
intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/livewallpaper" />
service>
上面配置文件中指定了实时壁纸的meta-data放在xml/livewallpaper中定义,因此程序还需要在res/xml目录下增加一个livewallpaper.xml的文件,此文件的代码如下:
xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"/>
运行该程序,当程序启动起来之后,切换到Android桌面,长按桌面,在弹出的“Add to Home screen”对话框中单击Wallpapers列表项,然后再在弹出的“Select wallpaper from”对话框中单击“Live wallpapers”列表项,最后选择“打开程序”,单击打开的预览窗口中“Set wallpaper”按钮,就可以在将我们的动态桌面设置为系统默认的桌面。
虽然本示例程序实现的实时壁纸只是在桌面上绘制运动的圆形,程序功能比较简单,但有关实时壁纸的知识点已经讲解的比较透彻。在实际的应用开发中我们可以根据自己的需求在系统桌面上绘制更加复杂美观的动态图形,从而实现漂亮的实时壁纸功能。
如果手机中应用程序安装的太多,找起来会很费劲。这种情况在Windows中是一样的,而Windows系统通过在桌面上为应用程序创建快捷方式来解决该问题。我们Android中有没有类似的功能呢?答案是肯定的,Android系统也有快捷方式的类似的功能。在Android中实现快捷方式的添加有两种情况:向快捷方式列表添加快捷方式和直接将快捷方式添加到桌面。在本节中我们会通过两个案例分针对这两种情况进行讲解。
默认情况下,应用程序的快捷方式不会出现在图7.1.5所示的列表(快捷方式列表)中,为了让指定应用程序出现在快捷方式列表中,只需要在功能清单文件中配置该Activity时指定相应的
示例7.2
向快捷方式列表添加快捷方式。新建一个项目,在该项目中新创建一个Activity,Activity类的详细代码如下:
public class AddShortcutToList extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button btn = new Button(this);
btn.setText("快捷方式列表中的快捷方式程序");
setContentView(btn);
}
}
在功能清单文件中对这个Activity类进行设置,代码如下:
<activity
android:name="MainViewAddShortcut"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.CREATE_SHORTCUT" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
此处配置快捷方式的Activity时,必须得指定CREATE_SHORTCUT动作,只需要将这个Activity添加到任何的应用程序中即可。在该程序启动后,快捷方式会自动添加到图7.1.4所示的列表中,如下图7.1.10所示。
在该程序启动后,快捷方式会自动添加到图7.1.5所示的列表中,如下图7.1.10所示。当我们单击这个列表项时,就会调用相对应的Activity程序。
图7.1.10 列表中的快捷方式
示例7.3
直接将快捷方式放在桌面上。
在示例7.2中我们只是将快捷方式直接添加到Shortcuts列表中,为了方便,我们也可以通过广播的方式直接将快捷方式添加到桌面上。这种应用在我们的平时所接触到的程序中比较常见,比如当我们第一次运行一个程序时,它会提示我们是否在桌面上设置快捷方式就是通过这种技术实现的。
首先写一个只显示一个ImageView的Activity,然后在主布局文件中提供一个id为shortCut的按钮,当用户单击这个按钮时调用上面创建的Activity,将图片显示在屏幕上。
Activity类代码:
public class ShortcutActivity extends Activity implements OnClickListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.shortcut);
Button btn = (Button) findViewById(R.id.shortCut);
btn.setOnClickListener(this);
}
@Override
public void onClick(View view) {
//指定安装快捷方式的Action
Intent installShortCut =
new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
//指定快捷方式在桌面上的显示名称
installShortCut.putExtra(Intent.EXTRA_SHORTCUT_NAME, "打开图片");
Parcelable icon =
Intent.ShortcutIconResource.fromContext(this, R.drawable.ic_launcher);
installShortCut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
Intent intent = new Intent("com.hc.activity.DesktopActivity");
installShortCut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
sendBroadcast(installShortCut);
}
}
在功能清单文件中添加如下权限:
运行程序,会在桌面上生成如下图7.1.11所示的快捷方式
图7.1.11 桌面快捷方式
所谓桌面小控件,指的是直接显示在Android系统桌面上的小程序,比如图7.1.1中的Google Seacher。一般来说, 我们可以把那些用户经常用到的程序,比如搜索框、指南针、时钟、日历等做成桌面控件,这样用户可以直接在桌面上看到程序的运行界面,非常方便。
桌面小控件是通过广播的形式进行控制的,因此每个桌面小控件都对应一个BroadcastReceiver类。为了简化桌面小控件的开发,Android系统提供了一个名为AppWidgetProvider的BroadcastReceiver的子类。在开发桌面小控件时,我们只需要继承这个类,并重写它的不同状态的生命周期方法,就可以非常方便地实现自己的需求。
AppWidgetProvider类里提供了如下四个不同的生命周期方法:
Ø onUpdate():负责更新桌面控件,它只有在小控件被用户放到桌面上时才会被调用到。
Ø onDeleted():当一个或多个桌面小控件被删除时回调这个方法。
Ø onEnabled():当接收到ACTION_APPWIDGET_ENABLED广播时回调这个方法。
Ø onDisabled():当接收到ACTION_APPWIDGET_DISABLED广播时回调这个方法。
一般来说,开发桌面小控件只需要定义一个AppWidgetProvider的子类,并重写它的onUpdate()方法即可,重写这个方法的步骤如下:
(1) 创建一个RemoteViews对象,创建该对象时可以加载指定的界面布局文件。
(2) 如果需要改变上一步所加载的界面布局文件的内容,可以通过RemoteViews对象进行修改。
(3) 创建一个ComponentName对象。
(4) 调用AppWidgetManager更新桌面小控件。
示例7.4
自定义一个数字时钟桌面程序,效果如下图7.1.12所示。
为了实现程序,我们需要在布局文件中定义5个ImageView,分别用来显示小时、分钟以及它们之间的冒号。为了让时钟实时地显示当前时间,程序需要每隔一分钟更新一次界面,我们可以借助Handler实现。
图7.1.12 桌面时钟
首先编写布局文件,在布局文件digitclock.xml中提供id值分别为img01、img02、img03、img04、img05的五个ImageView,用来显示具体的时钟界面。然后在res/xml目录下新建一个名为appwidget_provider.xml的文件,该文件用来指定桌面小控件使用谁作为meta-data,它的详细代码如下:
xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="300px"
android:minHeight="52px"
android:updatePeriodMillis="60000"
android:initialLayout="@layout/digitclock"/>
继承自AppWidgetProvider的数字时钟类的代码如下:
public class DigitClock extends AppWidgetProvider {
private Timer timer = new Timer();
private AppWidgetManager manager;
private Context context;
// 将0~9的数字图片定义成数组
private int[] digits = new int[] {
R.drawable.num1, R.drawable.num2, R.drawable.num3,
R.drawable.num4, R.drawable.num5, R.drawable.num6,
R.drawable.num7, R.drawable.num8, R.drawable.num9, R.drawable.num10};
// 将显示小时、分钟、秒钟的ImageView定义成数组
private int[] digitViews = new int[] { R.id.img01, R.id.img02, R.id.img04, R.id.img05 };
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
this.manager = appWidgetManager;
this.context = context;
// 定义计时器
timer = new Timer();
// 启动周期性调度
timer.schedule(new TimerTask() {
public void run() {
// 发送空消息,通知界面更新
handler.sendEmptyMessage(1234);
}
}, 0, 60000);
}
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1234) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.digitclock);
// 定义SimpleDateFormat对象
SimpleDateFormat df = new SimpleDateFormat("HHmm");
// 将当前时间格式化成HHmmss的形式
String time = df.format(new Date());
for (int i = 0; i < time.length(); i++) {
// 将第i个数字字符转换为对应的数字
int num = time.charAt(i) - 48;
// 将第i个图片的设为对应的数字图片
views.setImageViewResource(digitViews[i], digits[num]);
}
// 将AppWidgetProvider子类实例包装成ComponentName对象
ComponentName componentName = new ComponentName(context, DigitClock.class);
// 调用AppWidgetManager将remoteViews添加到ComponentName中
manager.updateAppWidget(componentName, views);
}
}
};
}
由于AppWidgetProvider继承了BroadcastReceiver,所以从本质上看它还是一个广播,因此我们还需要在功能清单文件中使用
<receiver
android:name=".DigitClock"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider" />
receiver>
把该应用布置到模拟器上,按照7.1节所示的方法将该桌面程序安装到桌面上,将会看到图7.1.12所示的效果,时钟会每隔一分钟更新一次,与模拟器系统时间保持一致。
7.5 实时文件夹
所谓的实时文件夹(LiveFoler)指的是用于显示ContentProvider提供的数据的桌面组件。众所周知ContentProvider组件用于向外提供数据访问接口,一个应用程序可通过ContentProvider把自己的数据暴露出来,从而允许其它程序调用。ContentProvider除了可以供其它程序访问外,还可以通过实时文件夹添加成桌面快捷方式。
当用户把实时文件夹添加到桌面上之后,如果用户单击该实时文件夹,系统将会显示从指定ContentProvider中查询出来的全部数据,这在很大程度上为用户的使用带来了便利。
实时文件夹可以访问其它应用程序中的数据,例如联系人、电子邮件、短信等。从本质上来说它也是一个Activity,只是该Activity并不会加载任何显示界面,开发实时文件夹同样需要继承Activity,并重写onCreate()方法,重写此方法时建议大家按照如下步骤进行:
(1) 创建一个Intent。
(2) 调用该Intent的setData(Uri uri)方法,该Uri参数就是ContentProvider对外提供数据的Uri。进入该文件夹将会显示此ContentProvider所返回的数据列表。
(3) 依次调用Intent对象的putExtra()方法来设置实时文件夹的图标、标题、显示模式等内容。
(4) 设置完成后,调用Activity的finish()方法结束当前的Activity。
示例7.5
创建实时文件夹,显示电话本中的联系人的信息。
Activity类的代码如下:
public class AddLiveFolder extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(getIntent().getAction())) {
Intent intent = new Intent();
intent.setData(Uri.parse("content://contacts/live_folders/people"));
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT, new Intent(Intent.ACTION_VIEW, ContactsContract.Contacts.CONTENT_URI));
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, "电话本");
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON, Intent.ShortcutIconResource.fromContext(this, android.R.drawable.sym_action_call));
intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, LiveFolders.DISPLAY_MODE_LIST);
setResult(RESULT_OK, intent);
} else {
setResult(RESULT_CANCELED);
}
finish();
}
}
本示例中用到了大量的实时文件夹相关的属性常量,具体含义参看下表7-1-1所示。
表7-1-1 实时文件夹常用属性及含义
名称 |
含义 |
EXTRA_LIVE_FOLDER_NAME |
实时文件夹的名称 |
EXTRA_LIVE_FOLDER_ICON |
实时文件夹的图标 |
EXTRA_LIVE_FOLDER_DISPLAY_MODE |
实时文件夹的显示模式,取值有: Ø DISPLAY_MODE_LIST(列表) Ø DISPLAY_MODE-GRID(网格) |
EXTRA_LIVE_FOLDER_BASE_INTENT |
当该项数据被单击时,将启动该Intent对应的程序组件,并把该数据的主键追加到该Intent的data数据后面。 |
功能清单文件中对上述配置文件进行注册,代码如下:
<activity
android:name=".AddLiveFolder"
android:label="电话本" >
<intent-filter>
<action android:name="android.intent.action.CREATE_LIVE_FOLDER" />
intent-filter>
activity>
程序运行效果如下图7.1.13所示,当我们单击这个电话图标时就会打开联系人界面,如下图7.1.14所示:
图7.1.13 电话本实时文件夹
图7.1.14 联系人列表
任务实训部分
训练技能点
自定义桌面小控件
需求说明
倒计时提醒是我们日常生活经常用到的一个功能,本程序要求大家做按照下图7.2.1所示的界面设计一个具有倒计时提醒功能的桌面小控件,用户可以自行指定提示的内容及时间。
图7.2.1 倒计时桌面控件
需求说明
为之前编写的软件管理器创建一个快捷方式。要求当用户第一次运行软件退出软件时,系统弹出一个询问用户是否创建桌面快捷方式的对话框,当用户确认后会在桌面上创建本程序的快捷方式;否则,不创建快捷方式。
巩固练习
一、简答题
1. 简单阐述在Android中开发实时壁纸的步骤。
2. 简单阐述在Android中创建实时文件夹的步骤。
二、上机练习
修改7.5节中的实时文件夹示例程序的代码,要求:当我们单击桌面的电话图标时,显示出来的联系人信息以网格的形式进行显示。