I want to make an app that can create notification on the screen on top of anything that is currently being displayed. Something like the Go SMS message popup or something like the ChatHead in the following picture:
It would be even better if it is possible to draw it dynamically including touch events.What is the conventional or standard way to do this?
Example:
Like an Icon that can be clicked or dragged no matter whether you are on home screen or app drawer or other apps.Pay attention to the circular icons near the edges of the screen in the picture posted. You can drag them anywhere in any app.
What you are looking for is System Alert Window.
There's a library called StandOut! which will assist you in creating such apps.
Here is how things like Toast and dialog windows work:
In the case where just adding or bringing to front does not work, say when you are having a service add its own view to another client activity or application (FaceUnlock does this), or you cannot depend on hierarchies, you need to use the window manager and a window token to do the trick. You can then create layouts and take advantage of animations and hardware acceleration as before.
WindowManager windowManager = (WindowManager) getBaseContext().getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.FIRST_SUB_WINDOW);
layoutParams.width = 300;
layoutParams.height = 300;
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.flags =
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
layoutParams.token = getWindow().getDecorView().getRootView().getWindowToken();
//Feel free to inflate here
mTestView = new View(this);
mTestView.setBackgroundColor(Color.RED);
//Must wire up back button, otherwise it's not sent to our activity
mTestView.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed();
}
return true;
}
});
windowManager.addView(mTestView, layoutParams);
Then be sure to remove the view onDestroy (or onPause) or you will crash
if (mTestView != null) {
WindowManager windowManager = (WindowManager) getBaseContext().getSystemService(Context.WINDOW_SERVICE);
if (mTestView.isShown()) {
windowManager.removeViewImmediate(mTestView);
}
}
You don't need a new activity to do this. All you need to do is to add another view into your existing activity and bring it to the front, and draw/write the things that you want into that view.
If you want to do special things with this extra view, you could create your own view class
class DrawOnTop extends View {
public DrawOnTop(Context activity) {
super(activity);
}
#Override
protected void onDraw(Canvas canvas) {
// put your drawing commands here
}
}
and then you do something like
DrawOnTop mDraw = new DrawOnTop(this);
addContentView(mDraw, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
mDraw.bringToFront();
Then to force it to draw, you need to use mDraw.invalidate();
You could have the parent of your whole layout as RelativeLayout. The first child being the "root" of your main layout. Anything after that can be considered an overlay which is placeable to your whims.
Example:
<RelativeLayout>
<LinearLayout>
... Main Layout here ...
</LinearLayout>
<TextView left="20dip" top="20dip" text="Overlay" alpha="0.7" />
</RelativeLayout>
The best way is to start a service with your application.
Create an ImageView.
Set the LayoutParams of the Image View.
Add the view along with the params to the window manager when the service is created.
ALL SET
Your Image sticks to your window (At any screen over all apps), till you application is closed.
You can even add onclicklisteners and ontouchlisteners to the imageview.
Eg. OnClick listeners to perform some actions and Ontouchlisteners move the image along the screen.
Related
I have a widget placed programatically on the Home screen, and I made it possible for the user to drag it to the place he wishes. I have added an OnTouchListener to the view to get motion events (so that I can remember view position on screen for next time), and it fires wherever I click on the Home screen, even when this is outside of my view.
What I would like to achieve is to forward the MotionEvent.ACTION_UP to the Launcher app so that, if user clicked on an app icon, that app will be launched. Or alternatively, NOT to receive this event when clicked outside of my view. Here's what I have done:
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflatedViewWidget = inflater.inflate(R.layout.mtc_appwidget, null);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
0,
PixelFormat.TRANSLUCENT);
final WindowManager wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
wm.addView(inflatedViewWidget, params);
OnTouchListener:
inflatedViewWidget.setClickable(true);
inflatedViewWidget.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
//how to forward event to Launcher app?
break;
AFAIK, doing a click in another app is not allowed, that would allow overlay apps to make you click on a pay button for example, you would need root or debugging capabilities for that.
So the trick is to play with the window size.
Your widget's window size and location should always match the clickable area.
You can do this with the updateViewLayout method on the WindowManager.
A good example can be found here:
https://medium.com/#kevalpatel2106/create-chat-heads-like-facebook-messenger-32f7f1a62064
I'm trying to do implement a floating widget button(like the Facebook Messenger) which is displayed over the other app. The button should be able to interfere with the displayed view of the other app and capture the whole smartphone screen underneath.
The floating button is already implemented and works fine on every app. Getting the root view and printing out the bitmap of this floating view will just return the floating button image. I think the floating window cant detect the view of the apps underneath.
One possible solution could be to start the other application as a separate activity and get its view, but I am not sure how to do that. Maybe there is some easier way to achieve this?
//Creating the floating window and set its Layout parameters
mFloatViewLayoutParams = new WindowManager.LayoutParams();
mFloatViewLayoutParams.format = PixelFormat.TRANSLUCENT;
mFloatViewLayoutParams.flags = WindowManager.LayoutParams.FORMAT_CHANGED;
mFloatViewLayoutParams.type = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
: WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
mFloatViewLayoutParams.gravity = Gravity.START;
mFloatViewLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mFloatViewLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
// adding the floatview to the WindowManager
LayoutInflater inflater = LayoutInflater.from(activity);
mFloatView = inflater.inflate(R.layout.float_view_layout, null);
mWindowManager.addView(mFloatView, mFloatViewLayoutParams);
...
// some clickListener to start the screenshot
ImageButton imageButton = mFloatView.findViewById(R.id.imageButton);
imageButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
// here I want to get the view of the whole screen to
screenshot it
}
});
}
});
What I don't want: I don't want to screenshot the view of my main activity which starts the floating button.
I am really out of ideas about how to handle this challenge. Hopefully, someone has a guess!
Cheers!
How would I create a dialog which does not consume touch events. Ie. I want to be able to interact with the underlying activity as normal.
public class LoadingDialog extends Dialog {
public LoadingDialog(Context context) {
super(context);
setContentView(R.layout.loading_dialog);
setCanceledOnTouchOutside(false);
getWindow().setBackgroundDrawable(new
ColorDrawable(android.graphics.Color.TRANSPARENT));
}
}
I tried the following with no success
#Override
public boolean dispatchTouchEvent(#NonNull MotionEvent ev) {
return false;
}
Thanks
Rather than use a dialogue, you can use a view that covers the whole screen with a tint and another view that looks like a dialogue on top of the tint and pass all touch events to the view below. Remove and show the dialogue by toggling the visibility of the view.
I am trying to execute an animation in which I have two fragments stacked on top of each other.
The top fragment is a details fragment.
the bottom fragment is a menu list view fragment.
I did this by creating two overlapping framelayouts in the activity layout. I want to be able to do an animation in which the background fragment would be revealed in a fashion similar to a door opening leaving only 20 percent of the edge of the top fragment in view.
I tried doing this animation with the standard view animation library available to API 9 but it seemed that only the pixels were moved and but the touch mapping still corresponded to the top fragment and the bottom menu fragment could not be accessed.
So I downloaded the nineoldandroids library and tried to user AnimatorSet with ObjectAnimators to do the animation... except this time when the fragment is animated away it reveals only a gray background rather than the fragment in the back like before.
This is a code snippet on how I tried to implement a simple translation to reveal the background fragment
private void animateFragmentOut() {
activeFragment = (Fragment)getSupportFragmentManager().findFragmentById(R.id.nav_item_fragment_container);
View myView = activeFragment.getView();
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(myView, "translationX", 0, 230)
);
set.setDuration(500).start();
}
Why is the background fragment not shown when I use this animation?
How do I use nineoldandroids to reveal the background fragment properly?
I ended up solving this problem by using a listener to inflate the background view right at the start of the animation using the following code
private void animateFragmentOut() {
activeFragment = (Fragment)getSupportFragmentManager().findFragmentById(R.id.nav_item_fragment_container);
View myView = activeFragment.getView();
Animator.AnimatorListener listener = new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
launchNavigationFragment();
}
#Override
public void onAnimationEnd(Animator animation) {
}
};
ObjectAnimator rotateY = ObjectAnimator.ofFloat(myView,"rotationY",-15f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(myView,"scaleX",0.8f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(myView,"scaleY", 0.8f);
ObjectAnimator translateX = ObjectAnimator.ofFloat(myView,"translationX",400f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(rotateY,scaleX,scaleY,translateX);
animatorSet.addListener(listener);
animatorSet.setDuration(700);
animatorSet.start();
}
Saldy despite this working great on API levels 11> and up on my API 9 device I am still having the same problem I had with the standard animation library. The view pixels translate but the touch areas stay mapped in the same place. So I cannot interact with the background navigation menu fragment.
I have multiple HorizontalScrollViews inside a ScrollView. Horizontal scroll isn't smooth at all. I have to scroll almost perfectly horizontally for scrolling to work. Is there a simple fix to tweak this ??? Thanks!
You can use Recycler view with Staggered layout manager
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.HORIZONTAL);
RecyclerViewAdapter recyclerViewAdapter = newRecyclerViewAdapter(this);
recyclerView.setAdapter(recyclerViewAdapter); //Don't miss to initialize your adapter
This class creates a ScrollView containing a HorizontalScrollView combined into one class. You can put stuff inside it using the AddChild() method. The dispatchTouchEvent overide keeps the scrolling smooth so you can pan around with a single slide of the finger.
(I recently used this to wrap a programmatically created TextView)
class MultiScrollView extends ScrollView
{
public HorizontalScrollView hscroll;
public MultiScrollView ( Context context )
{
super( context );
}
public void AddChild( View child )
{
hscroll.addView( child );
}
#Override
public boolean dispatchTouchEvent( MotionEvent event )
{
hscroll.dispatchTouchEvent(event);
onTouchEvent(event);
return true;
}
}
If you are using the horizontal scroll view solution from (http://www.dev-smart.com/archives/34) the solution for the cross focus problem between the scroll view and the list view is blocking the focus to the scroll view once you have focus on the list view.
From a technical point of view you should add the following line to the onScroll function inside the HorizontalListView class.
getParent().requestDisallowInterceptTouchEvent(true);
Hope this helps.
I've found the solution and still can't believe that this is what you have to do to make this work normal! Just added blank onClickListener to the each item in the HorizontalScrollView:
item.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
After this slide is really smooth, both upwards and downwards.
In general, you shouldn't be using nested ScrollViews in Android at all, the behaviour of scrolling in this way is unnatural too.
You may want to rethink your layout design, is it anything that couldn't be achieved with an expandable list?
While David's answer works, it has a downside. It passes ScrollView's MotionEvent object directly to HorizontalScrollView.onTouchEvent(), so if HorizontalScrollView or its children try to get the event coordinates, they will get the wrong coordinates which based on ScrollView.
My solution:
public class CustomScrollView extends ScrollView{
/*************skip initialization*************/
#Override
public boolean onInterceptTouchEvent(MotionEvent e){
//returning false means ScrollView is not interested at any events,
//so ScrollView's onTouchEvent() won't be called,
//and all of the events will be passed to ScrollView's child
return false;
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//manually call ScrollView's onTouchEvent(),
//the vertical scrolling happens there.
onTouchEvent(ev);
//dispatch the event,
//ScrollView's child will have every event.
return super.dispatchTouchEvent(ev);
}
}
Just wrap this CustomScrollView around the HorizontalScrollView in your layout file.