仿iPhone辅助球实现


示例图片:

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

本文建议零售价:¥ 5.00 元