Создание системного оверлея (всегда сверху)
Я пытаюсь создать постоянно активируемую кнопку / кликабельную картинку, которая постоянно остается поверх всех окон.
Доказательство концепции
- здесь - Smart Taskbar (на AppBrain)
- а вот V1.4.0 в стиле боковой панели SWKey - кнопка спасителя (на xda-developers)
Я добился успеха и теперь у меня есть работающий сервис. Сервис все время отображает текст в левом верхнем углу экрана, в то время как пользователь может свободно взаимодействовать с остальными приложениями в обычном режиме.
То, что я делаю, это подкласс ViewGroup
и добавьте его в корневой оконный менеджер с флагом TYPE_SYSTEM_OVERLAY
, Теперь я хочу добавить кнопку /clickable-изображение вместо этого текста, который может получать сенсорные события сам по себе. Я пытался переопределить "onTouchEvent" для всего ViewGroup
но он не получает никакого события.
Как я могу получать события только в определенных частях моей группы с постоянным просмотром? Пожалуйста, предложите.
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;
}
}
17 ответов
Это может быть глупым решением. Но это работает. Если вы можете улучшить его, пожалуйста, дайте мне знать.
На создание вашего сервиса: я использовал 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 блокирует определенные типы от прослушивания внешних событий касания, но некоторые типы, такие как 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()
в котором это добавит FLAG_NOT_FOCUSABLE
, FLAG_NOT_TOUCHABLE
и удалить FLAG_WATCH_OUTSIDE_TOUCH
флаги для TYPE_SYSTEM_OVERLAY
окна.
Это TYPE_SYSTEM_OVERLAY
окно не получит никакого сенсорного события на платформе ICS и, конечно, использовать TYPE_SYSTEM_OVERLAY
не является работоспособным решением для ICS и будущих устройств.
РАБОТАЙТЕ ВСЕГДА НА КНОПКАХ TOP IMAGE
прежде всего извините за мой английский
я редактирую ваши коды и заставляю рабочее изображение кнопки, которая слушает его сенсорное событие, не дать сенсорному управлению его фоновые элементы
также это дает слушателям прикосновение к другим элементам
кнопки внизу и слева
Вы можете изменить настройки, но вам нужно изменить кординацию в событии touch в элементе if
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;
}
}
Я один из разработчиков Tooleap SDK. Мы также предоставляем разработчикам возможность всегда отображать верхние окна и кнопки, и мы столкнулись с подобной ситуацией.
Одной из проблем, которые не были даны в ответах, является проблема защищенных кнопок Android.
Защищенные кнопки имеют filterTouchesWhenObscured
свойство, которое означает, что с ними нельзя взаимодействовать, если они размещены под окном, даже если это окно не получает никаких прикосновений. Цитирование документации Android:
Указывает, следует ли фильтровать касания, когда окно представления скрыто другим видимым окном. Когда установлено значение true, представление не будет получать прикосновения всякий раз, когда над окном представления появляется всплывающее окно, диалоговое окно или другое окно. Обратитесь к {@link android.view.View} документации по безопасности для получения дополнительной информации.
Примером такой кнопки является кнопка установки, когда вы пытаетесь установить сторонние apks. Любое приложение может отобразить такую кнопку, если добавить к макету вида следующую строку:
android:filterTouchesWhenObscured="true"
Если вы отображаете всегда поверх окна поверх "Защищенной кнопки", то все защищенные части кнопки, которые покрыты наложением, не будут обрабатывать никаких касаний, даже если это наложение не активировано. Поэтому, если вы планируете отображать такое окно, вы должны предоставить пользователю способ его переместить или закрыть. И если часть вашего оверлея прозрачна, учтите, что ваш пользователь может быть сбит с толку, почему определенная кнопка в базовом приложении не работает для него внезапно.
TYPE_SYSTEM_OVERLAY Эта константа устарела начиная с уровня API 26. Вместо этого используйте TYPE_APPLICATION_OVERLAY.
На самом деле вы можете попробовать WindowManager.LayoutParams.TYPE_SYSTEM_ERROR вместо TYPE_SYSTEM_OVERLAY. Это может звучать как хак, но оно позволяет отображать представление поверх всего и по-прежнему получать сенсорные события.
Попробуй это. Отлично работает в 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);
}
}
ОБНОВИТЬ
Образец здесь
Чтобы создать вид наложения, при настройке LayoutParams НЕ устанавливайте тип TYPE_SYSTEM_OVERLAY.
Instead set it to TYPE_PHONE.
Use the following flags:
FLAG_NOT_TOUCH_MODAL
FLAG_WATCH_OUTSIDE_TOUCH
Нашел библиотеку, которая делает именно это: https://github.com/recruit-lifestyle/FloatingView
В корневой папке есть пример проекта. Я запустил его, и он работает как требуется. Фон кликабелен - даже если это другое приложение.
Вот несколько простых решений. Все, что вам нужно, это накачать 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;
}
}
Это старый вопрос, но в последнее время Android поддерживает пузыри. Вскоре появятся пузыри, но сейчас разработчики могут начать их использовать. Они созданы как альтернатива использованиюSYSTEM_ALERT_WINDOW
. Такие приложения, как (Facebook Messenger и MusiXMatch используют ту же концепцию).
Пузыри создаются через API уведомлений, вы отправляете свое уведомление как обычно. Если вы хотите, чтобы он пузырился, вам нужно прикрепить к нему дополнительные данные. Для получения дополнительной информации о пузырях вы можете перейти к официальному руководству разработчика Android по пузырям.
Он использует полное руководство "android.permission.SYSTEM_ALERT_WINDOW" по этой ссылке: http://androidsrc.net/facebook-chat-like-floating-chat-heads/
Ответ @Sarwar Erfan больше не работает, поскольку Android не позволяет добавлять представление с WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY к окну, чтобы его можно было больше не трогать, даже с WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH.
Я нашел решение этой проблемы. Вы можете проверить это в следующем вопросе
Если кто-то все еще читает эту ветку и не может заставить ее работать, мне очень жаль сообщать вам, что этот способ перехвата события движения считается ошибкой и исправлением в Android >=4.2.
Событие движения, которое вы перехватили, хотя и имеет действие ACTION_OUTSIDE, возвращает 0 в getX и getY. Это означает, что вы не можете видеть все позиции движения на экране, и вы ничего не можете сделать. Я знаю, что док сказал, что получит х и у, но правда в том, что НЕ БУДЕТ. Кажется, что это заблокировать регистратор ключей.
Если у кого-то есть обходной путь, пожалуйста, оставьте свой комментарий.
ref: Почему ACTION_OUTSIDE каждый раз возвращает 0 на KitKat 4.4.2?
С помощью сервиса вы можете добиться этого:
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;
}
}
}
Хорошо, попробуйте мой код, по крайней мере, он дает вам строку в качестве наложения, вы можете очень хорошо заменить его кнопкой или изображением. Вы не поверите, что это мое первое приложение для Android LOL. В любом случае, если вы более опытны с приложениями для Android, чем я, пожалуйста, попробуйте
- изменение параметров 2 и 3 в "новом WindowManager.LayoutParams"
- попробуйте другой подход
TYPE_SYSTEM_OVERLAY
устарела и выдаст ошибку отказа в разрешении. Использовать
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
вместо.
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
может быть то, что вы ищете.
Флаг окна: даже если это окно фокусируется (его
FLAG_NOT_FOCUSABLE
не установлен), разрешить отправку любых событий указателя за пределами окна в окна за ним. В противном случае он сам будет использовать все события указателя, независимо от того, находятся ли они внутри окна.
Осуществлять
View.OnClickListener
или
View.onTouchListener
для прослушивания событий касания.