创build一个系统覆盖窗口(总在最上面)

我正在尝试创build一个始终保持在所有窗口顶部的始终为顶部的button/可点击图片。

概念的certificate是

  • 这里 – 智能任务栏(在AppBrain上)
  • 并在这里V1.4.0侧边栏风格SWKey – button救星(在XDA开发人员)

我已经成功,现在有一个运行服务。 该服务始终在屏幕的左上angular显示一些文本,而用户可以正常方式与其余应用程序自由交互。

我在做的是子类ViewGroup ,并将其添加到根窗口pipe理器与标志TYPE_SYSTEM_OVERLAY 。 现在我想添加一个button/可点击图像来代替可以接收触摸事件的文本。 我试图覆盖整个ViewGroup “onTouchEvent”,但它没有收到任何事件。

我怎样才能在我的永远在顶视图组的某些部分接收事件? 请build议。

 public class HUD extends Service { HUDView mView; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); Toast.makeText(getBaseContext(),"onCreate", Toast.LENGTH_LONG).show(); mView = new HUDView(this); WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, 0, // WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); params.gravity = Gravity.RIGHT | Gravity.TOP; params.setTitle("Load Average"); WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); wm.addView(mView, params); } @Override public void onDestroy() { super.onDestroy(); Toast.makeText(getBaseContext(),"onDestroy", Toast.LENGTH_LONG).show(); if(mView != null) { ((WindowManager) getSystemService(WINDOW_SERVICE)).removeView(mView); mView = null; } } } class HUDView extends ViewGroup { private Paint mLoadPaint; public HUDView(Context context) { super(context); Toast.makeText(getContext(),"HUDView", Toast.LENGTH_LONG).show(); mLoadPaint = new Paint(); mLoadPaint.setAntiAlias(true); mLoadPaint.setTextSize(10); mLoadPaint.setARGB(255, 255, 0, 0); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawText("Hello World", 5, 15, mLoadPaint); } @Override protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) { } @Override public boolean onTouchEvent(MotionEvent event) { //return super.onTouchEvent(event); Toast.makeText(getContext(),"onTouchEvent", Toast.LENGTH_LONG).show(); return true; } } 

这可能是一个愚蠢的解决scheme。 但它的作品。 如果你能改善它,请让我知道。

OnCreate你的服务:我已经使用了WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH标志。 这是服务中唯一的变化。

 @Override public void onCreate() { super.onCreate(); Toast.makeText(getBaseContext(),"onCreate", Toast.LENGTH_LONG).show(); mView = new HUDView(this); WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT); params.gravity = Gravity.RIGHT | Gravity.TOP; params.setTitle("Load Average"); WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); wm.addView(mView, params); } 

现在,你将开始获得每一个点击事件。 所以,你需要纠正你的事件处理程序。

在您的ViewGroup触摸事件

 @Override public boolean onTouchEvent(MotionEvent event) { // ATTENTION: GET THE X,Y OF EVENT FROM THE PARAMETER // THEN CHECK IF THAT IS INSIDE YOUR DESIRED AREA Toast.makeText(getContext(),"onTouchEvent", Toast.LENGTH_LONG).show(); return true; } 

另外,您可能需要将此权限添加到您的清单:

 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 

在@Sam Lu的回答之后,确实Android 4.x确实阻止了某些types的外部监听事​​件,但是一些types,比如TYPE_SYSTEM_ALERT仍然可以工作。

  WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT); WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); View myView = inflater.inflate(R.layout.my_view, null); myView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.d(TAG, "touch me"); return true; } }); // Add layout to window manager wm.addView(myView, params); 

权限

 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> 

从Android 4.x开始,Android团队通过添加一个新的函数adjustWindowParamsLw()来修复一个潜在的安全问题,在这个函数中,它将为TYPE_SYSTEM_OVERLAY窗口添加FLAG_NOT_FOCUSABLEFLAG_NOT_TOUCHABLE和删除FLAG_WATCH_OUTSIDE_TOUCH标志。

也就是说, TYPE_SYSTEM_OVERLAY窗口在ICS平台上不会收到任何触摸事件,当然,使用TYPE_SYSTEM_OVERLAY不是ICS和未来设备的可行解决scheme。

我是Tooleap SDK的开发人员之一。 我们也为开发人员提供了一种方法,总是显示在顶部的窗口和button上,而且我们也处理了类似的情况。

一个问题,这里的答案没有解决的是Android的“安全button”。

安全button具有filterTouchesWhenObscured属性,这意味着即使该窗口没有收到任何触摸,它们也不能与其交互。 引用Android文档:

指定当视图的窗口被另一个可见窗口遮挡时是否过滤触摸。 设置为true时,只要在视图窗口上方显示吐司,对话框或其他窗口,视图就不会接收到触摸。 有关更多详细信息,请参阅{@link android.view.View}安全性文档。

当您尝试安装第三方apks时,此button的一个示例是安装button。 任何应用程序可以显示这样的button,如果添加到视图布局下面的行:

 android:filterTouchesWhenObscured="true" 

如果您在“安全button”上方显示始终在顶部的窗口,则覆盖区覆盖的所有受保护的button部分将不会处理任何触摸,即使该覆盖区不可点击。 所以如果你打算显示这样一个窗口,你应该提供一个方法让用户移动它或解雇它。 如果您的覆盖层的一部分是透明的,请考虑到您的用户可能会感到困惑,为什么底层应用程序中的某个button突然没有为他工作。

始终在顶部的图像button上工作

首先抱歉我的英语

我编辑你的代码,并使侦听他的触摸事件的工作图像button不给他的背景元素的触摸控制。

也让触摸听众脱离其他元素

buttonalingments是底部和左侧

你可以chage alingments,但是你需要在if元素中触发cordinats touch事件

 import android.annotation.SuppressLint; import android.app.Service; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PixelFormat; import android.os.IBinder; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.Toast; public class HepUstte extends Service { HUDView mView; @Override public void onCreate() { super.onCreate(); Toast.makeText(getBaseContext(),"onCreate", Toast.LENGTH_LONG).show(); final Bitmap kangoo = BitmapFactory.decodeResource(getResources(), R.drawable.logo_l); WindowManager.LayoutParams params = new WindowManager.LayoutParams( kangoo.getWidth(), kangoo.getHeight(), WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT); params.gravity = Gravity.LEFT | Gravity.BOTTOM; params.setTitle("Load Average"); WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); mView = new HUDView(this,kangoo); mView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View arg0, MotionEvent arg1) { // TODO Auto-generated method stub //Log.e("kordinatlar", arg1.getX()+":"+arg1.getY()+":"+display.getHeight()+":"+kangoo.getHeight()); if(arg1.getX()<kangoo.getWidth() & arg1.getY()>0) { Log.d("tıklandı", "touch me"); } return false; } }); wm.addView(mView, params); } @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } } @SuppressLint("DrawAllocation") class HUDView extends ViewGroup { Bitmap kangoo; public HUDView(Context context,Bitmap kangoo) { super(context); this.kangoo=kangoo; } protected void onDraw(Canvas canvas) { //super.onDraw(canvas); // delete below line if you want transparent back color, but to understand the sizes use back color canvas.drawColor(Color.BLACK); canvas.drawBitmap(kangoo,0 , 0, null); //canvas.drawText("Hello World", 5, 15, mLoadPaint); } protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) { } public boolean onTouchEvent(MotionEvent event) { //return super.onTouchEvent(event); // Toast.makeText(getContext(),"onTouchEvent", Toast.LENGTH_LONG).show(); return true; } } 

尝试这个。 在ICS中工作正常。 如果您想要停止服务,只需单击状态栏中生成的通知。

  public class HUD extends Service { protected boolean foreground = false; protected boolean cancelNotification = false; private Notification notification; private View myView; protected int id = 0; private WindowManager wm; private WindowManager.LayoutParams params; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); // System.exit(0); Toast.makeText(getBaseContext(),"onCreate", Toast.LENGTH_SHORT).show(); params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); params.gravity=Gravity.TOP|Gravity.LEFT; wm = (WindowManager) getSystemService(WINDOW_SERVICE); inflateview(); foregroundNotification(1); //moveToForeground(1,n,true); } @Override public void onDestroy() { super.onDestroy(); ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(0); Toast.makeText(getBaseContext(),"onDestroy", Toast.LENGTH_SHORT).show(); if(myView != null) { ((WindowManager) getSystemService(WINDOW_SERVICE)).removeView(myView); myView = null; } } protected Notification foregroundNotification(int notificationId) { notification = new Notification(R.drawable.ic_launcher, "my Notification", System.currentTimeMillis()); notification.flags = notification.flags | Notification.FLAG_ONGOING_EVENT | Notification.FLAG_ONLY_ALERT_ONCE; notification.setLatestEventInfo(this, "my Notification", "my Notification", notificationIntent()); ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).notify(id, notification); return notification; } private PendingIntent notificationIntent() { Intent intent = new Intent(this, stopservice.class); PendingIntent pending = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); return pending; } public void inflateview() { LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); myView = inflater.inflate(R.layout.activity_button, null); myView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Toast.makeText(getBaseContext(),"onToasttt", Toast.LENGTH_SHORT).show(); return false; } }); // Add layout to window manager wm.addView(myView, params); } } 

UPDATE

在这里示例

要创build叠加视图,在设置LayoutParams时不要将types设置为TYPE_SYSTEM_OVERLAY。

 Instead set it to TYPE_PHONE. Use the following flags: FLAG_NOT_TOUCH_MODAL FLAG_WATCH_OUTSIDE_TOUCH 

实际上,你可以尝试WindowManager.LayoutParams.TYPE_SYSTEM_ERROR而不是TYPE_SYSTEM_OVERLAY。 这可能听起来像一个黑客攻击,但它可以让你展示一切的顶部,并仍然得到触摸事件。

它使用权限“android.permission.SYSTEM_ALERT_WINDOW”完整的教程在这个链接: http : //androidsrc.net/facebook-chat-like-floating-chat-heads/

下面是一些简单的解决scheme,您只需要像在列表适配器上一样膨胀XML布局,然后使用XML布局来扩充它。 这里是你需要的代码。

  public class HUD extends Service { View mView; LayoutInflater inflate; TextView t; Button b; @Override public void onCreate() { super.onCreate(); Toast.makeText(getBaseContext(),"onCreate", Toast.LENGTH_LONG).show(); WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); get phone display size int width = display.getWidth(); // deprecated - get phone display width int height = display.getHeight(); // deprecated - get phone display height WindowManager.LayoutParams params = new WindowManager.LayoutParams( width, height, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT); params.gravity = Gravity.LEFT | Gravity.CENTER; params.setTitle("Load Average"); inflate = (LayoutInflater) getBaseContext() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); mView = inflate.inflate(R.layout.canvas, null); b = (Button) mView.findViewById(R.id.button1); t = (TextView) mView.findViewById(R.id.textView1); b.setOnClickListener(new OnClickListener() { public void onClick(View v) { // TODO Auto-generated method stub t.setText("yes you click me "); } }); wm.addView(mView, params); } @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } } 

那么试试我的代码,至less它给你一个string作为覆盖,你可以很好地用一个button或图像replace它。 你不会相信这是我的第一个Android应用程序大声笑。 无论如何,如果你比我更有经验的android应用程序,请尝试

  • 在“新buildWindowManager.LayoutParams”中更改参数2和3
  • 尝试一些不同的事件方法

如果任何人仍然阅读这个线程,并不能得到这个工作,我很抱歉告诉你这种方法来拦截运动事件被认为是错误和修复android> = 4.2。

你拦截的运动事件虽然具有ACTION_OUTSIDE的作用,但在getX和getY中返回0。 这意味着你不能在屏幕上看到所有的动作位置,你也不能做任何事情。 我知道文件说,它会得到x和y,但事实是它不会。 看来这是阻止关键logging器。

如果有人有解决办法,请留下您的评论。

ref: 为什么ACTION_OUTSIDE每次在KitKat 4.4.2上都返回0?

https://code.google.com/p/android/issues/detail?id=72746

通过使用服务,你可以做到这一点:

 public class PopupService extends Service{ private static final String TAG = PopupService.class.getSimpleName(); WindowManager mWindowManager; View mView; String type ; @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { // registerOverlayReceiver(); type = intent.getStringExtra("type"); Utils.printLog("type = "+type); showDialog(intent.getStringExtra("msg")); return super.onStartCommand(intent, flags, startId); } private void showDialog(String aTitle) { if(type.equals("when screen is off") | type.equals("always")) { Utils.printLog("type = "+type); PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE); WakeLock mWakeLock = pm.newWakeLock((PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP), "YourServie"); mWakeLock.acquire(); mWakeLock.release(); } mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); mView = View.inflate(getApplicationContext(), R.layout.dialog_popup_notification_received, null); mView.setTag(TAG); int top = getApplicationContext().getResources().getDisplayMetrics().heightPixels / 2; LinearLayout dialog = (LinearLayout) mView.findViewById(R.id.pop_exit); // android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) dialog.getLayoutParams(); // lp.topMargin = top; // lp.bottomMargin = top; // mView.setLayoutParams(lp); final EditText etMassage = (EditText) mView.findViewById(R.id.editTextInPopupMessageReceived); ImageButton imageButtonSend = (ImageButton) mView.findViewById(R.id.imageButtonSendInPopupMessageReceived); // lp = (LayoutParams) imageButton.getLayoutParams(); // lp.topMargin = top - 58; // imageButton.setLayoutParams(lp); imageButtonSend.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Utils.printLog("clicked"); // mView.setVisibility(View.INVISIBLE); if(!etMassage.getText().toString().equals("")) { Utils.printLog("sent"); etMassage.setText(""); } } }); TextView close = (TextView) mView.findViewById(R.id.TextViewCloseInPopupMessageReceived); close.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { hideDialog(); } }); TextView view = (TextView) mView.findViewById(R.id.textviewViewInPopupMessageReceived); view.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { hideDialog(); } }); TextView message = (TextView) mView.findViewById(R.id.TextViewMessageInPopupMessageReceived); message.setText(aTitle); final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD // | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON , PixelFormat.RGBA_8888); mView.setVisibility(View.VISIBLE); mWindowManager.addView(mView, mLayoutParams); mWindowManager.updateViewLayout(mView, mLayoutParams); } private void hideDialog(){ if(mView != null && mWindowManager != null){ mWindowManager.removeView(mView); mView = null; } } } 

@Sarwar Erfan的答案不再工作,因为Android不允许添加视图与WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY窗口可以触摸了,甚至不用WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH。

我find了解决这个问题的方法。 你可以在下面的问题中看看

当使用WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY将视图添加到窗口时,没有获得触摸事件