项目GitHub地址:https://github.com/liaozhoubei/Rocket
这次我们要做一个简单的Demo,用于在桌面显示动画,效果如下:
一个简单的火箭发射的小动画,在虚拟机上运行有点卡顿,但是大致的效果就是这样了。
效果是不是很炫,其中包含了以下几个知识点:
- Service
- WindowManager
- Drawable Animation和AlphaAnimation
- 触摸点击事件。
好了,话不多说,我们直接上代码吧!
MainActivity
首先创建一个Android project,然后在MainActivity设置两个按钮点击时间,一个用于打开发射火箭的服务,一个用于关闭火箭的服务,由于界面简单,所以就不layout的代码了
MainActivity代码:
public class MainActivity extends Activity implements OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startRocket = (Button) findViewById(R.id.startRocket);
Button endRocket = (Button) findViewById(R.id.endRocket);
startRocket.setOnClickListener(this);
endRocket.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.startRocket:
//开启小火箭服务
startService(new Intent(this,RocketService.class));
finish();
break;
case R.id.endRocket:
//关闭小火箭服务
stopService(new Intent(this,RocketService.class));
finish();
break;
default:
break;
}
}
}
RocketService
创建一个Service,由于火箭是在手机桌面中运行,所以必须进驻后台。
RocketService代码:
public class RocketService extends Service {
private int widthPixels;
private int heightPixels;
private WindowManager.LayoutParams params;
private WindowManager windowManager;
private View view;
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
DisplayMetrics outMetrics= new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(outMetrics);
widthPixels = outMetrics.widthPixels;
heightPixels = outMetrics.heightPixels;
view = View.inflate(getApplicationContext(), R.layout.rocket, null);
ImageView iv_rocket = (ImageView)view.findViewById(R.id.iv_rocket);
params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
params.format = PixelFormat.TRANSLUCENT;
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ;
params.gravity = Gravity.LEFT | Gravity.TOP;
AnimationDrawable animationDrawable = (AnimationDrawable) iv_rocket.getBackground();
animationDrawable.start();
windowManager.addView(view, params );
setTouch();
}
Ok,以上是RocketService中的全部代码,现在我们来详解这些代码的含义吧。
1、关于WindowManager
这是一个全局的窗口管理者,如果想在手机桌面显示图案,就必须使用WindowManager的addView()方法将布局加载,这样才能显示
2、关于DisplayMetrics
由于Android手机的屏幕太多,分辨率也各不一致,很难一次获取到所有手机的屏幕大小,因此需要使用DisplayMetrics类来获取手机屏幕的高度和宽度
3、WindowManager.LayoutParams
这是挺好理解的一个参数,就像普通视图一样,一个控件放在一个视图中是有大小以及放在布局的那个地方的。这个参数就是来设置空间在窗口之中的参数的。
其中有以下代码:
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ;
这一段代码表示的是小火箭这个空间在屏幕中不会获得焦点,但是会在屏幕中一直出现。
params.gravity = Gravity.LEFT | Gravity.TOP;
这行代码表示的是视图在开始时位于窗口的左上角。
4、获取小火箭的布局
我们需要在桌面展示一个小火箭,那么就需要有个rocket的layout,代码如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/iv_rocket"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rocket" />
</RelativeLayout>
4、AnimationDrawable
在RocketService中有一段代码:
AnimationDrawable animationDrawable = (AnimationDrawable) iv_rocket.getBackground();
animationDrawable.start();
这段代码是帧动画,帧动画的资源是由res资源目录下的drawable目录下创建一个rocket.xml的文件决定的,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/desktop_rocket_launch_1" android:duration="200"/>
<item android:drawable="@drawable/desktop_rocket_launch_2" android:duration="200"/>
</animation-list>
这个文件是作为iv_rocket这个ImageView的background出现,所以需要从iv_rocket中获取。
所谓帧动画,就是一帧一帧的动画,我们这里是由两张图片不断循环组成的动画,及desktop_rocket_launch_1.png和desktop_rocket_launch_2.png。
6、最后我们在windowManager.addView(view, params );加载布局和设定好的参数,这个时候我们可以点开服务,发现可爱的火箭动画已经能出现在在桌面之中了!
当然了,现在我们仅仅是出现了一个火箭,但是它并不能随着手指移动而变化,也不能发射,因为我们还没有把setTouch()这个触摸方法完成嘛。
加入触摸方法
setTouch()方法的代码如下:
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
params.y -= 10;
windowManager.updateViewLayout(view, params);
};
};
}
private void setTouch() {
view.setOnTouchListener(new OnTouchListener() {
private int startX;
private int startY;
@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 newX = (int) event.getRawX();
int newY = (int) event.getRawY();
int dx = newX - startX;
int dy = newY - startY;
params.x += dx;
params.y += dy;
if (params.y < 0) {
params.y = 0;
}
windowManager.updateViewLayout(view, params);
startX = newX;
startY = newY;
break;
case MotionEvent.ACTION_UP:
if (params.y > 290 && params.x > 100 && params.x < 200) {
sendRocket();
Intent intent = new Intent(RocketService.this, BackGroundActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
break;
default:
break;
}
return true;
}
private void sendRocket() {
for(int i = 0; i < 45; i ++) {
new Thread(){
public void run() {
SystemClock.sleep(200);
handler.sendEmptyMessage(0);
};
}.start();
}
}
});
}
我们可以看到这个触摸方法与普通的点击事件稍微有点不同,因为它里面重写了一个onTouch()的方法,而这个方法是按照手指的触摸事件划分区域的。
一般来说触摸事件分为三种,即按下的时候,移动的时候,手指离开屏幕的时候。在这里也是如此。
我们在手指按下的时候获得手指按下时的坐标。
然后在手指移动的时候再次获得手指移动时的左边,然后减去开始的时候的坐标便获得了手指移动了多少距离,将这个距离赋予params.x和 params.y,然后通过windowManager.updateViewLayout(view, params)重新加载布局,这个时候就实现了小火箭能够随着手指在屏幕的移动而移动的效果。
最后我们需要手指在离开屏幕的时候火箭能够发射,其中的原理很简单,只需要改变小火箭在Y轴上的坐标便能够实现了。
但是我们不能让火箭随便发送,因此限定了火箭负责params.y > 290 && params.x > 100 && params.x < 200的条件时才能发射。
同时小火箭发射的速度不能太快,负责就看不到发射的效果了,因此我们设置火箭每隔200毫秒停顿一下,方便看到效果。但由于在主线程中不能停顿,所以使用Handler,每隔200毫秒发送空消息到Handler,然后在Handler中更新火箭的位置。
至此,我们的火箭发射效果也完成了。但是我们还有烟雾效果没有完成。
火箭发射烟雾效果
火箭发射的烟雾效果实际上由两张图片组成的渐变动画,它们在BackGroundActivity中,在火箭发射的时候直接启动这个Activity,从而达到效果。
BackGroundActivity的布局很简单就只是RelativeLayout包裹着两张图标,在这里就不写了。
BackGroundActivity代码:
public class BackGroundActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_background);
ImageView smoke_m = (ImageView) findViewById(R.id.smoke_m);
ImageView smoke_t = (ImageView) findViewById(R.id.smoke_t);
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(200);
smoke_m.startAnimation(alphaAnimation);
smoke_t.startAnimation(alphaAnimation);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, 1000);
}
}
BackGroundActivity中有AlphaAnimation,其中new AlphaAnimation(0, 1);代表着这个动画的效果是从透明到不透明的效果。
当然开启了这个Activity之后必须要销毁它,我们使用Handler延迟1秒钟发送销毁命令就行了。
需要注意的是之前在Rocket启动BackGroundActivity时,我们并没有如同普通的Activity之间相互启动那样,而是添加了:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
这行代码。这是因为service在Android中是不存在任务栈的,而打开Activity这是需要一个任务栈。因此如果不加这行代码会导致程序崩溃。
OK,这个项目就到这里为止。
当然了,在代码写完之后记得在mainfest中注册Activity和service,已经添加权限
权限代码如下:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
这个权限表示要在所有的窗口之前都能运行。
注册Activity和Service的代码如下:
<service android:name="com.example.rocket.RocketService"></service>
<activity android:name="com.example.rocket.BackGroundActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"></activity>
android:theme="@android:style/Theme.Translucent.NoTitleBar">这行代码的意思是需要一个透明的没有标题栏的主题
项目GitHub地址:https://github.com/liaozhoubei/Rocket