J'essaie de créer un bouton toujours visible/une image cliquable Qui reste constamment au-dessus de toutes les fenêtres.
La preuve du concept Est
J'ai réussi et j'ai un service en cours maintenant. Le service Affiche du texte dans le coin supérieur gauche de l'écran tout le temps, tandis que L'utilisateur peut librement interagir avec le reste des applications de manière normale.
Ce que je suis en train de faire est de créer une sous-classe ViewGroup
et de l'ajouter au gestionnaire de fenêtres racine avec Flag TYPE_SYSTEM_OVERLAY
. Maintenant, je veux ajouter un bouton/une image cliquable À la place de ce texte qui peut recevoir des événements tactiles sur lui-même. J'ai essayé de remplacer "onTouchEvent" pour la totalité de la variable ViewGroup
mais il ne reçoit aucun événement.
Comment puis-je recevoir des événements uniquement sur certaines parties .__ de mon groupe de vues toujours au top? Suggère de bien vouloir.
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;
}
}
Cela pourrait être une solution stupide. Mais ça marche. Si vous pouvez l'améliorer, s'il vous plaît faites le moi savoir.
OnCreate de votre service: J'ai utilisé le drapeau WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
. C'est le seul changement de service.
@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);
}
Maintenant, vous commencerez à recevoir chaque événement de clic. Donc, vous devez rectifier dans votre gestionnaire d’événements.
Dans votre événement tactile 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;
}
Aussi, vous devrez peut-être ajouter cette permission à votre manifeste:
<uses-permission Android:name="Android.permission.SYSTEM_ALERT_WINDOW" />
Après la réponse de @Sam Lu, En effet, Android 4.x empêche certains types d'écouter des événements tactiles extérieurs, mais certains types, tels que TYPE_SYSTEM_ALERT
, fonctionnent toujours.
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"/>
À partir de Android 4.x, l’équipe Android a corrigé un problème de sécurité potentiel en ajoutant une nouvelle fonction adjustWindowParamsLw()
dans laquelle il ajoutait FLAG_NOT_FOCUSABLE
, FLAG_NOT_TOUCHABLE
et supprimait les drapeaux FLAG_WATCH_OUTSIDE_TOUCH
pour les fenêtres TYPE_SYSTEM_OVERLAY
.
C'est-à-dire qu'une fenêtre TYPE_SYSTEM_OVERLAY
ne recevra aucun événement tactile sur la plate-forme ICS et, bien sûr, utiliser TYPE_SYSTEM_OVERLAY
n'est pas une solution viable pour les périphériques ICS et les futurs.
Je suis l’un des développeurs du Tooleap SDK . Nous fournissons également aux développeurs un moyen d’afficher toujours sur les fenêtres et les boutons du haut, et nous avons fait face à une situation similaire.
Un problème que les réponses ici n’ont pas abordé est celui de l’Android "Secured Buttons".
Les boutons sécurisés ont la propriété filterTouchesWhenObscured
, ce qui signifie qu'ils ne peuvent pas être interagis s'ils sont placés sous une fenêtre, même si cette fenêtre ne reçoit aucune touche. Citant la documentation Android:
Spécifie s'il faut filtrer les contacts lorsque la fenêtre de la vue est masquée par une autre fenêtre visible. Lorsqu'il est défini sur true, la vue ne recevra pas touche chaque fois qu'un pain grillé, une boîte de dialogue ou une autre fenêtre apparaît au-dessus du fenêtre de vue. Reportez-vous à la sécurité {@link Android.view.View} documentation pour plus de détails.
Un exemple d'un tel bouton est le bouton install lorsque vous essayez d'installer des apks tiers. Toute application peut afficher un tel bouton si vous ajoutez à la disposition de la vue la ligne suivante:
Android:filterTouchesWhenObscured="true"
Si vous affichez une fenêtre toujours visible sur un "bouton sécurisé", toutes les parties de bouton sécurisées qui sont recouvertes par une superposition ne gèreront aucune touche, même si cette superposition n'est pas cliquable. Donc, si vous envisagez d’afficher une telle fenêtre, vous devez fournir à l’utilisateur un moyen de la déplacer ou de la supprimer . Si une partie de votre superposition est transparente, prenez en compte le fait que votre utilisateur risque d’être confondu. un certain bouton de l'application sous-jacente ne fonctionne pas soudainement pour lui.
TRAVAILLER TOUJOURS SUR LE BOUTON D'IMAGE SUPÉRIEUR
tout d'abord désolé pour mon anglais
je modifie vos codes et crée un bouton d’image qui écoute son événement tactile. Ne donnez pas le contrôle tactile à ses éléments d’arrière-plan.
aussi, il donne aux auditeurs de toucher à d'autres éléments
les boutons sont en bas et à gauche
vous pouvez modifier les modifications, mais vous devez modifier les coordonnées des événements de contact dans l'élément 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;
}
}
En fait, vous pouvez essayer WindowManager.LayoutParams.TYPE_SYSTEM_ERROR au lieu de TYPE_SYSTEM_OVERLAY. Cela peut sembler être un hack, mais cela vous permet d’afficher une vue au-dessus de tout et d’obtenir des événements tactiles.
Essaye ça. Fonctionne correctement dans ICS . Si vous souhaitez arrêter le service, cliquez simplement sur la notification générée dans la barre d'état.
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);
}
}
METTRE &AGRAVE; JOUR
Échantillon ici
Pour créer une vue en superposition, lors de la configuration de LayoutParams, NE réglez PAS le type sur TYPE_SYSTEM_OVERLAY.
Instead set it to TYPE_PHONE.
Use the following flags:
FLAG_NOT_TOUCH_MODAL
FLAG_WATCH_OUTSIDE_TOUCH
Il utilise la permission "Android.permission.SYSTEM_ALERT_WINDOW" du didacticiel complet sur ce lien: http://androidsrc.net/facebook-chat-like-floating-chat-heads/
Voici une solution simple, Tout ce dont vous avez besoin est de gonfler la présentation XML comme vous le feriez sur les adaptateurs de liste, Il vous suffit de créer une disposition XML pour le gonfler . Voici le code dont vous avez besoin.
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;
}
}
en utilisant le service, vous pouvez y parvenir:
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;
}
}
}
TYPE_SYSTEM_OVERLAY Cette constante est déconseillée depuis le niveau d'API 26 . Utilisez plutôt TYPE_APPLICATION_OVERLAY.
Bien essayez mon code, au moins cela vous donne une chaîne en superposition, vous pouvez très bien la remplacer par un bouton ou une image. Vous ne croirez pas que c'est ma toute première application Android LOL. Quoi qu'il en soit, si vous êtes plus expérimenté que moi avec les applications Android, veuillez essayer
Si quelqu'un lit encore ce fil et ne peut pas le faire fonctionner, je suis désolé de vous dire que cette façon d'intercepter l'événement est considérée comme un bogue et une correction dans Android> = 4.2.
L'événement de mouvement que vous avez intercepté a bien une action comme ACTION_OUTSIDE, renvoie 0 dans getX Et getY. Cela signifie que vous ne pouvez pas voir toute la position du mouvement à l'écran, et vous ne pouvez rien faire. Je sais que le médecin a dit qu'il obtiendrait x et y, mais la vérité est que ce ne sera pas le cas. Il semble que cela consiste à bloquer l'enregistreur de clé.
Si quelqu'un a une solution de contournement, laissez s'il vous plaît votre commentaire.
ref: Pourquoi ACTION_OUTSIDE renvoie-t-il 0 à chaque fois sur KitKat 4.4.2?
J'ai trouvé une bibliothèque qui fait exactement cela: https://github.com/recruit-lifestyle/FloatingView
Il y a un exemple de projet dans le dossier racine. Je l'ai couru et cela fonctionne comme requis. L'arrière-plan est cliquable, même s'il s'agit d'une autre application.
La réponse de @Sarwar Erfan ne fonctionne plus car Android ne permet pas d'ajouter une vue avec WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY à la fenêtre pour ne plus être touché, même avec WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH.
J'ai trouvé une solution à ce problème. Vous pouvez le vérifier dans la question suivante