I'm trying to create a PopupWindow in Android that centers itself in the middle of the display and is dynamically sized to the content of the loaded view. In addition, any tap outside of the popup should dismiss the popup. In SDK versions 11 on, this code works just fine, however in SDK 10 (the minimum our app must support), the setWindowLayoutMode seemingly does nothing.
I've so far subclassed this logic, which seems clean and efficient to me, with the exception of the issue (bug?) with SDK 10. Any thoughts on what I am doing wrong? I see that setWindowLayoutMode has been around since version 3, so I'm having trouble believing it is simply not working as it is described to in the documentation. If an SDK bug is the case, how might I workaround the problem? I tried .measure() on the contentView with the screen dimensions as the restrictions with the intention of manually setting the window size, but the values it returned were wildly different from the expected results.
I can easily get the window centered by wrapping the TextView in a layout that I can set to match the screen dimensions, but then I lose the nice ACTION_OUTSIDE tap event, so I'd rather not stumble down that path if I can avoid it.
I should mention that the problem that is happening on SDK 10 is that the window simply doesn't appear... It is technically "appearing", either with dimensions of 0,0 or offscreen, as subsequent taps trigger the OnTouchListener, but it surely is not correctly displaying its contents.
public class InfoPopupWindow extends PopupWindow {
private View _parentView;
public InfoPopupWindow(Context context, View parentView) {
super(context);
LayoutInflater inflater = LayoutInflater.from(context);
View contentView = inflater.inflate(R.layout.window_info, null, false);
this.setContentView(contentView);
this.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
// This combo of parameters sends outside events properly, and inside events as well.
this.setOutsideTouchable(true);
this.setBackgroundDrawable(new BitmapDrawable());
this.setTouchInterceptor(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
Log.d("InfoPopupWindow", "Outside Window Touch Event");
dismiss();
}
return true;
}
});
this.setAnimationStyle(R.style.PopupAnimation);
_parentView = parentView;
}
public void show() {
this.showAtLocation(_parentView, Gravity.CENTER, 0, 0);
}
}
R.layout.window_info
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/info_text"
android:id="#+id/textView"
android:background="#000000"
android:padding="15dp" />
Well, I never found the solution to the broken setWindowLayoutMode, but I was able to call this method from the constructor to get API 10 to correctly size the popup. The workaround was one I had tested before, but I had been misusing the MeasureSpec parameters. Hope this helps someone else out someday...
private void calculateAndSetContentViewSize(Context context, View contentView) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
int screenWidth, screenHeight;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) {
Point windowSize = new Point();
display.getSize(windowSize);
screenWidth = windowSize.x;
screenHeight = windowSize.y;
} else {
screenWidth = display.getWidth();
screenHeight = display.getHeight();
}
int widthSpec = View.MeasureSpec.makeMeasureSpec(screenWidth, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(screenHeight, View.MeasureSpec.UNSPECIFIED);
contentView.measure(widthSpec, heightSpec);
this.setWidth(contentView.getMeasuredWidth());
this.setHeight(contentView.getMeasuredHeight());
this.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
Related
I want to detect pressing of a back button in service. I've just tried this code but it didn't show me any log. Can somebody explain me why? And what should I do to make it work?
The whole idea of doing this was was taken from this tutorial http://www.kpbird.com/2013/03/android-detect-global-touch-event.html
public class MyService extends Service implements View.OnKeyListener{
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
LinearLayout touchLayout = new LinearLayout(this);
// set layout width 30 px and height is equal to full screen
LayoutParams lp = new LayoutParams(30, LayoutParams.MATCH_PARENT);
touchLayout.setLayoutParams(lp);
touchLayout.setBackgroundColor(Color.RED);
touchLayout.setOnKeyListener(this);
WindowManager mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// set layout parameter of window manager
WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(
30, // width of layout 30 px
WindowManager.LayoutParams.MATCH_PARENT, // height is equal to full screen
WindowManager.LayoutParams.TYPE_PHONE, // Type Phone, These are non-application windows providing user interaction with the phone (in particular incoming calls).
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, // this window won't ever get key input focus
PixelFormat.TRANSLUCENT);
mParams.gravity = Gravity.LEFT | Gravity.TOP;
mWindowManager.addView(touchLayout, mParams);
}
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK){
Log.v("Point","KeyCode_Back");
return false;
}
return false;
}
}
Your Service is not a View, implementing a View.OnKeyListener does not deliver your desired functionality.
A Service is intended to be an "Activity without UI" which runs in the background of your application. You can use Binders/Broadcasts to communicate with your service but UI interaction is best left to Activity/Fragments.
Annex:
I guess you are trying to build a overlay like in the link you posted in the comment. This Tutorial is from 2013 so things have changed.
In general the Android system discourages App beheaviour like the below described method. Coding like this, goes into the category Lockscreen/Kiosk-App behaviour which is considered as malware.
If you want to accomplish a little side menu inside your app you can do this perfectly fine without using such a service. Outside your App you still have the options of using widgets, which are more user friendly than hardcoding something on the screen.
I'm trying to force the EditText control to lose focus when the user presses the back button to hide the keyboard. There are many questions similar to this already, but after several hours, I haven't been able to make it work.
First, just a little bit of context. I have a ListView with custom items. Each item has several TextViews and one EditText. I have an AfterTextChanged() method saving edited values. I have a style set up to highlight the field if it has focus. Unfortunately, it is now much more obvious that the EditText doesn't actually lose focus when you hide the (soft) keyboard, and I think it's confusing. I would like the EditText to not be focused if there's no keyboard.
The solution that seemed the most reasonable is to override OnBackPressed() in the activity as described here. Unfortunately, it doesn't appear that my method is being called. I.e. the field is still focused, and a breakpoint in the function doesn't fire.
Similarly, an OnKeyUp() listener on the activity doesn't fire, and Xamarin doesn't appear to support the OnKeyUp handler for the EditText control.
I'm not trying to suppress the keyboard on creation, or anything, so using any of the invisible control tricks don't help either.
It's obvious that a lot of people have this problem. I'm sure one of you has solved it! Can you please share your solution?
Thank you so much!
-Karen
P.S. I do not need to know how to hide the keyboard. I need to take an action when the user hides the keyboard with the back button. Thanks :)
In my experience onBackPressed() (at least the default #Override one in an activity) will not normally fire when pushing the back button to close the keyboard. As far as I know it will only fire when a Back press would initiate a finish() on the current activity.
Below is a kind of "hacky" way to know when the keyboard is shown/hidden by monitoring the change in the view size. You must also set the Activity to android:windowSoftInputMode="adjustResize" in the AndroidManifest.xml.
final View activityRootView = findViewById("Your main View");
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
activityRootView.getWindowVisibleDisplayFrame(r);
int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
//Keyboard is shown
}
if(heightDiff <= 100) {
//Keybaord not shown
}
}
});
With sincere thanks to #Shadesblade (and Xamarin's sample code), my EditTexts now unfocus! Here's the Xamarin-ized solution:
To your activity, add this class:
class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
Action on_global_layout;
public GlobalLayoutListener (Action onGlobalLayout)
{
on_global_layout = onGlobalLayout;
}
public void OnGlobalLayout ()
{
on_global_layout ();
}
}
Add a class variable to hold the View so that the delegate can access it:
View _rootview;
In your OnCreate() add:
GlobalLayoutListener gll = new GlobalLayoutListener(
delegate {
Android.Graphics.Rect r = new Android.Graphics.Rect();
_rootView.GetWindowVisibleDisplayFrame(r);
int heightDiff = _rootView.RootView.Height - (r.Bottom - r.Top);
if (heightDiff < 100)
{
if (Window.CurrentFocus != null)
Window.CurrentFocus.ClearFocus();
}
});
_rootView = FindViewById<View>(Resource.Id.relativeLayoutOrder);
_rootView.ViewTreeObserver.AddOnGlobalLayoutListener(gll);
I expect to need to dork around with the heightDiff level and/or have to add some rotation checking, but I haven't done any rotation support at this point, so I can punt that until later.
Thank you again! *happy dance*
adding on to Shadesblade's answer, if you are using a scrollview, his answer needs a change to work, because not all of the scrollview is showing on screen.
so instead of doing
int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
you should do
int heightDiff = Utils.getScreenHeight(SearchActivity.this) - (r.bottom - r.top);
where Utils.getScreenHeight is this:
public static int getScreenHeight(Context c) {
if (screenHeight == 0) {
WindowManager wm = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
screenHeight = size.y;
screenWidth = size.x;
}
return screenHeight;
}
Is there a way to know the size of the keyboard that is shown in the screen?
I am using Cocos2dx for programming, but I want to know the height of the keyboard shown in screen in the part of Android or the part of Cocos, it does not matter.
I know that Keyboard has a getHeight() method but I don't want to create new keyboards, i want to use the default one.
We did it with this
myLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
parent.getWindowVisibleDisplayFrame(r);
int screenHeight = parent.getRootView().getHeight();
int heightDifference = screenHeight - (r.bottom - r.top);
Log.d("Keyboard Size", "Size: " + heightDifference);
}
});
We only resize views with the keyboard, so we could use this.
Rect r = new Rect();
View rootview = this.getWindow().getDecorView(); // this = activity
rootview.getWindowVisibleDisplayFrame(r);
Result of this is the amount of space your application uses on screen (works even when activity is not resized). Obviously remaining screen space will be used by the keyboard ( if its visible)
Found id up here: https://github.com/freshplanet/ANE-KeyboardSize/blob/master/android/src/com/freshplanet/ane/KeyboardSize/getKeyboardY.java
if your activity is not fullscreen, using code below:
content.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
if (keyBoardHeight <= 100) {
Rect r = new Rect();
content.getWindowVisibleDisplayFrame(r);
int screenHeight = content.getRootView()
.getHeight();
int heightDifference = screenHeight
- (r.bottom - r.top);
int resourceId = getResources()
.getIdentifier("status_bar_height",
"dimen", "android");
if (resourceId > 0) {
heightDifference -= getResources()
.getDimensionPixelSize(resourceId);
}
if (heightDifference > 100) {
keyBoardHeight = heightDifference;
}
Log.d("Keyboard Size", "Size: " + heightDifference);
}
// boolean visible = heightDiff > screenHeight / 3;
}
});
If you want to calculate the Virtual Keyboard height while your activity does not change in size (adjustPan) then you can use this sample:
https://github.com/siebeprojects/samples-keyboardheight
It uses a hidden window in order to calculate the height difference between the window and the root view of the activity.
You can't tell. No, really: you simply can't tell.
The keyboard does not need to be any particular shape. It does not have to be placed at the bottom of the screen (many of the most popular options are not), it does not have to keep its current size when you change text fields (almost none do depending on the flags). It does not even have to be rectangular. It may also just take over the entire screen.
I know this is an old post, but I noticed that the chosen solution for me did not work on all devices. There seemed to be a discrepancy and so I implemented this and it seems to be a catch all:
final int[] discrepancy = new int[1];
discrepancy[0] = 0;
// this gets the height of the keyboard
content.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
View rootview = activity.getWindow().getDecorView(); // this = activity
rootview.getWindowVisibleDisplayFrame(r);
int screen_height = rootview.getRootView().getHeight();
int keyboard_height = screen_height - (r.bottom + r.top) - discrepancy[0];
if (discrepancy[0] == 0) {
discrepancy[0] = keyboard_height;
if (keyboard_height == 0) discrepancy[0] = 1;
}
int margin_bottom = keyboard_height + Helper.getDp(10, activity);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) carousel_container.getLayoutParams();
params.setMargins(0, 0, 0, margin_bottom);
//boolean visible = heightDiff > screenHeight / 3;
}
});
When the listener is first called it measures the screen without a keyboard and if there is a discrepancy I account for it the next time around. If there is no discrepancy I set the discrepancy to 1 just so it is no longer 0.
After 2020, if your min SDK large or equal then 21, you can check the visibility and height of IME by below functions:
fun isKeyboardVisible(attachedView: View): Boolean {
val insets = ViewCompat.getRootWindowInsets(attachedView)
return insets?.isVisible(WindowInsetsCompat.Type.ime()) ?: false
}
fun getKeyboardHeight(attachedView: View): Int {
val insets = ViewCompat.getRootWindowInsets(attachedView)
return insets?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0
}
Ref: Animating your keyboard (part 1). New WindowInsets APIs for checking theā¦ | by Chris Banes | Android Developers | Medium
in cocos2d-x we have got CCEditBox.
Inside Extensions->GUI->CCEditBox, you can find the class CCEditBox.
The beauty is that it hides the keyboard of tapping somewhere else on the scene. and automatically moves the keyboard up incase your edit box was placed too low on the scene.
If you are using cocos2d-x v2.1.3 then you can navigate to sample Project by going to
samples->cpp->TestCpp->Classes->ExtensionTest->EditBoxTest.
I'm just going to use it instead of CCTextField from now on. just came across it yesterday :)
After hours of searching I found a solution if you want to set windowSoftInput="adjustPan"
Here is the code snippet:
final View root = findViewById(android.R.id.content);
root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
Rect r = new Rect();
{
root.getWindowVisibleDisplayFrame(r);
}
#Override
public void onGlobalLayout() {
Rect r2 = new Rect();
root.getWindowVisibleDisplayFrame(r2);
int keyboardHeight = r.height() - r2.height();
if (keyboardHeight > 100) {
root.scrollTo(0, keyboardHeight);
}
else {
root.scrollTo(0, 0);
}
}
});
In this code, after I found the keyboard height I scroll the view up to not covered by the keyboard which is the main reason for finding the keyboard height.
According to the docs :
void getWindowVisibleDisplayFrame(Rect outRect) : Retrieve the overall visible display size in which the window this view is attached to has been positioned in.
The ROOT_VIEW of an android display screen can be visualized as being a single screen view with VISIBLE DISPLAY FRAME which displays your activity's view.
This VISIBLE DISPLAY FRAME is adjusted when SOFT KEYBOARD is displayed or hidden from the screen.
NOTE : Please look at the two images by clicking on the links given below for better understanding
So the ROOT VIEW of a display screen can be visualized as :
RootView of display screen
The adjustment of VISIBLE DISPLAY FRAME with the opening and closing of SOFT KEYBOARD can be visualized as :
VISIBLE_DISPLAY_SCREEN adjustment
This adjustment of the VISUAL DISPLAY FRAME can be very well used to find out the height of the keyboard as :
(when the soft keyboard is open)
SOFT_KEYBOARD_HEIGHT = ROOT_VIEW_HEIGHT - (VISUAL_DISPLAY_FRAME_HEIGHT + EXTRA_SCREEN_HEIGHT)
The code to achieve the above is :
int mExtraScreenHeight=-1, mKeyboardHeight=-1;
boolean mKeyboardOpen;
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int rootViewHeight, visibleDisplayFrameHeight, fakeHeight;
/* (rootViewHeight - visibleDisplayFrameHeight) is not the real height of the keyboard
it is the fake height as it also consist of extra screen height
so FAKE_HEIGHT = KEYBOARD_HEIGHT + EXTRA_SCREEN_HEIGHT
To get keyboard height extra screen height must be removed from fake height
*/
Rect rect = new Rect();
rootView.getWindowVisibleDisplayFrame(rect);
rootViewHeight = rootView.getRootView().getHeight();
visibleDisplayFrameHeight = rect.height();
fakeHeight = rootViewHeight-visibleDisplayFrameHeight;
if (mExtraScreenHeight == -1){
mExtraScreenHeight=fakeHeight;
}
/* Suppose the soft keyboard is open then the VISIBLE_DISPLAY_FRAME is in reduced size
due to the space taken up by extra screen and the keyboard but when the soft keyboard closes
then KEYBOARD_HEIGHT=0 and thus FAKE_HEIGHT = EXTRA_SCREEN_HEIGHT
*/
else if (fakeHeight <= mExtraScreenHeight){
mExtraScreenHeight=fakeHeight;
mKeypadOpen=false;
}
else if (fakeHeight > mExtraScreenHeight){
mKeypadHeight=fakeHeight-mExtraScreenHeight;
mKeypadOpen=true;
}
}
});
NOTE : The onGlobalLayout() function will be called only when the global layout changes like when the soft keyboard opens. So the soft keyboard must be open at least once to get the soft keyboard height.
It worked for me ;)
Sorry for not being able to comment, two or three of the answers helped me solve my issue and they were related to using the AddOnGlobalLayoutListener and then determining the remaining height before and after a keyboard showed up.
The solution I used was based off of Rudy_TM's answer.
HOWEVER, one thing that I had to find was that in order for that method to work, you must have the following line somewhere
Window.SetSoftInputMode(SoftInput.AdjustResize);
Before I had SoftInput.AdjustNothing (or something like that) and it would not work. Now it works perfect. Thanks for the answers!
Complete answer & worked perfectly for me:
Rect r = new Rect();
View rootview = this.getWindow().getDecorView(); // this = activity
rootview.getWindowVisibleDisplayFrame(r);
int keyboardHeight = rootview.getHeight() - r.bottom;
I have created a view that displays on top of all applications and windows with the following code:
//These three are our main components.
WindowManager wm;
LinearLayout ll;
WindowManager.LayoutParams ll_lp;
//Just a sample layout parameters.
wm = (WindowManager) getSystemService(WINDOW_SERVICE);
ll_lp = new WindowManager.LayoutParams();
ll_lp.format = PixelFormat.TRANSLUCENT;
ll_lp.height = WindowManager.LayoutParams.FILL_PARENT;
ll_lp.width = WindowManager.LayoutParams.FILL_PARENT;
ll_lp.gravity = Gravity.CLIP_HORIZONTAL | Gravity.TOP;
//This one is necessary.
ll_lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
//Play around with these two.
ll_lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
ll_lp.flags = ll_lp.flags | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//This is our main layout.
ll = new LinearLayout(this);
ll.setBackgroundColor(android.graphics.Color.argb(50, 255, 255, 255));
ll.setHapticFeedbackEnabled(true);
ll.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Toast.makeText(getApplicationContext(), "TOUCHED", Toast.LENGTH_SHORT).show();
return false;
}
});
//And finally we add what we created to the screen.
wm.addView(ll, ll_lp);
Because FLAG_NOT_TOUCHABLE is set, it only displays the view but doesn't receive any touch events. The application behind the view receives all touch events.
However, if i don't set the flag, then only the view receives touches causing the application behind it to not receive any.
Is there a way for both the view and the application behind it to receive touches? I have tried returning false but still the same.
Any help would be greatly appreciated!
If im not mistaking, the view thats in the background is not receiving touch events because its being filtered out by the system to prevent "click jacking" exploits.
You might be able to get around this system feature by turning off the filtering of touch events for the view in the background using the View.setFilterTouchesWhenObscured(Boolean) method.
What's the best way to check if the view is visible on the window?
I have a CustomView which is part of my SDK and anybody can add CustomView to their layouts. My CustomView is taking some actions when it is visible to the user periodically. So if view becomes invisible to the user then it needs to stop the timer and when it becomes visible again it should restart its course.
But unfortunately there is no certain way of checking if my CustomView becomes visible or invisible to the user. There are few things that I can check and listen to: onVisibilityChange //it is for view's visibility change, and is introduced in new API 8 version so has backward compatibility issue
onWindowVisibilityChange //but my CustomView can be part of a ViewFlipper's Views so it can pose issues
onDetachedFromWindows //this not as useful
onWindowFocusChanged //Again my CustomView can be part of ViewFlipper's views. So if anybody has faced this kind of issues please throw some light.
In my case the following code works the best to listen if the View is visible or not:
#Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
Log.e(TAG, "is view visible?: " + (visibility == View.VISIBLE));
}
onDraw() is called each time the view needs to be drawn. When the view is off screen then onDraw() is never called. When a tiny bit of the view is becomes visible to the user then onDraw() is called. This is not ideal but I cannot see another call to use as I want to do the same thing. Remember to call the super.onDraw or the view won't get drawn. Be careful of changing anything in onDraw that causes the view to be invalidate as that will cause another call to onDraw.
If you are using a listview then getView can be used whenever your listview becomes shown to the user.
obviously the activity onPause() is called all your views are all covered up and are not visible to the user. perhaps calling invalidate() on the parent and if ondraw() is not called then it is not visible.
This is a method that I have used quite a bit in my apps and have had work out quite well for me:
static private int screenW = 0, screenH = 0;
#SuppressWarnings("deprecation") static public boolean onScreen(View view) {
int coordinates[] = { -1, -1 };
view.getLocationOnScreen(coordinates);
// Check if view is outside left or top
if (coordinates[0] + view.getWidth() < 0) return false;
if (coordinates[1] + view.getHeight() < 0) return false;
// Lazy get screen size. Only the first time.
if (screenW == 0 || screenH == 0) {
if (MyApplication.getSharedContext() == null) return false;
Display display = ((WindowManager)MyApplication.getSharedContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
try {
Point screenSize = new Point();
display.getSize(screenSize); // Only available on API 13+
screenW = screenSize.x;
screenH = screenSize.y;
} catch (NoSuchMethodError e) { // The backup methods will only be used if the device is running pre-13, so it's fine that they were deprecated in API 13, thus the suppress warnings annotation at the start of the method.
screenW = display.getWidth();
screenH = display.getHeight();
}
}
// Check if view is outside right and bottom
if (coordinates[0] > screenW) return false;
if (coordinates[1] > screenH) return false;
// Else, view is (at least partially) in the screen bounds
return true;
}
To use it, just pass in any view or subclass of view (IE, just about anything that draws on screen in Android.) It'll return true if it's on screen or false if it's not... pretty intuitive, I think.
If you're not using the above method as a static, then you can probably get a context some other way, but in order to get the Application context from a static method, you need to do these two things:
1 - Add the following attribute to your application tag in your manifest:
android:name="com.package.MyApplication"
2 - Add in a class that extends Application, like so:
public class MyApplication extends Application {
// MyApplication exists solely to provide a context accessible from static methods.
private static Context context;
#Override public void onCreate() {
super.onCreate();
MyApplication.context = getApplicationContext();
}
public static Context getSharedContext() {
return MyApplication.context;
}
}
In addition to the view.getVisibility() there is view.isShown().
isShown checks the view tree to determine if all ancestors are also visible.
Although, this doesn't handle obstructed views, only views that are hidden or gone in either themselves or one of its parents.
In dealing with a similar issue, where I needed to know if the view has some other window on top of it, I used this in my custom View:
#Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (!hasWindowFocus) {
} else {
}
}
This can be checked using getGlobalVisibleRect method. If rectangle returned by this method has exactly the same size as View has, then current View is completely visible on the Screen.
/**
* Returns whether this View is completely visible on the screen
*
* #param view view to check
* #return True if this view is completely visible on the screen, or false otherwise.
*/
public static boolean onScreen(#NonNull View view) {
Rect visibleRect = new Rect();
view.getGlobalVisibleRect(visibleRect);
return visibleRect.height() == view.getHeight() && visibleRect.width() == view.getWidth();
}
If you need to calculate visibility percentage you can do it using square calculation:
float visiblePercentage = (visibleRect.height() * visibleRect.width()) / (float)(view.getHeight() * view.getWidth())
This solution takes into account view obstructed by statusbar and toolbar, also as view outside the window (e.g. scrolled out of screen)
/**
* Test, if given {#code view} is FULLY visible in window. Takes into accout window decorations
* (statusbar and toolbar)
*
* #param view
* #return true, only if the WHOLE view is visible in window
*/
public static boolean isViewFullyVisible(View view) {
if (view == null || !view.isShown())
return false;
//windowRect - will hold available area where content remain visible to users
//Takes into account screen decorations (e.g. statusbar)
Rect windowRect = new Rect();
view.getWindowVisibleDisplayFrame(windowRect);
//if there is toolBar, get his height
int actionBarHeight = 0;
Context context = view.getContext();
if (context instanceof AppCompatActivity && ((AppCompatActivity) context).getSupportActionBar() != null)
actionBarHeight = ((AppCompatActivity) context).getSupportActionBar().getHeight();
else if (context instanceof Activity && ((Activity) context).getActionBar() != null)
actionBarHeight = ((Activity) context).getActionBar().getHeight();
//windowAvailableRect - takes into account toolbar height and statusbar height
Rect windowAvailableRect = new Rect(windowRect.left, windowRect.top + actionBarHeight, windowRect.right, windowRect.bottom);
//viewRect - holds position of the view in window
//(methods as getGlobalVisibleRect, getHitRect, getDrawingRect can return different result,
// when partialy visible)
Rect viewRect;
final int[] viewsLocationInWindow = new int[2];
view.getLocationInWindow(viewsLocationInWindow);
int viewLeft = viewsLocationInWindow[0];
int viewTop = viewsLocationInWindow[1];
int viewRight = viewLeft + view.getWidth();
int viewBottom = viewTop + view.getHeight();
viewRect = new Rect(viewLeft, viewTop, viewRight, viewBottom);
//return true, only if the WHOLE view is visible in window
return windowAvailableRect.contains(viewRect);
}
you can add to your CustomView's constractor a an onScrollChangedListener from ViewTreeObserver
so if your View is scrolled of screen you can call view.getLocalVisibleRect() and determine if your view is partly offscreen ...
you can take a look to the code of my library : PercentVisibleLayout
Hope it helps!
in your custom view, set the listeners:
getViewTreeObserver().addOnScrollChangedListener(this);
getViewTreeObserver().addOnGlobalLayoutListener(this);
I am using this code to animate a view once when it is visible to user.
2 cases should be considered.
Your view is not in the screen. But it will be visible if user scrolled it
public void onScrollChanged() {
final int i[] = new int[2];
this.getLocationOnScreen(i);
if (i[1] <= mScreenHeight - 50) {
this.post(new Runnable() {
#Override
public void run() {
Log.d("ITEM", "animate");
//animate once
showValues();
}
});
getViewTreeObserver().removeOnScrollChangedListener(this);
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
Your view is initially in screen.(Not in somewhere else invisible to user in scrollview, it is in initially on screen and visible to user)
public void onGlobalLayout() {
final int i[] = new int[2];
this.getLocationOnScreen(i);
if (i[1] <= mScreenHeight) {
this.post(new Runnable() {
#Override
public void run() {
Log.d("ITEM", "animate");
//animate once
showValues();
}
});
getViewTreeObserver().removeOnGlobalLayoutListener(this);
getViewTreeObserver().removeOnScrollChangedListener(this);
}
}