Android Floating App的实现

本文实现了一个Floating App,实际上是一个service,通过Service创建一个Window,为了保证这个window能够放置在Activity窗口之上,设定它的窗口类型为TYPE_SYSTEM_ALERT。

为了保证能够在屏幕上正常拖动并且在点击时又能正确响应这个window中的子控件的事件,重写了这个window的布局。不多说了,上代码(不包含布局)。
Floating App的manifest文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="aaa.bbb.ccc"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <service android:name=".FloatingService" >
            <intent-filter>
                <action android:name="aaa.bbb.ccc.FloatingService" />
            </intent-filter>
         </service>
    </application>
	<uses-permission
		android:name="android.permission.SYSTEM_ALERT_WINDOW" />
</manifest>
需要注意的是,需要这个permission。
android:name="android.permission.SYSTEM_ALERT_WINDOW"
如果需要其他应用启动这个FloatingService,发隐式的Intent就可以了。

FloatingService的代码

public class FloatingService extends Service {

	private WindowManager mWindowManager;
	private LayoutInflater mInflater;
	private WindowManager.LayoutParams mLayoutParams;
	private FloatingLayout mFloatingLayout;
	
	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		makeFloatingView();
	}

	@Override
	public void onDestroy() {
		mWindowManager.removeView(mFloatingKeyLayout);
		super.onDestroy();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		return super.onStartCommand(intent, flags, startId);
	}
	
	private void makeFloatingView() {
		mWindowManager = (WindowManager) getApplicationContext().getSystemService("window");
		mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		
		mLayoutParams = new WindowManager.LayoutParams();
		mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
		mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
		mLayoutParams.format = PixelFormat.RGBA_8888;
		mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
		mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
		mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
		mLayoutParams.x = 50;
		mLayoutParams.y = 50;
		
		mFloatingLayout = (FloatingLayout)mInflater.inflate(R.layout.float_layout, null);
		mWindowManager.addView(mFloatingKeyLayout, mLayoutParams);
		
	}
}

重写的布局文件,这里继承了LinearLayout

public class FloatingLayout extends LinearLayout implements
		View.OnClickListener {
	
	public static final String TAG = "FloatingLayout";


	private Context mContext;
	private Button mBtnXxx;

	private boolean mIsBeingDragged = false;
	/**
	 * Position of the last motion event.
	 */
	private float mLastMotionX;
	private float mLastMotionY;
	private final int mTouchSlop = 5;

	private WindowManager mWindowManager;
	private WindowManager.LayoutParams mLayoutParams;

	public FloatingKeyLayout(Context context) {
		super(context);
		mContext = context;
	}

	public FloatingKeyLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mContext = context;
	}

	public FloatingKeyLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
	}

	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
                //取得这个LinearLayout中的各个子控件并设定事件监听器 		
                mBtnXxx = (Button) findViewById(R.id.xxx);
		mBtnXxx.setOnClickListener(this);
                ......
               
	}


	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		if (mWindowManager == null) {
			mWindowManager = (WindowManager) getContext().getSystemService(
					Context.WINDOW_SERVICE);
			mLayoutParams = (WindowManager.LayoutParams) getLayoutParams();
		}
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {

		final int action = ev.getAction();
		if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
			return true;
		}

		switch (ev.getAction() & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_MOVE: {
			final float x = ev.getX();
			final float y = ev.getY();
			final int xDiff = (int) Math.abs(x - mLastMotionX);
			final int yDiff = (int) Math.abs(y - mLastMotionY);
			if (xDiff > mTouchSlop || yDiff > mTouchSlop) {
				mIsBeingDragged = true;
			}
			break;
		}

		case MotionEvent.ACTION_DOWN: {
			final float x = ev.getRawX();
			final float y = ev.getRawY();
			mLastMotionX = x;
			mLastMotionY = y;
			mIsBeingDragged = false;
			break;
		}

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			/* Release the drag */
			mIsBeingDragged = false;
			break;
		}
		return mIsBeingDragged;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction() & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_MOVE: {
			final float x = event.getRawX();
			final float y = event.getRawY();
			mLayoutParams.x = (int) (x - mLastMotionX + mLayoutParams.x);
			mLayoutParams.y = (int) (y - mLastMotionY + mLayoutParams.y);
			mWindowManager.updateViewLayout(this, mLayoutParams);
			mLastMotionX = x;
			mLastMotionY = y;
			break;
		}

		case MotionEvent.ACTION_DOWN: {
			final float x = event.getX();
			final float y = event.getY();
			mLastMotionX = x;
			mLastMotionY = y;
			break;
		}

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			mIsBeingDragged = false;
			break;
		}
		return true;
	}

	@Override
	public void onClick(View view) {
		switch (view.getId()) {
		case R.id.xxx: {
	                //do something
			break;
		}
		......
		}
	}

}

这里主要处理了拖动事件的拦截,对于Touch事件的传递机制不太清楚的,可以参考文章 Android Touch事件在View层级结构中的传递机制

注意:这个计算位置的时候使用的是event.getRawX(),而不是getX(),这里如果使用getX出现了抖动的现象。

它们的区别如下:

getX()是表示Widget相对于自身左上角的x坐标
而getRawX()是表示相对于屏幕左上角的x坐标值
(注意:这个屏幕左上角是手机屏幕左上角,不管activity是否有titleBar或是否全屏幕),getY(),getRawY()一样的道理。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值