随着Android手机的屏幕越来越大,Android浮动窗口的应用也越来越多。像经常会用到的,音乐播放器的桌面歌词以及一些手机卫士软件,像腾讯手机管家的小火箭清理内存,都应用到了浮动窗口的原理,今天拿来桌面歌词做一个简单的记录,举一反三即可实现类似的应用。效果图如下:
一、浮动窗口的实现
1.首先我们要申请权限,以便我们可以实现浮动窗口的拖拽
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />2.接下来,我们在service中的OnCreate方法显示浮动窗口,在OnDestory方法中关闭浮动窗口,这样我们的浮动窗口就可以与Service保持相同的生命周期。
3.显示浮动窗口:首先通过getApplicationContext().getSystemService(WINDOW_SERVICE)方法我们可以获得窗口管理类,接下来我们需要设置窗口的params,设置其类型为系统级,否则无法显示;设置焦点,否则无法获得触摸事件;最后通过wm.addView(tv, params)方法将我们的View添加到窗口中。
4.窗口的拖拽:在这里我们重新写一个TextView来放置我们的歌词,并在onTouchEvent中响应触摸事件,获得触摸点的移动来计算触摸点的移动,并重新设置窗口的位置,在这里需要注意,MotionEvent中的RawX,RawY是相对屏幕左上角的坐标(包括状态栏高度),而X,Y是相对于容器本身的坐标,即TextView左上角的坐标。这样利用RawX-X既可以得到TextView左上角点的屏幕x坐标,RawY-Y-状态栏高即可获得TextView左上角点的屏幕y坐标。之后我们调用wm.updateViewLayout(this, params)进行更新。
二、渲染歌词的实现
通过一个Shader shader = new LinearGradient(0, 0, len, 0, new int[] {Color.YELLOW, Color.RED }, new float[] { one, two },TileMode.CLAMP)可以进行歌词的渲染,其中前四个参数表示从哪里渲染到哪里,第5个参数为渲染的两种不同颜色, 第6个参数表示渲染的相对长度,范围从0到1, 第7个参数表示模式。我们可以通过一个异步线程不断更新one和two的值,并调用postInvalidate方法更新界面。
代码实现:
MainActivity类:
package com.example.windowtest;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.example.windowtest.service.TestService;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((Button) findViewById(R.id.button1))
.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Intent serviceIntent = new Intent(MainActivity.this,
TestService.class);
startService(serviceIntent);
}
});
((Button) findViewById(R.id.button2))
.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Intent serviceIntent = new Intent(MainActivity.this,
TestService.class);
stopService(serviceIntent);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
WindowText类:实现TextView显示歌词package com.example.windowtest.widget;
import java.lang.reflect.Field;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.widget.TextView;
public class WindowText extends TextView {
private static final String TAG = WindowText.class.getSimpleName();
public static WindowManager.LayoutParams params = new WindowManager.LayoutParams();
private float startX;
private float startY;
private float one = 0.0f;
private float two = 0.01f;
private WindowManager wm;
private String text;
private int statusBarHeight;
public WindowText(Context context) {
super(context);
// handler.post(update);
wm = (WindowManager) getContext().getApplicationContext()
.getSystemService(Context.WINDOW_SERVICE);
updateTextThread.start();
statusBarHeight = getStatusBarHeight();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 触摸点相对于屏幕左上角坐标
float x = event.getRawX();
float y = event.getRawY() - statusBarHeight;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.w(TAG, "x::" + startX + ",y::" + startY);
Log.w(TAG, "rawx::" + x + ",rawy::" + y);
case MotionEvent.ACTION_UP:
updatePosition(x - startX, y - startY);
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
float len = getTextSize() * text.length();
/*
* 渲染歌词,前四个参数表示从哪里渲染到哪里,第5个参数为渲染的两种不同颜色, 第6个参数表示渲染的相对位置,范围从0到1 第7个参数表示模式
*/
Shader shader = new LinearGradient(0, 0, len, 0, new int[] {
Color.YELLOW, Color.RED }, new float[] { one, two },
TileMode.CLAMP);
Paint p = new Paint();
p.setShader(shader);
p.setTextSize(getTextSize());
canvas.drawText(text, 0, getTextSize(), p);
}
// 通过一个异步线程来控制歌词渲染的速度
private Thread updateTextThread = new Thread() {
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
while (true) {
one += 0.001f;
two += 0.001f;
if (two > 1.0) {
one = 0.0f;
two = 0.01f;
}
postInvalidate();
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
}
}
};
// 更新浮动窗口位置参数
private void updatePosition(float x, float y) {
// View的当前位置
params.x = (int) x;
params.y = (int) y;
wm.updateViewLayout(this, params);
}
// 获得状态栏高度
private int getStatusBarHeight() {
Class<?> c = null;
Object obj = null;
Field field = null;
int x = 0;
try {
c = Class.forName("com.android.internal.R$dimen");
obj = c.newInstance();
field = c.getField("status_bar_height");
x = Integer.parseInt(field.get(obj).toString());
return getResources().getDimensionPixelSize(x);
} catch (Exception e1) {
e1.printStackTrace();
return 75;
}
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
Service类:package com.example.windowtest.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.view.Gravity;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import com.example.windowtest.widget.WindowText;
public class TestService extends Service {
private WindowManager wm;
private WindowText tv;
public TestService() {
// TODO Auto-generated constructor stub
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
wm = (WindowManager) getApplicationContext().getSystemService(
WINDOW_SERVICE);
showWindow();
}
// 显示浮动窗口
private void showWindow() {
WindowManager.LayoutParams params = WindowText.params;
params.type = LayoutParams.TYPE_SYSTEM_ALERT
| LayoutParams.TYPE_SYSTEM_OVERLAY;// 设置窗口类型为系统级
params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE;// 设置窗口焦点
params.width = WindowManager.LayoutParams.FILL_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.alpha = 80;
params.gravity = Gravity.LEFT | Gravity.TOP;
// 以屏幕左上角为原点,设置x、y初始值,将悬浮窗口设置在屏幕中间的位置
params.x = 0;
params.y = wm.getDefaultDisplay().getHeight() / 2;
tv = new WindowText(TestService.this);
tv.setTextSize(20);
tv.setText("难以忘记初次见你,一双迷人的眼睛");
wm.addView(tv, params);
}
// service退出时关闭浮动窗口
@Override
public void onDestroy() {
// TODO Auto-generated method stub
WindowManager wm = (WindowManager) getApplicationContext()
.getSystemService(WINDOW_SERVICE);
if (tv != null && tv.isShown()) {
wm.removeView(tv);
}
super.onDestroy();
}
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
}
原文:http://blog.csdn.net/smbroe/article/details/43570891