Android实战:在桌面显示动画

项目GitHub地址:https://github.com/liaozhoubei/Rocket

这次我们要做一个简单的Demo,用于在桌面显示动画,效果如下:


rocket.gif

一个简单的火箭发射的小动画,在虚拟机上运行有点卡顿,但是大致的效果就是这样了。
效果是不是很炫,其中包含了以下几个知识点:

  • 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容