仿iPhone辅助球实现



对本文有任何问题,可加我的个人微信询问:kymjs666

示例图片:

效果如图,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;
        }
    }