Создание окна оверлея системы (всегда сверху)

Я пытаюсь создать кнопку "always-op-top" /clickable -image который постоянно остается на вершине всех окон.

Доказательство концепция

Я был успешным и имею текущее обслуживание. Обслуживание отображает текст в левом верхнем углу экрана все время, пока пользователь может свободно взаимодействовать с остальными приложениями в обычном режиме.

Что я это подкласс ViewGroup и добавить его в корневой менеджер окон с помощью флаг TYPE_SYSTEM_OVERLAY. Теперь я хочу добавить кнопку /clickable -image вместо этого текста, который может принимать события касания сам по себе. я попробовал переопределить "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;
    }
}

Ответ 1

Это может быть глупое решение. Но это работает. Если вы можете улучшить его, пожалуйста, дайте мне знать.

OnCreate of your Service: Я использовал флаг 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" />

Ответ 2

Следуя @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"/>

Ответ 3

Начиная с 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 и будущих устройств.

Ответ 4

Я один из разработчиков Tooleap SDK. Мы также предоставляем разработчикам возможность отображения всегда на верхних окнах и кнопках, и мы имеем дело с аналогичной ситуацией.

Одна из проблем, на которые здесь не затронуты, - это проблема Android "Защищенные кнопки".

Защищенные кнопки имеют свойство filterTouchesWhenObscured, что означает, что они не могут взаимодействовать с ними, если они находятся под окном, даже если это окно не получает никаких касаний. Указание документации для Android:

Указывает, следует ли фильтровать касания, когда окно просмотра закрыто другим видимым окном. Если установлено значение true, представление не будет получать затрагивает всякий раз, когда появляется тост, диалог или другое окно над окно просмотра. Обратитесь к {@link android.view.View} безопасности документацию для более подробной информации.

Примером такой кнопки является кнопка установить при попытке установить сторонние апки. Любое приложение может отображать такую ​​кнопку, если добавить в макет представления следующую строку:

android:filterTouchesWhenObscured="true"

Если вы показываете окно "поверх всех" на "Защищенной кнопке", то все защищенные части кнопок, которые покрываются наложением, не будут обрабатывать никаких касаний, даже если этот оверлей не будет доступен для кликов. Поэтому, если вы планируете отображать такое окно, вы должны предоставить пользователю возможность переместить его или отклонить. И если часть вашего наложения прозрачна, учтите, что ваш пользователь может быть смущен, почему определенная кнопка в базовом приложении не работает для него внезапно.

Ответ 5

РАБОТАЙТЕ ВСЕГДА НА КНОПКЕ ТОГО ИЗОБРАЖЕНИЯ

в первую очередь извините за мой английский

Я редактирую ваши коды и создаю кнопку рабочего изображения, которая прослушивает его сенсорное событие не давайте сенсорный контроль своим фоновым элементам.

также он дает сенсорным прослушивателям другие элементы.

кнопки внизу и слева

вы можете использовать заклинания, но вам нужно обманывать кординаты в событии касания в элементе 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;
    }
}

Ответ 6

На самом деле вы можете попробовать WindowManager.LayoutParams.TYPE_SYSTEM_ERROR вместо TYPE_SYSTEM_OVERLAY. Это может звучать как хак, но это позволяет вам отображать представление поверх всего и все еще получать события касания.

Ответ 7

Попробуйте это. Прекрасно работает в 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

Пример здесь

Чтобы создать представление наложения, при настройке LayoutParams НЕ устанавливайте тип TYPE_SYSTEM_OVERLAY.

Instead set it to TYPE_PHONE.

Use the following flags:

FLAG_NOT_TOUCH_MODAL

FLAG_WATCH_OUTSIDE_TOUCH

Ответ 8

TYPE_SYSTEM_OVERLAY Эта константа устарела начиная с уровня API 26. Вместо этого используйте TYPE_APPLICATION_OVERLAY. или ** для пользователей ниже и выше Android 8

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
    LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_PHONE;
}

Ответ 9

Вот несколько простых решений, Все, что вам нужно, это раздуть 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;
    }

}

Ответ 11

Хорошо попробуйте мой код, по крайней мере, он дает вам строку в качестве наложения, вы вполне можете заменить ее кнопкой или изображением. Вы не поверите, что это мое первое приложение Android для Android. В любом случае, если у вас больше опыта с приложениями для Android, чем у меня, попробуйте

  • изменение параметров 2 и 3 в "новых WindowManager.LayoutParams"
  • попробуйте другой подход к событиям.

Ответ 12

Если кто-то все еще читает этот поток и не может получить эту работу, мне очень жаль говорить вам, что этот способ перехвата события движения считается ошибкой и исправлением в android >= 4.2.

Событие движения, которое вы перехватили, хотя имеет действие как ACTION_OUTSIDE, возвращает 0 в getX и getY. Это означает, что вы не можете видеть все положение движения на экране, и вы ничего не можете сделать. Я знаю, что док сказал, что он получит х и у, но правда в том, что НЕ БУДЕТ. Кажется, что это блокировать регистратор ключей.

Если у кого-то есть обходной путь, оставьте свой комментарий.

исх: Почему ACTION_OUTSIDE возвращает 0 каждый раз на KitKat 4.4.2?

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

Ответ 13

с помощью службы вы можете достичь этого:

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;
        }
    }
}

Ответ 14

Нашел библиотеку, которая делает именно это: https://github.com/recruit-lifestyle/FloatingView

Там образец проекта в корневой папке. Я запустил его, и он работает как требуется. Фон кликабелен - даже если это другое приложение.

enter image description here

Ответ 15

@Sarwar Erfan ответ больше не работает, так как Android не позволяет добавлять вид с WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY в окно, чтобы быть осязаемым больше, чем даже WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH.

Я нашел решение этой проблемы. Вы можете проверить это в следующем вопросе

При добавлении вида в окно с WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY он не получает событие касания