我希望我的应用程序像在Facebook Messenger中一样显示浮动气泡通知。以下https://www.androidhive.info/2016/11/android-floating-widget-like-facebook-chat-head/之后是我编写的服务。
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
public class BubbleNotifyService extends Service {
private WindowManager windowManager;
private View BubbleView;
private TextView bubbleTitle, bubbleData;
public BubbleNotifyService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@SuppressLint({"RtlHardcoded", "InflateParams"})
@Override
public void onCreate() {
super.onCreate();
//android.os.Debug.waitForDebugger();
setTheme(R.style.AppTheme);
BubbleView =
LayoutInflater.from(this).inflate(R.layout.floating_bubble, null);
final WindowManager.LayoutParams params;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
);
} else {
params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ,
PixelFormat.TRANSLUCENT
);
}
if (BubbleView == null) {
Log.d("Bubble", "BubbleView is null.");
}
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = 0;
params.y = 100;
windowManager =
(WindowManager) getSystemService(WINDOW_SERVICE);
if (windowManager != null) {
Log.d("Bubble", "added bubble to view.");
windowManager.addView(BubbleView, params);
} else {
Log.d("Bubble", "windowManager is null");
}
final View collapsedView =
BubbleView.findViewById(R.id.collapsed_view);
final View expandedView =
BubbleView.findViewById(R.id.expanded_view);
expandedView.setVisibility(View.GONE);
collapsedView.setVisibility(View.VISIBLE);
ImageView close_collapsed = BubbleView.findViewById(R.id.collaspsed_cancel);
ImageView close_expanded = BubbleView.findViewById(R.id.expanded_cancel);
Button open_act_btn = BubbleView.findViewById(R.id.open_full_btn);
bubbleTitle = BubbleView.findViewById(R.id.bubble_title);
bubbleMeaning = BubbleView.findViewById(R.id.bubble_data);
close_collapsed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
stopSelf();
}
});
close_expanded.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
collapsedView.setVisibility(View.VISIBLE);
expandedView.setVisibility(View.GONE);
}
});
open_act_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//TODO new Activity
}
});
BubbleView.findViewById(R.id.bubble_root).setOnTouchListener(new View.OnTouchListener() {
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
initialX = params.x;
initialY = params.y;
initialTouchX = motionEvent.getRawX();
initialTouchY = motionEvent.getRawY();
return true;
case MotionEvent.ACTION_UP:
int Xdiff = (int) (motionEvent.getRawX() - initialTouchX);
int Ydiff = (int) (motionEvent.getRawY() - initialTouchY);
if (Xdiff < 10 && Ydiff < 10) {
if (isViewCollapsed()) {
collapsedView.setVisibility(View.GONE);
expandedView.setVisibility(View.VISIBLE);
}
}
return true;
case MotionEvent.ACTION_MOVE:
params.x =
initialX + (int) (motionEvent.getRawX() - initialTouchX);
params.y =
initialY + (int) (motionEvent.getRawY() - initialTouchY);
windowManager.updateViewLayout(BubbleView, params);
return true;
}
return false;
}
});
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
String title = intent.getStringExtra(IntentKeys.BUBBLE_DATA_TITLE);
String data = intent.getStringExtra(IntentKeys.BUBBLE_DATA);
bubbleTitle.setText(title);
bubbleData.setText(data);
return START_NOT_STICKY;
}
private boolean isViewCollapsed() {
return BubbleView == null ||
BubbleView.findViewById(R.id.collapsed_view).getVisibility() == View.VISIBLE;
}
@Override
public void onDestroy() {
super.onDestroy();
if (BubbleView != null) {
windowManager.removeView(BubbleView);
}
}
}
从另一个接收通知的服务调用 BubbleNotifyService
:
Intent intent = new Intent(context, BubbleNotifyService.class);
intent.putExtra(IntentKeys.BUBBLE_DATA_TITLE, Results.getWord());
intent.putExtra(IntentKeys.BUBBLE_DATA, Results.getData());
context.startService(intent);
所有来自意图的数据都传递到BubbleNotifyService
。该服务作为清单android:process=":BubbleResult"
中指定的单独进程运行,但是该服务不显示任何覆盖。授予其他应用程序绘画权限。
气泡的布局(floating_bubble.xml)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bubble_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="@+id/bubble_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="@+id/collapsed_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible">
<ImageView
android:id="@+id/collapsed_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@mipmap/ic_launcher"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/collaspsed_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@+id/collapsed_icon"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_action_cancel"
tools:ignore="ContentDescription" />
</android.support.constraint.ConstraintLayout>
<android.support.v7.widget.CardView
android:id="@+id/expanded_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"
app:cardCornerRadius="5dp"
app:cardElevation="10dp"
app:cardUseCompatPadding="true"
app:contentPadding="5dp">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/bubble_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/expanded_cancel"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/result_card_title" />
<TextView
android:id="@+id/bubble_data"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bubble_title"
tools:text="@string/result_card_data" />
<ImageView
android:id="@+id/expanded_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/bubble_title"
app:srcCompat="@drawable/ic_action_cancel"
tools:ignore="ContentDescription" />
<Button
android:id="@+id/open_full_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:text="@string/result_card_see_more"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bubble_data" />
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>
</FrameLayout>
如果要对此进行投票,请注意解释拒绝的原因。
答案 0 :(得分:2)
测试了您的代码和提到的博客文章的代码。问题似乎是因为您在布局中使用了ConstraintLayout
和CardView
。
对于widget behavior,不支持这些视图类型。当我们直接将视图添加到系统窗口时,它们的行为类似于RemoteViews
。一旦您更改了ConstraintLayout
和CardView
,您的布局就变得可见并且可以操作。
答案 1 :(得分:0)
这是我的工作代码。需要显示浮动视图时,将启动此服务。
FloatingService.class
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.Toast;
public class FloatingService extends Service {
private WindowManager mWindowManager;
private View mFloatingView;
public FloatingService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
//Inflate the floating view layout we created
mFloatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_widget, null);
int LAYOUT_FLAG;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_PHONE;
}
//Add the view to the window.
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
LAYOUT_FLAG,
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;
//Add the view to the window
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
mWindowManager.addView(mFloatingView, params);
//The root element of the collapsed view layout
final View collapsedView = mFloatingView.findViewById(R.id.collapse_view);
//The root element of the expanded view layout
final View expandedView = mFloatingView.findViewById(R.id.expanded_container);
//Set the close button
ImageView closeButtonCollapsed = (ImageView) mFloatingView.findViewById(R.id.close_btn);
closeButtonCollapsed.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//close the service and remove the from from the window
stopSelf();
}
});
//Set the view while floating view is expanded.
//Set the play button.
ImageView playButton = (ImageView) mFloatingView.findViewById(R.id.play_btn);
playButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FloatingService.this, "Playing the song.", Toast.LENGTH_LONG).show();
}
});
//Set the next button.
ImageView nextButton = (ImageView) mFloatingView.findViewById(R.id.next_btn);
nextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FloatingService.this, "Playing next song.", Toast.LENGTH_LONG).show();
}
});
//Set the pause button.
ImageView prevButton = (ImageView) mFloatingView.findViewById(R.id.prev_btn);
prevButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FloatingService.this, "Playing previous song.", Toast.LENGTH_LONG).show();
}
});
//Set the close button
ImageView closeButton = (ImageView) mFloatingView.findViewById(R.id.close_button);
closeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
collapsedView.setVisibility(View.VISIBLE);
expandedView.setVisibility(View.GONE);
}
});
//Open the application on thi button click
ImageView openButton = (ImageView) mFloatingView.findViewById(R.id.open_button);
openButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Open the application click.
Intent intent = new Intent(FloatingService.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
//close the service and remove view from the view hierarchy
stopSelf();
}
});
//Drag and move floating view using user's touch action.
mFloatingView.findViewById(R.id.root_container).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:
int Xdiff = (int) (event.getRawX() - initialTouchX);
int Ydiff = (int) (event.getRawY() - initialTouchY);
//The check for Xdiff <10 && YDiff< 10 because sometime elements moves a little while clicking.
//So that is click event.
if (Xdiff < 10 && Ydiff < 10) {
if (isViewCollapsed()) {
//When user clicks on the image view of the collapsed layout,
//visibility of the collapsed layout will be changed to "View.GONE"
//and expanded view will become visible.
collapsedView.setVisibility(View.GONE);
expandedView.setVisibility(View.VISIBLE);
}
}
return true;
case MotionEvent.ACTION_MOVE:
//Calculate the X and Y coordinates of the view.
params.x = initialX + (int) (event.getRawX() - initialTouchX);
params.y = initialY + (int) (event.getRawY() - initialTouchY);
//Update the layout with new X & Y coordinate
mWindowManager.updateViewLayout(mFloatingView, params);
return true;
}
return false;
}
});
}
/**
* Detect if the floating view is collapsed or expanded.
*
* @return true if the floating view is collapsed.
*/
private boolean isViewCollapsed() {
return mFloatingView == null || mFloatingView.findViewById(R.id.collapse_view).getVisibility() == View.VISIBLE;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mFloatingView != null) mWindowManager.removeView(mFloatingView);
}
}
然后添加
<service
android:name="yourpakcage.FloatingService"
android:enabled="true"
android:exported="false"/>
layout_floating_widget.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!--Root container-->
<RelativeLayout
android:id="@+id/root_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="UselessParent">
<!--View while view is collapsed-->
<RelativeLayout
android:id="@+id/collapse_view"
android:layout_width="wrap_content"
android:visibility="visible"
android:layout_height="wrap_content"
android:orientation="vertical">
<!--Icon of floating widget -->
<ImageView
android:id="@+id/collapsed_iv"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="8dp"
android:src="@drawable/ic_android_circle"
tools:ignore="ContentDescription"/>
<!--Close button-->
<ImageView
android:id="@+id/close_btn"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginLeft="40dp"
android:src="@drawable/ic_close"
tools:ignore="ContentDescription"/>
</RelativeLayout>
<!--View while view is expanded-->
<LinearLayout
android:id="@+id/expanded_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#F8BBD0"
android:visibility="gone"
android:orientation="horizontal"
android:padding="8dp">
<!--Album image for the song currently playing.-->
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/music_player"
tools:ignore="ContentDescription"/>
<!--Previous button-->
<ImageView
android:id="@+id/prev_btn"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:src="@mipmap/ic_previous"
tools:ignore="ContentDescription"/>
<!--Play button-->
<ImageView
android:id="@+id/play_btn"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
android:src="@mipmap/ic_play"
tools:ignore="ContentDescription"/>
<!--Next button-->
<ImageView
android:id="@+id/next_btn"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
android:src="@mipmap/ic_play_next"
tools:ignore="ContentDescription"/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/close_button"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/ic_close"/>
<ImageView
android:id="@+id/open_button"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentBottom="true"
android:src="@drawable/ic_open"/>
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
</FrameLayout>
请添加虚拟可绘制对象和字符串。
显示浮动视图。
startService(new Intent(passContext, FloatingViewService.class));
答案 2 :(得分:-1)
很遗憾,您一直在遵循的指南已过时。由于Android Oreo中引入了Background Execution Limits,因此您试图在后台运行应用程序时启动服务,导致IllegalArgumentException
出现。链接底部包含迁移指南。