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:
接下来将要开发的应用程序将具有以下功能:
- Display a floating action button as the overlay view when the application is in the background. We’ll be using CounterFab library. 当应用程序在后台时,将浮动操作按钮显示为叠加视图。 我们将使用CounterFab库。
- Dragging the widget anywhere on the screen. 将窗口小部件拖动到屏幕上的任意位置。
- Letting the widget position itself along the nearest edge of the screen (instead of keeping it hanging in the middle). 让窗口小部件将自身定位在屏幕的最近边缘(而不是使其挂在中间)。
- Click the widget to launch back the application and pass the data from the Service to the Activity. 单击小部件以启动应用程序,并将数据从服务传递到活动。
- Add android floating widget by clicking a button from our application. 通过从我们的应用程序中单击一个按钮来添加android浮动小部件。
- 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:
从以上代码得出的推论很少是:
- 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 。 - 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
添加到屏幕的左上角。 - 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坐标。 - 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.
There are a few things left to implement to complete the application.
还有一些事情需要完成以完成应用程序。
- Auto positioning the widget to the nearest edge of the screen (left/right). 自动将窗口小部件定位到屏幕的最近边缘(左/右)。
- Clicking the widget should launch the application. (We’ll be possibly passing data from the service to the activity). 单击小部件将启动应用程序。 (我们可能会将数据从服务传递到活动)。
- 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()
- 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 whenonPause()
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。 - We’d placed a null checker on the
mOverlayView
instance to update the CounterFab badge count if it already exists. 我们在mOverlayView
实例上放置了一个空检查器,以更新CounterFab徽章计数(如果已存在)。 - 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
是我们使用的变量)。 使用以下代码段即可完成。 - We need to subtract the width of the android floating widget from the display width of the screen.
We useGlobalLayoutListener
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 usingGlobalLayoutListener
such aslayout.getWidth()
orcounterFab.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
回调 - 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 invokesonDestroy()
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