1
To create a Float Button that works outside the app, you need to work with Service, it works as if it were a Broadcast that always works in background, IE, when the APP is closed or in the background.
To create the service, you need to do something in a way that creates the image that will be the float button via codes, example:
public class BubbleNoteService extends Service {
public static final String TAG = "BubbleNoteService";
private static final int MOVE_THRESHOLD = 100; // square of the threshold distance in pixels
private WindowManager mWindowManager;
private ViewGroup mBubble;
private View mContent;
private boolean mbExpanded = false;
private boolean mbMoved = false;
private int[] mPos = {0, -20};
private static Spring sBubbleSpring;
private static Spring sContentSpring;
public static int sSpringTension = 200;
public static int sSpringFriction = 20;
public static void setSpringConfig() {
SpringConfig config = sBubbleSpring.getSpringConfig();
config.tension = sSpringTension;
config.friction = sSpringFriction;
}
@Override
public IBinder onBind(Intent intent) {
// Not used
return null;
}
@Override public void onCreate() {
super.onCreate();
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
mBubble = (ViewGroup) inflater.inflate(R.layout.bubble, null, false);
mContent = mBubble.findViewById(R.id.content);
mContent.setScaleX(0.0f);
mContent.setScaleY(0.0f);
ViewGroup.LayoutParams contentParams = mContent.getLayoutParams();
contentParams.width = Utils.getScreenWidth(this);
contentParams.height = Utils.getScreenHeight(this) - getResources().getDimensionPixelOffset(R.dimen.bubble_height);
mContent.setLayoutParams(contentParams);
mBubble.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mContent.setPivotX(mBubble.findViewById(R.id.bubble).getWidth() / 2);
if (Build.VERSION.SDK_INT >= 16) {
mBubble.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
mBubble.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
});
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = mPos[0];
params.y = mPos[1];
params.dimAmount = 0.6f;
SpringSystem system = SpringSystem.create();
SpringConfig springConfig = new SpringConfig(sSpringTension, sSpringFriction);
sContentSpring = system.createSpring();
sContentSpring.setSpringConfig(springConfig);
sContentSpring.setCurrentValue(0.0);
sContentSpring.addListener(new SpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
float value = (float) spring.getCurrentValue();
float clampedValue = (float) SpringUtil.clamp(value, 0.0, 1.0);
mContent.setScaleX(value);
mContent.setScaleY(value);
mContent.setAlpha(clampedValue);
}
@Override
public void onSpringAtRest(Spring spring) {
mContent.setLayerType(View.LAYER_TYPE_NONE, null);
if (spring.currentValueIsApproximately(0.0)) {
hideContent();
}
}
@Override
public void onSpringActivate(Spring spring) {
mContent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
@Override
public void onSpringEndStateChange(Spring spring) {
}
});
sBubbleSpring = system.createSpring();
sBubbleSpring.setSpringConfig(springConfig);
sBubbleSpring.setCurrentValue(1.0);
sBubbleSpring.addListener(new SpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
double value = spring.getCurrentValue();
params.x = (int) (SpringUtil.mapValueFromRangeToRange(value, 0.0, 1.0, 0.0, mPos[0]));
params.y = (int) (SpringUtil.mapValueFromRangeToRange(value, 0.0, 1.0, 0.0, mPos[1]));
mWindowManager.updateViewLayout(mBubble, params);
if (spring.isOvershooting() && sContentSpring.isAtRest()) {
sContentSpring.setEndValue(1.0);
}
}
@Override
public void onSpringAtRest(Spring spring) {
}
@Override
public void onSpringActivate(Spring spring) {
}
@Override
public void onSpringEndStateChange(Spring spring) {
}
});
mBubble.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:
mbMoved = false;
initialX = params.x;
initialY = params.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
showContent();
return true;
case MotionEvent.ACTION_UP:
if (mbMoved) return true;
if (! mbExpanded) {
mBubble.getLocationOnScreen(mPos);
mPos[1] -= Utils.getStatusBarHeight(BubbleNoteService.this);
params.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
sBubbleSpring.setEndValue(0.0);
} else {
params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND;
sBubbleSpring.setEndValue(1.0);
sContentSpring.setEndValue(0.0);
}
mbExpanded = ! mbExpanded;
mWindowManager.updateViewLayout(mBubble, params);
return true;
case MotionEvent.ACTION_MOVE:
int deltaX = (int) (event.getRawX() - initialTouchX);
int deltaY = (int) (event.getRawY() - initialTouchY);
params.x = initialX + deltaX;
params.y = initialY + deltaY;
if (deltaX * deltaX + deltaY * deltaY >= MOVE_THRESHOLD) {
mbMoved = true;
hideContent();
mWindowManager.updateViewLayout(mBubble, params);
}
return true;
}
return false;
}
});
mWindowManager.addView(mBubble, params);
}
@Override
public void onDestroy() {
super.onDestroy();
if (mBubble != null) {
mWindowManager.removeView(mBubble);
}
}
private void showContent() {
mContent.setVisibility(View.VISIBLE);
}
private void hideContent() {
mContent.setVisibility(View.GONE);
}
}
With this service created, you already have the image (float button) and capture all its movements on the device.
To control the click on the floatbutton, you must have a call on the onCreate of your Mainactivity, thus:
Intent intent = new Intent(MainActivity.this, BubbleNoteService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
And to control the background, you can put onStop or onDestroy:
Below are some libs links that control this:
https://github.com/JustinScott/AppFloater
https://github.com/vickychijwani/BubbleNote
While this link may answer the question, it is best to include the essential parts of the answer here and provide the link for reference. Replies per link only can be invalidated if the page with the link is changed. - From Review
– Diego
Thanks for the tip Diego, I changed my answer.
– Leonardo Dias