cocos bubbles_像Messenger Bubbles这样的Android浮动小部件

cocos bubbles

If you’ve used the Facebook Messenger Application anytime, you must have come across the chat bubbles that can be seen floating on your screen irrespective of which application you’re using currently. In this tutorial, we’ll be discussing and implementing an android floating widget that’ll stay on the screen even when the application is in the background. This feature is handy to use for multitasking such as switching easily between applications.

如果您曾经使用过Facebook Messenger应用程序,那么无论您当前正在使用哪个应用程序,您都一定会遇到可以在屏幕上看到的聊天气泡。 在本教程中,我们将讨论和实现一个即使在应用程序在后台时仍会停留在屏幕上的android浮动小部件。 此功能便于进行多任务处理,例如在应用程序之间轻松切换。

Android浮动小部件概念 (Android Floating Widget Concept)

Android Floating Widget is nothing but overlay views drawn over applications. To allow drawing views over other applications we need to add the following permission inside the AndroidManifest.xml file of our project.

Android Floating Widget只是在应用程序上绘制的叠加视图。 为了允许在其他应用程序上绘制视图,我们需要在项目的AndroidManifest.xml文件内添加以下权限。

android.permission.SYSTEM_ALERT_WINDOW

To display an android floating widget we need to start a background service and add our custom view to an instance of WindowManager so as to keep the custom view at the top of the view hierarchy of the current screen.

要显示android浮动小部件,我们需要启动后台服务并将自定义视图添加到WindowManager的实例中,以便将自定义视图保持在当前屏幕的视图层次结构的顶部。

The application that we’ll be developing next will have the following features in place:

接下来将要开发的应用程序将具有以下功能:

  1. Display a floating action button as the overlay view when the application is in the background. We’ll be using CounterFab library.

    当应用程序在后台时,将浮动操作按钮显示为叠加视图。 我们将使用CounterFab库。
  2. Dragging the widget anywhere on the screen.

    将窗口小部件拖动到屏幕上的任意位置。
  3. Letting the widget position itself along the nearest edge of the screen (instead of keeping it hanging in the middle).

    让窗口小部件将自身定位在屏幕的最近边缘(而不是使其挂在中间)。
  4. Click the widget to launch back the application and pass the data from the Service to the Activity.

    单击小部件以启动应用程序,并将数据从服务传递到活动。
  5. Add android floating widget by clicking a button from our application.

    通过从我们的应用程序中单击一个按钮来添加android浮动小部件。
  6. Keep a badge count over the FAB displaying the number of times the widget was created (let’s say it denotes the number of messages).

    保持FAB上的徽章计数,显示创建小部件的次数(假设它表示消息的数量)。

Android Floating Widget示例项目结构 (Android Floating Widget Example Project Structure)

The project consists of a single activity and a background service.

该项目由一个活动和一个后台服务组成。

Android Floating Widget示例代码 (Android Floating Widget Example Code)

Before jumping into the business logic of our application let’s see the AndroidManifest.xml file once.

在进入应用程序的业务逻辑之前,让我们AndroidManifest.xml一下AndroidManifest.xml文件。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.journaldev.floatingchatheads">


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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".FloatingWidgetService"
            android:enabled="true"
            android:exported="false" />


    </application>

</manifest>

Add the following dependency inside the build.gradle of your project

在项目的build.gradle中添加以下依赖项

compile 'com.github.andremion:counterfab:1.0.1'

The xml code for activity_main.xml layout is given below.

下面给出了activity_main.xml布局的xml代码。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.journaldev.floatingchatheads.MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Hello World!" />

</RelativeLayout>

The layout for the android floating widget is mentioned in the overlay_layout.xml file as shown below:

如下所示,在overlay_layout.xml文件中提到了android浮动小部件的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    android:id="@+id/layout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:orientation="vertical"
    android:visibility="visible">


    <com.andremion.counterfab.CounterFab
        android:id="@+id/fabHead"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_input_add"
        app:fabSize="normal" />


</RelativeLayout>

The code for the MainActivity.java class is given below :

MainActivity.java类的代码如下:

package com.journaldev.floatingchatheads;

import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {


    private static final int DRAW_OVER_OTHER_APP_PERMISSION = 123;
    private Button button;

    private TextView textView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        askForSystemOverlayPermission();

        button = (Button) findViewById(R.id.button);
        textView = (TextView) findViewById(R.id.textView);


        int badge_count = getIntent().getIntExtra("badge_count", 0);

        textView.setText(badge_count + " messages received previously");

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(MainActivity.this)) {
                    startService(new Intent(MainActivity.this, FloatingWidgetService.class));
                } else {
                    errorToast();
                }
            }
        });

    }

    private void askForSystemOverlayPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {

            //If the draw over permission is not available open the settings screen
            //to grant the permission.
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, DRAW_OVER_OTHER_APP_PERMISSION);
        }
    }


    @Override
    protected void onPause() {
        super.onPause();


        // To prevent starting the service if the required permission is NOT granted.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(this)) {
            startService(new Intent(MainActivity.this, FloatingWidgetService.class).putExtra("activity_background", true));
            finish();
        } else {
            errorToast();
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == DRAW_OVER_OTHER_APP_PERMISSION) {

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (!Settings.canDrawOverlays(this)) {
                    //Permission is not available. Display error text.
                    errorToast();
                    finish();
                }
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    private void errorToast() {
        Toast.makeText(this, "Draw over other app permission not available. Can't start the application without the permission.", Toast.LENGTH_LONG).show();
    }

}

In the above code, we check if the permission to draw view over other apps is enabled or not.
We start the background service intent namely FloatingWidgetService.java when the onPause() method is invoked(signalling that the application is in background).

在上面的代码中,我们检查是否允许在其他应用程序上绘制视图的权限。
当调用onPause()方法(表示应用程序在后台)时,我们启动后台服务意图,即FloatingWidgetService.java

The code for FloatingWidgetService.java is given below:

下面给出了FloatingWidgetService.java的代码:

package com.journaldev.floatingchatheads;

import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;

import com.andremion.counterfab.CounterFab;

/**
 * Created by anupamchugh on 01/08/17.
 */

public class FloatingWidgetService extends Service {


    private WindowManager mWindowManager;
    private View mOverlayView;
    CounterFab counterFab;


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public void onCreate() {
        super.onCreate();

        setTheme(R.style.AppTheme);

        mOverlayView = LayoutInflater.from(this).inflate(R.layout.overlay_layout, null);


        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);


        //Specify the view position
        params.gravity = Gravity.TOP | Gravity.LEFT;        //Initially view will be added to top-left corner
        params.x = 0;
        params.y = 100;


        mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        mWindowManager.addView(mOverlayView, params);

      
        counterFab = (CounterFab) mOverlayView.findViewById(R.id.fabHead);
        counterFab.setCount(1);

        counterFab.setOnTouchListener(new View.OnTouchListener() {
            private int initialX;
            private int initialY;
            private float initialTouchX;
            private float initialTouchY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:

                        //remember the initial position.
                        initialX = params.x;
                        initialY = params.y;


                        //get the touch location
                        initialTouchX = event.getRawX();
                        initialTouchY = event.getRawY();


                        return true;
                    case MotionEvent.ACTION_UP:

                        //Add code for launching application and positioning the widget to nearest edge.
                      
                
                         return true;
                    case MotionEvent.ACTION_MOVE:


                        float Xdiff = Math.round(event.getRawX() - initialTouchX);
                        float Ydiff = Math.round(event.getRawY() - initialTouchY);


                        //Calculate the X and Y coordinates of the view.
                        params.x = initialX + (int) Xdiff;
                        params.y = initialY + (int) Ydiff;

                        //Update the layout with new X & Y coordinates
                        mWindowManager.updateViewLayout(mOverlayView, params);


                        return true;
                }
                return false;
            }
        });


    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mOverlayView != null)
            mWindowManager.removeView(mOverlayView);
    }

}

Few inferences drawn from the above code are:

从以上代码得出的推论很少是:

  1. Unlike an Activity, we need to explicitly set the Theme in a Service using setTheme() method. Failing to do so would cause IllegalArgumentException.

    与活动不同,我们需要使用setTheme()方法在服务中显式设置主题。 否则将导致IllegalArgumentException
  2. We’ve created an instance of WindowManager and added the overlay_layout to the top-left of the screen in the above code.

    我们已经创建了WindowManager的实例,并在上面的代码中将overlay_layout添加到屏幕的左上角。
  3. To drag the floating widget along the screen, we’ve overridden the onTouchListener() to listen to drag events and change the X and Y coordinates of the overlay view on the screen.

    为了沿屏幕拖动浮动小部件,我们重写了onTouchListener()以侦听拖动事件并更改屏幕上叠加视图的X和Y坐标。
  4. We’ve set the badge count of the CounterFab class as 1 by invoking the method setCount().

    我们通过调用setCount()方法将CounterFab类的标志计数设置为1。

The output that the above piece of code gives us is given below.

android floating widget example output

上面的代码给我们的输出如下。

There are a few things left to implement to complete the application.

还有一些事情需要完成以完成应用程序。

  1. Auto positioning the widget to the nearest edge of the screen (left/right).

    自动将窗口小部件定位到屏幕的最近边缘(左/右)。
  2. Clicking the widget should launch the application. (We’ll be possibly passing data from the service to the activity).

    单击小部件将启动应用程序。 (我们可能会将数据从服务传递到活动)。
  3. Adding a button inside the activity to create android floating widget. (Instead of creating new view for each invocation we’ll be just incrementing the badge count).

    在活动中添加按钮以创建android浮动小部件。 (而不是为每次调用创建新视图,我们只是增加徽章计数)。

Let’s get started with adding a button inside the activity_main.xml layout as shown below:

让我们开始在activity_main.xml布局内添加一个按钮,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.journaldev.floatingchatheads.MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Hello World!" />


    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_margin="16dp"
        android:text="ADD FLOATING BUTTON" />

</RelativeLayout>

The code for FloatingWidgetService.java class is given below :

下面给出了FloatingWidgetService.java类的代码:

package com.journaldev.floatingchatheads;

import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import com.andremion.counterfab.CounterFab;

/**
 * Created by anupamchugh on 01/08/17.
 */

public class FloatingWidgetService extends Service {


    private WindowManager mWindowManager;
    private View mOverlayView;
    int mWidth;
    CounterFab counterFab;
    boolean activity_background;


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (intent != null) {
            activity_background = intent.getBooleanExtra("activity_background", false);

        }

        if (mOverlayView == null) {

            mOverlayView = LayoutInflater.from(this).inflate(R.layout.overlay_layout, null);


            final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                    WindowManager.LayoutParams.WRAP_CONTENT,
                    WindowManager.LayoutParams.WRAP_CONTENT,
                    WindowManager.LayoutParams.TYPE_PHONE,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                    PixelFormat.TRANSLUCENT);


            //Specify the view position
            params.gravity = Gravity.TOP | Gravity.LEFT;        //Initially view will be added to top-left corner
            params.x = 0;
            params.y = 100;


            mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
            mWindowManager.addView(mOverlayView, params);

            Display display = mWindowManager.getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);


            counterFab = (CounterFab) mOverlayView.findViewById(R.id.fabHead);
            counterFab.setCount(1);

           final RelativeLayout layout = (RelativeLayout) mOverlayView.findViewById(R.id.layout);
            ViewTreeObserver vto = layout.getViewTreeObserver();
            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    int width = layout.getMeasuredWidth();

                   //To get the accurate middle of the screen we subtract the width of the android floating widget.
                   mWidth = size.x - width;

                }
            });

            counterFab.setOnTouchListener(new View.OnTouchListener() {
                private int initialX;
                private int initialY;
                private float initialTouchX;
                private float initialTouchY;

                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:

                            //remember the initial position.
                            initialX = params.x;
                            initialY = params.y;


                            //get the touch location
                            initialTouchX = event.getRawX();
                            initialTouchY = event.getRawY();


                            return true;
                        case MotionEvent.ACTION_UP:


                            if (activity_background) {

                                //xDiff and yDiff contain the minor changes in position when the view is clicked.
                                float xDiff = event.getRawX() - initialTouchX;
                                float yDiff = event.getRawY() - initialTouchY;

                                if ((Math.abs(xDiff) < 5) && (Math.abs(yDiff) = middle ? mWidth : 0;
                            params.x = (int) nearestXWall;


                            mWindowManager.updateViewLayout(mOverlayView, params);


                            return true;
                        case MotionEvent.ACTION_MOVE:


                            int xDiff = Math.round(event.getRawX() - initialTouchX);
                            int yDiff = Math.round(event.getRawY() - initialTouchY);


                            //Calculate the X and Y coordinates of the view.
                            params.x = initialX + xDiff;
                            params.y = initialY + yDiff;

                            //Update the layout with new X & Y coordinates
                            mWindowManager.updateViewLayout(mOverlayView, params);


                            return true;
                    }
                    return false;
                }
            });
        } else {

            counterFab.increase();

        }


        return super.onStartCommand(intent, flags, startId);


    }

    @Override
    public void onCreate() {
        super.onCreate();

        setTheme(R.style.AppTheme);


    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mOverlayView != null)
            mWindowManager.removeView(mOverlayView);
    }

}

In the above code, we’ve moved the logic from onCreate() to the onStartCommand() method. Why?
We’ll be starting the FloatingWidgetService multiple times. onCreate() method of the Service class is called only the first time. In order to update the widget and retrieve intent extras we need to shift our code into onStartCommand()

在上面的代码中,我们将逻辑从onCreate()移到了onStartCommand()方法。 为什么?
我们将多次启动FloatingWidgetService 。 仅在第一次调用Service类的onCreate()方法。 为了更新小部件并检索意向附加信息,我们需要将代码移入onStartCommand()

  1. We need to detect whether the activity is running in the background or not. Only if the application is running in the background we’ll launch our Activity from the Service(No point launching another instance of the activity if it’s in foreground). activity_background bundle extra is passed with the value of true when onPause() is invoked in the activity that we’ll be seeing shortly.
    if (intent != null) {
                activity_background = intent.getBooleanExtra("activity_background", false);
    
    }

    我们需要检测活动是否在后台运行。 仅当应用程序在后台运行时,我们才会从服务启动活动(如果活动在前台,则无法启动活动的另一个实例)。 在不久将看到的活动中调用onPause()时,将使用activity_background捆绑包extra传递值为true。
  2. We’d placed a null checker on the mOverlayView instance to update the CounterFab badge count if it already exists.

    我们在mOverlayView实例上放置了一个空检查器,以更新CounterFab徽章计数(如果已存在)。
  3. To auto position the view along the nearest edge we first need to find the width of the screen and store it(mWidth is the variable we’ve used). It’s done using the below code snippet.
    Display display = mWindowManager.getDefaultDisplay();
                Point size = new Point();
                display.getSize(size);
                
                // mWidth = size.x; //Inaccurate width of the screen since it doesn't take the width of the android floating widget in consideration.

    为了使视图沿着最近的边缘自动定位,我们首先需要找到屏幕的宽度并存储它( mWidth是我们使用的变量)。 使用以下代码段即可完成。
  4. We need to subtract the width of the android floating widget from the display width of the screen.
    We use GlobalLayoutListener for this. It calculates the width of the view only after the view is properly laid.
    final RelativeLayout layout = (RelativeLayout) mOverlayView.findViewById(R.id.layout);
                ViewTreeObserver vto = layout.getViewTreeObserver();
                vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        int width = layout.getMeasuredWidth();
    
                       //To get the accurate middle of the screen we subtract the width of the floating widget in android.
                       mWidth = size.x - width;
    
                    }
                });

    Note: Calling getWidth() on a view directly without using GlobalLayoutListener such as layout.getWidth() or counterFab.getWidth() would return 0 since the view hasn’t been laid yet. GlobalLayoutListener callback gets triggered only after the view is properly laid


    GlobalLayoutListener ,我们使用GlobalLayoutListener 。 仅在正确放置视图后,才计算视图的宽度。

    注意 :如果GlobalLayoutListener视图,则不使用诸如layout.getWidth()counterFab.getWidth()类的layout.getWidth()直接在视图上调用getWidth()会返回0 。 仅在正确放置视图之后才触发GlobalLayoutListener回调

  5. Updating the view to be along the nearest edge, as well as detecting if the view was clicked, both these features would be triggered only when the user lifts his/her finger from the screen and the MotionEvent.ACTION_UP is triggered. The code for the ACTION_UP case is given below:
    //Only start the activity if the application is in the background. Pass the current badge_count to the activity
                                if (activity_background) {
                                    
                                    float xDiff = event.getRawX() - initialTouchX;
                                    float yDiff = event.getRawY() - initialTouchY;
    
                                    if ((Math.abs(xDiff) < 5) && (Math.abs(yDiff) = middle ? mWidth : 0;
                                params.x = (int) nearestXWall;
    
    
                                mWindowManager.updateViewLayout(mOverlayView, params);

    The badge_count extra is passed onto the activity when the view is clicked.
    stopSelf() is invoked to kill the service which invokes onDestroy() where the floating widget is removed from the WindowManager.

    MotionEvent.ACTION_UP时,才会触发这两个功能。 下面给出了ACTION_UP情况的代码:

    单击视图时,将badge_count extra传递给活动。
    调用stopSelf()服务,该服务调用onDestroy() ,在该服务中从窗口管理器中删除了浮动小部件。

The code for MainActivity.java class is given below:

MainActivity.java类的代码如下:

package com.journaldev.floatingchatheads;

import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {


    private static final int DRAW_OVER_OTHER_APP_PERMISSION = 123;
    private Button button;

    private TextView textView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        askForSystemOverlayPermission();

        button = (Button) findViewById(R.id.button);
        textView = (TextView) findViewById(R.id.textView);


        int badge_count = getIntent().getIntExtra("badge_count", 0);

        textView.setText(badge_count + " messages received previously");

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(MainActivity.this)) {
                    startService(new Intent(MainActivity.this, FloatingWidgetService.class));
                } else {
                    errorToast();
                }
            }
        });

    }

    private void askForSystemOverlayPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {

            //If the draw over permission is not available to open the settings screen
            //to grant permission.
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, DRAW_OVER_OTHER_APP_PERMISSION);
        }
    }


    @Override
    protected void onPause() {
        super.onPause();


        // To prevent starting the service if the required permission is NOT granted.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(this)) {
            startService(new Intent(MainActivity.this, FloatingWidgetService.class).putExtra("activity_background", true));
            finish();
        } else {
            errorToast();
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == DRAW_OVER_OTHER_APP_PERMISSION) {

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (!Settings.canDrawOverlays(this)) {
                    //Permission is not available. Display error text.
                    errorToast();
                    finish();
                }
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    private void errorToast() {
        Toast.makeText(this, "Draw over other app permission not available. Can't start the application without the permission.", Toast.LENGTH_LONG).show();
    }

}

The above code now allows starting a service by the button click. Also, it displays the current badge_count value returned from the FloatingWidgetService, the default value being 0.

上面的代码现在允许通过单击按钮来启动服务。 此外,它badge_count显示从FloatingWidgetService返回的当前badge_count值,默认值为0。

The output of the above application in action is given below.

下面给出了上面应用程序的输出。

This brings an end to this tutorial. We’ve extracted the power of Services to display floating widgets for good use. There’s one interesting bit left though: Killing the application by moving the floating widget to trash. We’ll look into that in a later tutorial. You can download the final Android FloatingChatHeads Project from the link below.

本教程到此结束。 我们已经利用服务的功能来显示浮动窗口小部件,以供良好使用。 不过,还有一个有趣的地方:通过将浮动窗口小部件移至垃圾桶来终止应用程序。 我们将在以后的教程中对此进行研究。 您可以从下面的链接下载最终的Android FloatingChatHeads项目

翻译自: https://www.journaldev.com/14673/android-floating-widget

cocos bubbles

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值