效果如图,demo中我并没有做完全和iphone那样展开的菜单(因为懒)。
那么接下来就讲讲这个能漂浮在任意Activity、包括手机桌面上的手势菜单是如何实现的。
正文
首先从宏观原理上来讲,它就是一个跑在Service中的View。 从细节上来讲,主要有两个问题需要我们注意:
如何让一个Service中能够显示出View来。
如何让这个View做到相应手势事件(包括点击和拖动)
第一个问题:这要从Activity开始讲起了。
我们都知道,Android中的View并不包括Activity、ActionBar、Dialog、Notification等。Activity能够显示出一个View,是因为我们调用了setContentView()这个方法。再往深层看,Activity之所以能显示View是因为Activity本身使用has…a…关系包含了一个Window对象,而这个Window对象可以理解为一个屏幕,如果你有做过IOS开发,你一定能理解这个Window的含义,其实就是代表了当前屏幕。
通过这个Window对象,我们可以获得一个DecorView对象,这个DecorView对象是一个ViewGroup就是当前屏幕的根View,一切Activity的ContentView都会被add进这个DecorView容器中。
再回到上一个问题,View是如何显示出来的。
在Window对象中,有一个内部类叫做LocalWindowManager,这个内部类实现了一个接口叫WindowManager,而就是这个LocalWindowManager在实现WindowManager.addView()接口方法的时候,实现了在屏幕上绘制View的功能。
原理说了这么多,你也许已经糊涂了,这到底和Service显示View有什么关系。
其实很简单,因为Window对象负责显示我们的View,那么在Service中得到当前的Window对象,一切问题就解决了。
在Context中有一个获取当前系统管理者的方法叫getSystemService(String name);我们就通过这个方法来获取到Window对象中的WindowManager而这里得到的WindowManager对象就是上文提到的LocalWindowManager对象。通过给这个对象的addView()再添加一个我们的手势球View,就解决了View显示问题。
第二个问题:让一个View随着手指移动而移动。
有两种方法实现这种需求。
在《疯狂Android讲义》中有一章是介绍通过自定义控件的形式,重写ondraw()方法来实现,这是在View内部封装,这里我不讲了,希望了解的可以自己去看看电子书。我实现的方法是通过外部给View设置触摸事件监听setOnTouchListener(),然后通过当前手指所在屏幕的坐标,来动态修改View的margin边距来达到View改变位置的效果。
代码
具体实现,我们来这个核心Service类
public class TopFloatService extends Service implements OnClickListener {
WindowManager wm = null;
WindowManager.LayoutParams ballWmParams = null;
private float mTouchStartX;
private float mTouchStartY;
private float x;
private float y;
private View ballView; // 球状态View
private View menuView; // 菜单状态View
private PopupWindow pop;
private Button floatImage;
private RelativeLayout menuLayout; // 菜单的布局
private RelativeLayout menuTop;
private boolean ismoving = false;
@Override
public void onCreate() {
super.onCreate();
// 加载辅助球布局
ballView = View.inflate(this, R.layout.floatball, null);
floatImage = (Button) ballView.findViewById(R.id.float_image);
setUpFloatMenuView();
createView();
}
/**
* 窗口菜单初始化
*/
private void setUpFloatMenuView() {
menuView = View.inflate(this, R.layout.floatmenu, null);
menuLayout = (RelativeLayout) menuView.findViewById(R.id.menu);
menuTop = (RelativeLayout) menuView.findViewById(R.id.lay_main);
menuLayout.setOnClickListener(this);
menuTop.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.lay_main:
Toast.makeText(getApplicationContext(), "111", Toast.LENGTH_SHORT)
.show();
break;
default:
if (pop != null && pop.isShowing()) {
pop.dismiss();
}
break;
}
}
private void createView() {
wm = (WindowManager) getSystemService("window");
ballWmParams = new WindowManager.LayoutParams();
ballWmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
ballWmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
ballWmParams.gravity = Gravity.LEFT | Gravity.TOP;
ballWmParams.x = 0;
ballWmParams.y = 0;
ballWmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
ballWmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
ballWmParams.format = PixelFormat.RGBA_8888;
// 添加显示
wm.addView(ballView, ballWmParams);
// 注册触摸事件监听
floatImage.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
x = event.getRawX();
y = event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
ismoving = false;
mTouchStartX = (int) event.getX();
mTouchStartY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
ismoving = true;
updateViewPosition();
break;
case MotionEvent.ACTION_UP:
mTouchStartX = mTouchStartY = 0;
break;
}
// 如果拖动则返回false,否则返回true
if (ismoving == false) {
return false;
} else {
return true;
}
}
});
// 注册点击事件监听
floatImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 获取在xml中定义的大小
DisplayMetrics dm = getResources().getDisplayMetrics();
pop = new PopupWindow(menuView, dm.widthPixels, dm.heightPixels);
pop.showAtLocation(ballView, Gravity.CENTER, 0, 0);
pop.update();
}
});
}
/**
* 更新view的显示位置
*/
private void updateViewPosition() {
ballWmParams.x = (int) (x - mTouchStartX);
ballWmParams.y = (int) (y - mTouchStartY);
wm.updateViewLayout(ballView, ballWmParams);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}