At a given pixel location, there is a view. I have the coordinates of the pixel. How to find the id of a view at a given coordinate ?
If you are inside of ViewGroup:
int count = viewgroup.getChildCount();
for (int i = 0; i < count; i++)
{
View view = viewgroup.getChildAt(i);
if (view.getX() == theX && view.getY() == theY)
return view.getId()
}
EDIT (kcoppock): within the for loop, I'd do something like this:
View view = viewgroup.getChildAt(i);
if(!view.getVisibility() == View.VISIBLE) continue;
int[] location = {0, 0};
view.getLocationOnScreen(location);
int right = location[0] + view.getWidth();
int bottom = location[1] + view.getHeight();
Rect bounds = new Rect(location[0], location[1], right, bottom);
if(bounds.contains(coordinatesX, coordinatesY)) return view.getId();
I think something like this should work:
Walk your view hierarchy to find child views (i.e. those with no sub-views) that are Visible.
use View.getLocationOnScreen() on your views to retrieve their location (the top/left window coordinate)
use getMeasuredWidth() / getMeasuredHeight() to get the view width and height
see if your pixel coordinate falls within this rectangle
Related
My Android app has a game map with markers on it. When the user taps a marker, I'm displaying a PopupWindow aligned with the map marker (similar to Google Maps). The problem I'm having is if a marker is close to the top of the screen, the PopupWindow overlaps with the ActionBar (and under the status bar, if the marker is high enough).
I'm displaying the PopupWindow by calling showAtLocation(), which I'd hoped would constrain the view to inside the "Map" fragment (it does on the left and right sides), but that's not working.
I had already implemented an adjustment, to account for text inside the popup taking up more than one line, where I update the Y position of the popup after the View has been laid out. That worked with no problem, but when I tried to add another vertical adjustment for this situation, the PopupWindow's position does not change.
Here is the code for the PopupWindow implementation:
/**
* Displays a PopupWindow.
*/
public class MyPopupWindow extends PopupWindow
{
private Fragment m_fragment;
private int m_x;
private int m_y;
/**
* Displays a PopupWindow at a certain offset (x, y) from the center of fragment's view.
*/
public static MyPopupWindow createPopup (Context context, Fragment fragment, int x, int y)
{
// Create the PopupWindow's content view
LayoutInflater inflater = LayoutInflater.from (context);
View popupView = inflater.inflate (R.layout.view_map_popup, null);
MyPopupWindow popupWindow = new MyPopupWindow (popupView, x, y);
popupWindow.m_fragment = fragment;
popupWindow.showAtLocation (fragment.getView (), Gravity.CENTER, x, y);
return popupWindow;
}
private MyPopupWindow (View view, int x, int y)
{
super (view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
m_x = x;
m_y = y;
TextView title = (TextView) view.findViewById (R.id.lblMapPopupTitle);
title.setText ("Exercise Title");
TextView description = (TextView) view.findViewById (R.id.lblMapPopupDescription);
description.setText ("Exercise Description\nSecond Line\nThird Line\nFourth Line\nAnd one more...");
view.setVisibility (View.INVISIBLE);
view.addOnLayoutChangeListener (layoutChangeListener);
}
// If the tapped map location is too close to the edge, determine the
// delta-x and delta-y needed to align it with the PopupWindow
private int getHorizontalAdjustment (int popupWidth)
{
int horizontalAdjustment = 0;
int parentWidth = m_fragment.getView ().getWidth ();
if ((parentWidth / 2) + m_x + (popupWidth / 2) > parentWidth)
{
horizontalAdjustment = parentWidth - ((parentWidth / 2) + m_x + (popupWidth / 2));
}
else if ((parentWidth / 2) + m_x - (popupWidth / 2) < 0)
{
horizontalAdjustment = 0 - ((parentWidth / 2) + m_x - (popupWidth / 2));
}
return horizontalAdjustment;
}
private int getVerticalAdjustment (int popupHeight)
{
int verticalAdjustment = 0;
int parentHeight = m_fragment.getView ().getHeight ();
int y = m_y - (popupHeight / 2);
if ((parentHeight / 2) + y + (popupHeight / 2) > parentHeight)
{
verticalAdjustment = parentHeight - ((parentHeight / 2) + y + (popupHeight / 2));
}
else if ((parentHeight / 2) + y - (popupHeight / 2) < 20)
{
verticalAdjustment = 20 - ((parentHeight / 2) + y - (popupHeight / 2));
}
return verticalAdjustment;
}
private View.OnLayoutChangeListener layoutChangeListener = new View.OnLayoutChangeListener ()
{
#Override
public void onLayoutChange (View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)
{
int height = bottom - top;
int width = right - left;
// Adjust the y-position for the inflated height of the view
// (so the popup's bottom edge lines up with the tapped marker's top edge)
int y = m_y - (height / 2);
int x = m_x;
// Determine any adjustments that need to be made to line up the tapped marker with the popup
int horizontalAdjustment = getHorizontalAdjustment (width);
int verticalAdjustment = getVerticalAdjustment (height);
y -= verticalAdjustment;
// Update our position with the re-calculated position
update (x, y, -1, -1);
view.setVisibility (View.VISIBLE);
}
};
}
The issue can be seen in this screenshot: PopupWindow overlaps ActionBar
Basically, I need to either (1) get the PopupWindow to clip to the Fragment's view and not overlap the ActionBar, or (2) get the PopupWindow to update its position in the event that it does overlap the ActionBar.
Any help is greatly appreciated.
Can we get the visible width of a child inside a horizontalscrollview ?
i tried
int viewLeft = v.getLeft();
int viewRight = v.getRight();
but it's always the same result
I need to know if the visibility is higher than half the width
"Visible width" sounds like you want a child view's width actually visible to the user (whole width minus the currently invisible part(s)).
In that case it's correct to use getLeft() and getRight(), but like this instead (hsv being your HorizontalScrollView instance):
int viewLeft = v.getLeft();
int viewRight = v.getRight();
int scrollX = hsv.getScrollX();
if (scrollX > viewLeft) {
viewLeft = scrollX;
}
if (scrollX < viewRight) {
viewRight = scrollX + hsv.getWidth();
}
int visibleWidth = viewRight - viewLeft;
Im working on my circular list view. I implement almost all functionality, but i have some problem. When my activity starts my listView looks like: https://dl.dropbox.com/u/44444463/Screenshot_2013-02-28-14-37-17.png
But it is not right. It should looks like: https://dl.dropbox.com/u/44444463/Screenshot_2013-02-28-14-37-44.png
Here is my problem.
When activity starts its look like in first screenshot. If i touch any element in listView, or scroll it (if there is a lot of elements) it baceme like in screenshot2. Exactly what i need.
How can i take image like in scrennshot 2 in activity start?
I overrited method #Override public void onScroll(..) in OnScrollListener of my listView, where i aplly all paddings:
// int x = 30;
for (int i = 0; i < visibleItemCount; ++i)
{
View v = listView.getChildAt(i);
if (v != null)
{
LinearLayout rl = (LinearLayout) v.findViewById(R.id.rl);
float sin = (float) (v.getTop() + v.getHeight() / 2 - centerOfCircle) / radius;
double angle = Math.asin(sin);
int x = (int) (-dXRow + radius * Math.cos(angle));
if (x < 0)
x = 0;
Log.d("MN", "setPadding: " + x);
rl.setPadding(x, 0, 0, 0);
}
}
Really sorry for my Engish
You should set starting peddings in the getView() method of your listView's adapter. And then recompute them onScroll() as you need.
OnScroll method isnt invoked when the listView appear, only when you scroll it.
is it possible to find the View that is displayed at a given absolute x/y pixel coord?
Edit: I found a suitable Solution that works great:
private View findViewByCoord(float x, float y){
TextView textView = null;
int[] location = new int[2];
int width = 0;
int height = 0;
for(int reference : cardReference){
textView = (TextView) findViewById(reference);
textView.getLocationOnScreen(location);
width = textView.getWidth();
height = textView.getHeight();
if(location[0] <= x && x <= (location[0] + width) && location[1] <= y && y <= (location[1] + height)){
Log.i("Test", "Card " + textView.getText() + " is pointed");
return textView;
}
}
return null;
}
Where cardReference is an array of integer to Resources (in my case 20 TextViews arranged in a 4 x 5 Matrix):
int[] cardReference = new int[]{R.id.card1_1, R.id.card1_2, R.id.card1_3, R.id.card1_4,
R.id.card2_1, R.id.card2_2, R.id.card2_3, R.id.card2_4,
R.id.card3_1, R.id.card3_2, R.id.card3_3, R.id.card3_4,
R.id.card4_1, R.id.card4_2, R.id.card4_3, R.id.card4_4,
R.id.card5_1, R.id.card5_2, R.id.card5_3, R.id.card5_4};
To speed up performance i would consider to use an array of TextViews then call findViewById() in every Loop.
One 'solution' would be to loop through the parent view's children and check the getLeft() and getTop() coordinates against the X and Y coordinates of your choice. If there is a match, you have your view.
I'd like to hear other alternatives though.
Edit: You'd also have to work out the height/width of the view too in relation to the left and top coordinates given to see if your coordinates are within that range.
Can I get a View's x and y position relative to the root layout of my Activity in Android?
The Android API already provides a method to achieve that.
Try this:
Rect offsetViewBounds = new Rect();
//returns the visible bounds
childView.getDrawingRect(offsetViewBounds);
// calculates the relative coordinates to the parent
parentViewGroup.offsetDescendantRectToMyCoords(childView, offsetViewBounds);
int relativeTop = offsetViewBounds.top;
int relativeLeft = offsetViewBounds.left;
Here is the doc
This is one solution, though since APIs change over time and there may be other ways of doing it, make sure to check the other answers. One claims to be faster, and another claims to be easier.
private int getRelativeLeft(View myView) {
if (myView.getParent() == myView.getRootView())
return myView.getLeft();
else
return myView.getLeft() + getRelativeLeft((View) myView.getParent());
}
private int getRelativeTop(View myView) {
if (myView.getParent() == myView.getRootView())
return myView.getTop();
else
return myView.getTop() + getRelativeTop((View) myView.getParent());
}
Let me know if that works.
It should recursively just add the top and left positions from each parent container.
You could also implement it with a Point if you wanted.
Please use view.getLocationOnScreen(int[] location); (see Javadocs). The answer is in the integer array (x = location[0] and y = location[1]).
View rootLayout = view.getRootView().findViewById(android.R.id.content);
int[] viewLocation = new int[2];
view.getLocationInWindow(viewLocation);
int[] rootLocation = new int[2];
rootLayout.getLocationInWindow(rootLocation);
int relativeLeft = viewLocation[0] - rootLocation[0];
int relativeTop = viewLocation[1] - rootLocation[1];
First I get the root layout then calculate the coordinates difference with the view.
You can also use the getLocationOnScreen() instead of getLocationInWindow().
No need to calculate it manually.
Just use getGlobalVisibleRect like so:
Rect myViewRect = new Rect();
myView.getGlobalVisibleRect(myViewRect);
float x = myViewRect.left;
float y = myViewRect.top;
Also note that for the centre coordinates, rather than something like:
...
float two = (float) 2
float cx = myViewRect.left + myView.getWidth() / two;
float cy = myViewRect.top + myView.getHeight() / two;
You can just do:
float cx = myViewRect.exactCenterX();
float cy = myViewRect.exactCenterY();
You can use `
view.getLocationOnScreen(int[] location)
;` to get location of your view correctly.
But there is a catch if you use it before layout has been inflated you will get wrong position.
Solution to this problem is adding ViewTreeObserver like this :-
Declare globally the array to store x y position of your view
int[] img_coordinates = new int[2];
and then add ViewTreeObserver on your parent layout to get callback for layout inflation and only then fetch position of view otherwise you will get wrong x y coordinates
// set a global layout listener which will be called when the layout pass is completed and the view is drawn
parentViewGroup.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
//Remove the listener before proceeding
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
parentViewGroup.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
parentViewGroup.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
// measure your views here
fab.getLocationOnScreen(img_coordinates);
}
}
);
and then use it like this
xposition = img_coordinates[0];
yposition = img_coordinates[1];
I wrote myself two utility methods that seem to work in most conditions, handling scroll, translation and scaling, but not rotation. I did this after trying to use offsetDescendantRectToMyCoords() in the framework, which had inconsistent accuracy. It worked in some cases but gave wrong results in others.
"point" is a float array with two elements (the x & y coordinates), "ancestor" is a viewgroup somewhere above the "descendant" in the tree hierarchy.
First a method that goes from descendant coordinates to ancestor:
public static void transformToAncestor(float[] point, final View ancestor, final View descendant) {
final float scrollX = descendant.getScrollX();
final float scrollY = descendant.getScrollY();
final float left = descendant.getLeft();
final float top = descendant.getTop();
final float px = descendant.getPivotX();
final float py = descendant.getPivotY();
final float tx = descendant.getTranslationX();
final float ty = descendant.getTranslationY();
final float sx = descendant.getScaleX();
final float sy = descendant.getScaleY();
point[0] = left + px + (point[0] - px) * sx + tx - scrollX;
point[1] = top + py + (point[1] - py) * sy + ty - scrollY;
ViewParent parent = descendant.getParent();
if (descendant != ancestor && parent != ancestor && parent instanceof View) {
transformToAncestor(point, ancestor, (View) parent);
}
}
Next the inverse, from ancestor to descendant:
public static void transformToDescendant(float[] point, final View ancestor, final View descendant) {
ViewParent parent = descendant.getParent();
if (descendant != ancestor && parent != ancestor && parent instanceof View) {
transformToDescendant(point, ancestor, (View) parent);
}
final float scrollX = descendant.getScrollX();
final float scrollY = descendant.getScrollY();
final float left = descendant.getLeft();
final float top = descendant.getTop();
final float px = descendant.getPivotX();
final float py = descendant.getPivotY();
final float tx = descendant.getTranslationX();
final float ty = descendant.getTranslationY();
final float sx = descendant.getScaleX();
final float sy = descendant.getScaleY();
point[0] = px + (point[0] + scrollX - left - tx - px) / sx;
point[1] = py + (point[1] + scrollY - top - ty - py) / sy;
}
Incase someone is still trying to figure this out. This is how you get the center X and Y of the view.
int pos[] = new int[2];
view.getLocationOnScreen(pos);
int centerX = pos[0] + view.getMeasuredWidth() / 2;
int centerY = pos[1] + view.getMeasuredHeight() / 2;
I just found the answer here
It says:
It is possible to retrieve the location of a view by invoking the methods getLeft() and getTop(). The former returns the left, or X, coordinate of the rectangle representing the view. The latter returns the top, or Y, coordinate of the rectangle representing the view. These methods both return the location of the view relative to its parent. For instance, when getLeft() returns 20, that means the view is located 20 pixels to the right of the left edge of its direct parent.
so use:
view.getLeft(); // to get the location of X from left to right
view.getRight()+; // to get the location of Y from right to left
You can use the following the get the difference between parent and the view you interested in:
private int getRelativeTop(View view) {
final View parent = (View) view.getParent();
int[] parentLocation = new int[2];
int[] viewLocation = new int[2];
view.getLocationOnScreen(viewLocation);
parent.getLocationOnScreen(parentLocation);
return viewLocation[1] - parentLocation[1];
}
Dont forget to call it after the view is drawn:
timeIndicator.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
final int relativeTop = getRelativeTop(timeIndicator);
});