5

我正在尝试添加通过服务生成的视图。我使用的代码基于始终可见的Facebook Chatheads,无论应用程序状态如何。它们也显示在其他任何东西之上:

在此处输入图像描述 安卓聊天头

我现在希望将聊天头限制在活动应用程序中。具体来说,每当我将 Window.LayoutParams 从 TYPE_PHONE 更改为 TYPE_DRAWN_APPLICATION 时,我都会处理 Bad Token Exception。

我的问题:我知道我需要将正确的窗口令牌传递给 LayoutParams,但似乎无法弄清楚如何正确执行此操作。任何建议将不胜感激。

这是我的代码:

//主要活动

private void addNewBubble() {
        BubbleLayout bubbleView = (BubbleLayout)LayoutInflater.from(MainActivity.this).inflate(R.layout.bubble_layout, null);
        bubblesManager.addBubble(bubbleView, 60, 20);
}

// initializes Bubbles Manager
private void initializeBubblesManager() {
        bubblesManager = new BubblesManager.Builder(this)
                .setTrashLayout(R.layout.task_bubble_trash_layout)
                .setInitializationCallback(new OnInitializedCallback() {
                    @Override
                    public void onInitialized() {
                        addNewBubble(); // Called when addNewBubble is initialized and the bubble data is loaded. When used on devices running API 18 or below, this function is always called.

                    }
                })
                .build();
        bubblesManager.initialize();
    }

// initializes Bubbles Manager
        private void initializeBubblesManager() {
            bubblesManager = new BubblesManager.Builder(this)
                    .setTrashLayout(R.layout.task_bubble_trash_layout)
                    .setInitializationCallback(new OnInitializedCallback() {
                        @Override
                        public void onInitialized() {
                            addNewBubble(); // Called when addNewBubble is initialized and the bubble data is loaded. When used on devices running API 18 or below, this function is always called.

                        }
                    })
                    .build();
            bubblesManager.initialize();
        }

//XML - 自定义 Bubble_layout

<com.momely.bubbles.BubbleLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:clipChildren="false"
    android:clipToPadding="false">

    <ImageView
        android:id="@+id/avatar"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_gravity="center"
        android:background="@drawable/profile_decorator"
        android:src="@drawable/round_button"
        android:scaleType="centerCrop"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/white"
        android:textSize="15sp"
        android:layout_marginTop="2dp"
        android:layout_marginLeft="2dp"
        android:paddingLeft="4dp"
        android:paddingRight="4dp"
        android:background="@drawable/bubble_counter_bkg"
        android:text="1"/>

</com.momely.bubbles.BubbleLayout>

//在气泡管理器中

public class BubblesManager {
    private static BubblesManager INSTANCE;
    private Context context;
    private boolean bounded;
    private BubblesService bubblesService;
    private int trashLayoutResourceId;
    private OnInitializedCallback listener;


    //getInstance (called in Builder below)
    private static BubblesManager getInstance(Context context){
        if (INSTANCE == null) {
            INSTANCE = new BubblesManager(context);
        }
        return INSTANCE;
    }

    //Binds the service to the application
    private ServiceConnection bubbleServiceConnection = new ServiceConnection(){
        @Override
        public void onServiceConnected(ComponentName name, IBinder service){
            BubblesService.BubblesServiceBinder binder = (BubblesService.BubblesServiceBinder)service;
            BubblesManager.this.bubblesService = binder.getService();
            configureBubblesService();
            bounded = true;
            if(listener != null){
                listener.onInitialized();
            }
        }

   //Initializes Bubbles Manager
   private BubblesManager(Context context){
        this.context = context;
        }

   //Initializes the service
   public void initialize(){
        context.bindService(new Intent(context, BubblesService.class),
            bubbleServiceConnection,
            Context.BIND_AUTO_CREATE);
        }

    public void addBubble(BubbleLayout bubble, int x, int y){
        if(bounded){
            bubblesService.addBubble(bubble, x, y);
            Log.d("Bubble", "Bubble created");
        }

    //Builder class
    public static class Builder {
        private BubblesManager bubblesManager;

        //Builder constructor
        public Builder(Context context){
            this.bubblesManager = getInstance(context);
        }

        //Sets initialization Callbacks - a callback is when we provide a function as an argument to another function in order to enforce the order of operations.
        public Builder setInitializationCallback(OnInitializedCallback listener){
            bubblesManager.listener = listener;
            return this;
        }

        //Sets Trash Layout
        public Builder setTrashLayout(int trashLayoutResourceId){
            bubblesManager.trashLayoutResourceId = trashLayoutResourceId;
            return this;
        }

        //Triggers BubbleManager;
        public BubblesManager build(){
            return bubblesManager;
        }
    }
}

//在气泡服务中

imports...


public class BubblesService extends Service{
    private BubblesServiceBinder binder = new BubblesServiceBinder();
    private List<BubbleLayout> bubbles = new ArrayList<>();
    private BubbleTrashLayout bubblesTrash;
    private WindowManager windowManager;
    private BubblesLayoutCoordinator layoutCoordinator;

    //overrides the IBind method
    @Override
    public IBinder onBind(Intent intent){
        return binder;
    }


    //overrides the onUnbind method
    @Override
    public boolean onUnbind(Intent intent){
        for (BubbleLayout bubble : bubbles){
            recycleBubble(bubble);
        }
        bubbles.clear();
        return super.onUnbind(intent);
   }


    //Gets the Windows Manager
    private WindowManager getWindowManager(){
        if (windowManager ==null){
            windowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
        }
        return windowManager;
    }

    // Adds view to the Window
    public void addBubble(BubbleLayout bubble, int x, int y){
        WindowManager.LayoutParams layoutParams = buildLayoutParamsForBubble(bubble, x,y);
        layoutParams.token = bubble.getApplicationWindowToken();
        bubble.setWindowManager(getWindowManager());
        bubble.setViewParams(layoutParams);
        bubble.setLayoutCoordinator(layoutCoordinator);
        bubbles.add(bubble);
        addViewToWindow(bubble);
    }


    // Initializes the Layout Cocordinator
    private void initializeLayoutCoordinator(){
        layoutCoordinator = new BubblesLayoutCoordinator.Builder(this)
                .setWindowManager(getWindowManager())
                .setTrashView(bubblesTrash)
                .setTrashView(bubblesTrash)
                .build();
    }

    //Adds view to the Window
    private void addViewToWindow(final BubbleBaseLayout view){
        new Handler(Looper.getMainLooper()).post(new Runnable(){
            @Override
            public void run(){
                getWindowManager().addView(view, view.getViewParams());
            }
        });
    }

    //BUILDING LAYOUT PARAMS --> THIS IS WHERE THE TYPE IS SET
    private WindowManager.LayoutParams buildLayoutParamsForBubble(BubbleLayout bubble, int x, int y){
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION, //!!!! WHEN this is set to TYPE_PHONE the chat head stays on the screen even if the application is onPause.
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSPARENT);
        params.gravity = Gravity.TOP | Gravity.START;
        params.token = bubble.getApplicationWindowToken();
        params.x = x;
        params.y = y;
        return params;
    }


    //defines the BubblesService Binder service
    public class BubblesServiceBinder extends Binder {
        public BubblesService getService(){
            return BubblesService.this;
        }
    }

}

///我收到的错误

E/AndroidRuntime: FATAL EXCEPTION: main
   Process: com.momely.mascapone, PID: 16638
   android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
       at android.view.ViewRootImpl.setView(ViewRootImpl.java:683)
       at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
       at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
       at com.momely.bubbles.BubblesService$2.run(BubblesService.java:115)
       at android.os.Handler.handleCallback(Handler.java:751)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:154)
       at android.app.ActivityThread.main(ActivityThread.java:6119)
       at java.lang.reflect.Method.invoke(Native Method)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

关于如何在应用程序处于暂停状态时将聊天头限制在应用程序窗口而不保留在屏幕上的任何建议?

Z

4

1 回答 1

2

“我现在希望将聊天头限制在活动的应用程序上。”

我看到两个选项。作为一个简单的技巧(保留服务),请使用Option 1
选项 2意味着复制BubblesService.javatoBubblesLocal.javaBubblesManager.javato BubblesManagerLocal.java,并破解所有Service代码。我建议选项 1是你想要的(更容易,你可以打开和关闭它)。这是一张图片

选项1

当您的应用程序不活动时,只需隐藏气泡即可。
将以下代码添加到您的项目中(经过测试,正在运行):

MainActivity.java

//update ActionBarActivity to AppCompatActivity 
`public class MainActivity extends AppCompatActivity //ActionBarActivity`
private boolean mStarted = false;
 @Override
protected void onCreate(Bundle savedInstanceState) {
...
        initializeBubblesManager();
        mStarted = true;
//------------------------------------------------------------------------------------------------
    @Override
    protected void onResume() 
    {
        Log.i("MainActivity:","onResume");
        super.onResume();
        if(mStarted) bubblesManager.showBubbles();
    }
//------------------------------------------------------------------------------------------------
    @Override
    protected void onPause() 
    {
        Log.i("MainActivity:","onPause");
        super.onPause();
        if(mStarted) bubblesManager.hideBubbles();
    }
//------------------------------------------------------------------------------------------------

气泡管理器.java

//------------------------------------------------------------------------------------------------
    public void showBubbles()
    {
        if(bounded && bubbleServiceConnection != null)bubblesService.showBubbles();
    }//showBubbles
//------------------------------------------------------------------------------------------------
    public void hideBubbles()
    {
        if(bounded && bubbleServiceConnection != null)bubblesService.hideBubbles();
    }//hideBubbles
//------------------------------------------------------------------------------------------------

气泡服务.java

//------------------------------------------------------------------------------------------------
    public void showBubbles()
    {
        if(bubbles.size() > 0)
        {
            for (BubbleLayout bubble : bubbles) 
            {
                bubble.showBubble();
            }
        }
    }//showBubbles
//------------------------------------------------------------------------------------------------
    public void hideBubbles()
    {
        if(bubbles.size() > 0)
        {
            for (BubbleLayout bubble : bubbles) 
            {
                bubble.hideBubble();
            }
        }
    }//hideBubbles
//------------------------------------------------------------------------------------------------

气泡布局.java

//------------------------------------------------------------------------------------------------
    public void showBubble()
    {
            //View.GONE This view is invisible, and it doesn't take any space for layout purposes.
            //View.INVISIBLE This view is invisible, but it still takes up space for layout purposes.

        getRootView().setVisibility(View.VISIBLE);
    }//showBubble
//------------------------------------------------------------------------------------------------
    public void hideBubble()
    {
        getRootView().setVisibility(View.INVISIBLE);
    }//hideBubble
//------------------------------------------------------------------------------------------------
于 2017-12-03T21:57:52.077 回答