首先,得先说明,这个例子并不是我写的,是从eoeAndroid的一个帖子上面看到的,下载了他的源代码,然后分析一下,供学习共享。(另外,对于其代码有所修改,以便于更好的说明问题

一开始,我们先看一下运行效果:

lua悬浮窗教程 andlua悬浮窗模板_悬浮窗口

其中,

lua悬浮窗教程 andlua悬浮窗模板_状态栏_02

这一块就是悬浮窗,可以随意拖动,动态显示当前内存使用量。

下面看一下代码是如何实现的:

悬浮窗的实现是用了一个service,为什么要用service呢?了解service特点的大体就会明白。下面看一下:

publicclassFloatServiceextendsService {
WindowManager wm = null;
WindowManager.LayoutParams wmParams = null;
View view;
privatefloatmTouchStartX;
privatefloatmTouchStartY;
privatefloatx;
privatefloaty;
intstate;
TextView tx1;
TextView tx;
ImageView iv;
privatefloatStartX;
privatefloatStartY;
intdelaytime=1000;
@Override
publicvoidonCreate() {
Log.d("FloatService","onCreate");
super.onCreate();
view = LayoutInflater.from(this).inflate(R.layout.floating,null);
tx = (TextView) view.findViewById(R.id.memunused);
tx1 = (TextView) view.findViewById(R.id.memtotal);
tx.setText(""+ memInfo.getmem_UNUSED(this) +"KB");
tx1.setText(""+ memInfo.getmem_TOLAL() +"KB");
iv = (ImageView) view.findViewById(R.id.img2);
iv.setVisibility(View.GONE);
createView();
handler.postDelayed(task, delaytime);
}
privatevoidcreateView() {
// 获取WindowManager
wm = (WindowManager) getApplicationContext().getSystemService("window");
// 设置LayoutParams(全局变量)相关参数
wmParams = newWindowManager.LayoutParams();
wmParams.type = 2002;
wmParams.flags |= 8;
wmParams.gravity = Gravity.LEFT | Gravity.TOP; // 调整悬浮窗口至左上角
// 以屏幕左上角为原点,设置x、y初始值
wmParams.x = 0;
wmParams.y = 0;
// 设置悬浮窗口长宽数据
wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.format = 1;
wm.addView(view, wmParams);
view.setOnTouchListener(newOnTouchListener() {
publicbooleanonTouch(View v, MotionEvent event) {
// 获取相对屏幕的坐标,即以屏幕左上角为原点
x = event.getRawX();
y = event.getRawY() - 25;// 25是系统状态栏的高度
Log.i("currP","currX"+ x +"====currY"+ y);// 调试信息
switch(event.getAction()) {
caseMotionEvent.ACTION_DOWN:
state = MotionEvent.ACTION_DOWN;
StartX = x;
StartY = y;
// 获取相对View的坐标,即以此View左上角为原点
mTouchStartX = event.getX();
mTouchStartY = event.getY();
Log.i("startP","startX"+ mTouchStartX +"====startY"
+ mTouchStartY);// 调试信息
break;
caseMotionEvent.ACTION_MOVE:
state = MotionEvent.ACTION_MOVE;
updateViewPosition();
break;
caseMotionEvent.ACTION_UP:
state = MotionEvent.ACTION_UP;
updateViewPosition();
showImg();
mTouchStartX = mTouchStartY = 0;
break;
}
returntrue;
}
});
iv.setOnClickListener(newOnClickListener() {
@Override
publicvoidonClick(View v) {
// TODO Auto-generated method stub
Intent serviceStop = newIntent();
serviceStop.setClass(FloatService.this, FloatService.class);
stopService(serviceStop);
}
});
}
publicvoidshowImg() {
if(Math.abs(x - StartX) <1.5&& Math.abs(y - StartY) <1.5
&& !iv.isShown()) {
iv.setVisibility(View.VISIBLE);
} elseif(iv.isShown()) {
iv.setVisibility(View.GONE);
}
}
privateHandler handler =newHandler();
privateRunnable task =newRunnable() {
publicvoidrun() {
// TODO Auto-generated method stub
dataRefresh();
handler.postDelayed(this, delaytime);
wm.updateViewLayout(view, wmParams);
}
};
publicvoiddataRefresh() {
tx.setText(""+ memInfo.getmem_UNUSED(this) +"KB");
tx1.setText(""+ memInfo.getmem_TOLAL() +"KB");
}
privatevoidupdateViewPosition() {
// 更新浮动窗口位置参数
wmParams.x = (int) (x - mTouchStartX);
wmParams.y = (int) (y - mTouchStartY);
wm.updateViewLayout(view, wmParams);
}
@Override
publicvoidonStart(Intent intent,intstartId) {
Log.d("FloatService","onStart");
setForeground(true);
super.onStart(intent, startId);
}
@Override
publicvoidonDestroy() {
handler.removeCallbacks(task);
Log.d("FloatService","onDestroy");
wm.removeView(view);
super.onDestroy();
}
@Override
publicIBinder onBind(Intent intent) {
returnnull;
}
}

其主要功能部分在creatView方法里:

privatevoidcreateView() {
// 获取WindowManager
wm = (WindowManager) getApplicationContext().getSystemService("window");
// 设置LayoutParams(全局变量)相关参数
wmParams = newWindowManager.LayoutParams();
wmParams.type = 2002;
wmParams.flags |= 8;
wmParams.gravity = Gravity.LEFT | Gravity.TOP; // 调整悬浮窗口至左上角
// 以屏幕左上角为原点,设置x、y初始值
wmParams.x = 0;
wmParams.y = 0;
// 设置悬浮窗口长宽数据
wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.format = 1;
wm.addView(view, wmParams);
view.setOnTouchListener(newOnTouchListener() {
publicbooleanonTouch(View v, MotionEvent event) {
// 获取相对屏幕的坐标,即以屏幕左上角为原点
x = event.getRawX();
y = event.getRawY() - 25;// 25是系统状态栏的高度
Log.i("currP","currX"+ x +"====currY"+ y);// 调试信息
switch(event.getAction()) {
caseMotionEvent.ACTION_DOWN:
state = MotionEvent.ACTION_DOWN;
StartX = x;
StartY = y;
// 获取相对View的坐标,即以此View左上角为原点
mTouchStartX = event.getX();
mTouchStartY = event.getY();
Log.i("startP","startX"+ mTouchStartX +"====startY"
+ mTouchStartY);// 调试信息
break;
caseMotionEvent.ACTION_MOVE:
state = MotionEvent.ACTION_MOVE;
updateViewPosition();
break;
caseMotionEvent.ACTION_UP:
state = MotionEvent.ACTION_UP;
updateViewPosition();
showImg();
mTouchStartX = mTouchStartY = 0;
break;
}
returntrue;
}
});
iv.setOnClickListener(newOnClickListener() {
@Override
publicvoidonClick(View v) {
// TODO Auto-generated method stub
Intent serviceStop = newIntent();
serviceStop.setClass(FloatService.this, FloatService.class);
stopService(serviceStop);
}
});
}

首先,代码里面用到了WindowManager借口,整个Android的窗口机制是基于一个叫做 WindowManager,这个接口可以添加view到屏幕,也可以从屏幕删除view。它面向的对象一端是屏幕,另一端就是View,直接忽略我们以前的Activity或者Dialog之类的东东。其实我们的Activity或者Diolog底层的实现也是通过WindowManager,这个 WindowManager是全局的,整个系统就是这个唯一的东东。它是显示View的最底层了。(该段文字来自网络

lua悬浮窗教程 andlua悬浮窗模板_状态栏_03

)其方法很简单,基本用到的就三个addView,removeView,updateViewLayout。另:在设置View高度和宽度的时候一个错误,即在View的构造函数中获取getWidth()和getHeight(),当一个view对象创建时,android并不知道其大小,所以getWidth()和getHeight()返回的结果是0,真正大小是在计算布局时才会计算,所以会发现一个有趣的事,即在onDraw( ) 却能取得长宽的原因。使用一下方法即可:

width=activity.getWindowManager().getDefaultDisplay().getWidth();
height=activity.getWindowManager().getDefaultDisplay().getHeight();

下面是LayoutParams,设置他的属性:详情请看上一篇文章:

在这里是设置成了所有应用程序之上,状态栏之下的形式,当移动的时候,会调用case MotionEvent.ACTION_MOVE:

下面的代码主要是:

privatevoidupdateViewPosition() {
// 更新浮动窗口位置参数
wmParams.x = (int) (x - mTouchStartX);
wmParams.y = (int) (y - mTouchStartY);
wm.updateViewLayout(view, wmParams);

}从新设置浮动栏的位置参数。这样就实现了拖动的功能。其内存数据是如何获取及及时更新的呢?

我们注意到了handler:

handler.postDelayed(task, delaytime);privateRunnable task =newRunnable() {
        publicvoidrun() {
            // TODO Auto-generated method stub
            dataRefresh();
            handler.postDelayed(this, delaytime);
            wm.updateViewLayout(view, wmParams);
        }
    };

      我们找到dataRefresh方法,delaytime是设置的1000,也就是每一秒钟更新一次数据。

publicvoiddataRefresh() {
tx.setText(""+ memInfo.getmem_UNUSED(this) +"KB");
tx1.setText(""+ memInfo.getmem_TOLAL() +"KB");

}最后,看下memInfo的定义:

publicclassmemInfo {
publicstaticlonggetmem_UNUSED(Context mContext) {
longMEM_UNUSED;
ActivityManager am = (ActivityManager) mContext
.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mi = newActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
MEM_UNUSED = mi.availMem / 1024;
returnMEM_UNUSED;
}
publicstaticlonggetmem_TOLAL() {
longmTotal;
// 系统内存
String path = "/proc/meminfo";
// 存储器内容
String content = null;
BufferedReader br = null;
try{
br = newBufferedReader(newFileReader(path),8);
String line;
if((line = br.readLine()) !=null) {
// 采集内存信息
content = line;
}
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
} finally{
if(br !=null) {
try{
br.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
// beginIndex
intbegin = content.indexOf(':');
// endIndex
intend = content.indexOf('k');
// 采集数量的内存
content = content.substring(begin + 1, end).trim();
// 转换为Int型
mTotal = Integer.parseInt(content);
returnmTotal;
}
}

里面只定义了两个方法,获取总内存和使用内存。