1 桌面宠物APP需求分析
1.1 项目简介
使用安卓开发实现一个类似QQ宠物的桌面宠物APP。
1.2 灵感来源
之前看到360安全卫士在安卓桌面实现悬浮窗的功能,我想利用悬浮窗这个想法来实现一个全局安卓桌面宠物APP。
1.3 实现功能
• 宠物成长功能:根据手机使用时长实现宠物进化升级的功能,主要是宠物形态的变化;
• 随意移动宠物;
• 点击宠物打开功能菜单:包括照相、购物和回家功能;
• 打开APP首页;
• 打开淘宝首页;
• 打开照相机;
• 未完待续。
2 桌面宠物APP实现分析
2.1 悬浮窗的实现
基于Android开发四大组件之一的Service实现悬浮窗。
• 定义PetWindowService类继承Service类
1
public class PetWindowService extends Service{...}
• 用WindowManager类来实现悬浮窗的窗体参数的设置。
1
wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void showPetView() {
//获取屏幕宽度和高度
winWidth = wm.getDefaultDisplay().getWidth();
winHeight = wm.getDefaultDisplay().getHeight();
params = new WindowManager.LayoutParams();
int LAYOUT_FLAG;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
// 自定义宠物窗体params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
// 屏幕左上角对齐
params.gravity = Gravity.LEFT + Gravity.TOP;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.type = LAYOUT_FLAG;
// 加入宠物窗体
petView = View.inflate(this, R.layout.activity_pet, null);
// 悬浮窗功能菜单控件
imageView = petView.findViewById(R.id.petView);
imgHome = petView.findViewById(R.id.imgHome);
imgCart = petView.findViewById(R.id.imgCart);
imgCamera = petView.findViewById(R.id.imgCamera);
petNameView = petView.findViewById(R.id.petName);
wm.addView(petView, params);
}
• 实现宠物拖拽。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 实现拖拽宠物
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getRawX();
int moveY = (int) event.getRawY();
int disX = moveX - startX;
int disY = moveY - startY;
params.x = params.x + disX;
params.y = params.y + disY;
if (params.x < 0) {
params.x = 0;
}
if (params.y < 0) {
params.y = 0;
}
if (params.x > winWidth - petView.getWidth()) {
params.x = winWidth - petView.getWidth();
}
if (params.y > winHeight - petView.getHeight()) {
params.y = winHeight - petView.getHeight();
}
wm.updateViewLayout(petView, params);
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
});
2.2 宠物功能菜单栏的实现
• 实现功能菜单栏,主要是ImageView的监听事件
实现菜单开关:使用count计数来判断奇数单击还是偶数单击对应打开菜单和关闭菜单。
控制菜单可见方法:ImageView控件的setVisibility方法包括三个属性:VISIBLE、GONE、INVISIBLE,分别表示控件可见、不可见且不占位、不可见且占位。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 实现单击打开菜单,第二次关闭菜单
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
count++;
if(count % 2 == 1){
imgHome.setVisibility(View.VISIBLE);
imgCamera.setVisibility(View.VISIBLE);
imgCart.setVisibility(View.VISIBLE);
}
else {
imgHome.setVisibility(View.GONE);
imgCamera.setVisibility(View.GONE);
imgCart.setVisibility(View.GONE);
count = 0;
}
}
});
• 跳转到APP主页,从Service跳转到Activity需要设置intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);,跳转到其他APP也要用到这行代码。
1
2
3
4
5
6
7
8
9
// 点击imgHome打开主页面
imgHome.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(PetWindowService.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
• 跳转到相机。
1
2
3
4
5
6
7
8
9
10
// 点击imgCamera打开相机
imgCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction("android.media.action.STILL_IMAGE_CAMERA");
startActivity(intent);
}
});
• 跳转到淘宝。
1
2
3
4
5
6
7
8
9
10
11
// 点击imgCart打开淘宝
imgCart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction("Android.intent.action.VIEW");
intent.setClassName("com.taobao.taobao", "com.taobao.tao.welcome.Welcome");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
2.3 APP主页显示宠物信息
主页实现功能主要包括:宠物取名,选择宠物类型,领养新宠物,选择已有宠物进行展示,宠物名字展示,宠物类型展示,宠物年龄展示,释放宠物,收回宠物,删除宠物。
• 第一部分:创建宠物
选择宠物类型,Spinner控件监听事件。
1
2
3
4
5
6
7
8
9
10
11
// 选择宠物类型
petMode.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mode = modeArray[position];
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
创建宠物类型,Button控件监听事件,将宠物名字一并记录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 创建宠物
bn_createPet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 取名
petName = editName.getText().toString();
boolean sameName = false;
for (int i = 0; i < petNameAdapter.getCount(); i++) {
if (petName.equals(petNameAdapter.getItem(i))) {
sameName = true;
break;
}
}
if (!editName.getText().toString().equals("") && !sameName){
// 创建宠物
createPet(mode, petName, petAgeSeconds);
Toast.makeText(MainActivity.this,
"恭喜你拥有了一只" + mode + "类宠物," + "它的名字叫" + petName,
Toast.LENGTH_LONG).show();
petNameAdapter.add(petName);
} else {
Toast.makeText(MainActivity.this, "名字不能为空或重复,请再次输入!", Toast.LENGTH_LONG).show();
}
}
});
• 第二部分:展示宠物信息
首先从数据库中选择已有宠物进行展示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 从数据库中选择一只宠物
petNameList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
tmp_name = (String) petNameList.getSelectedItem();
// 上一个宠物名字
if (!last_name.equals("")){
last_name = showPetName.getText().toString();
} else {
last_name = tmp_name;
}
// 显示宠物名字
showPetName.setText(tmp_name);
// 显示宠物类型
showPetMode.setText(getPetMode(tmp_name));
// 显示宠物图像
showPetImg.setImageResource(changePet(showPetMode.getText().toString()));
// 显示宠物年龄
petAgeView.setText(showPetAge(getPetAge(tmp_name)));
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
释放宠物,即生成悬浮窗。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 释放宠物
bn_showPet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 传宠物参数到Service
Intent intent = new Intent(MainActivity.this, PetWindowService.class);
intent.putExtra("pet_name", showPetName.getText().toString())
.putExtra("pet_mode", showPetMode.getText().toString())
.putExtra("pet_age", String.valueOf(getPetAge(showPetName.getText().toString())));
startService(intent);
// finish();
Log.d(TAG, "onClick: 成功释放");
}
});
收回宠物。
1
2
3
4
5
6
7
8
// 收回宠物
bn_backPet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, PetWindowService.class);
stopService(intent);
}
});
删除宠物:删除适配器和数据库对应宠物。
1
2
3
4
5
6
7
public void removePet(String name) {
// 删除适配器对应宠物对象
petNameAdapter.remove(name);
// 删除数据库对应宠物数据
usePetDB.removePet(name);
}
详细代码见github。