I am writing a drag and drop application and got really confused because of some parameters.
Please help to figure out.
First of all, I read the documentation for the View class and got the following explanations.
getX() : The visual x position of this view, in pixels.
getY() : The visual y position of this view, in pixels.
getWidth() : Return the width of the your view.
getHeight() : Return the width of the your view.
getTop() : Top position of this view relative to its parent.
getLeft() : Left position of this view relative to its parent.
Now when we finished with the official documentation, let's see what do we have.
I have an image with original size 500x500 called circle.
And here's the actual screenshot of my application
Here is the xml for the layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:id="#+id/imageView1"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="#drawable/circle" />
</LinearLayout>
Now what am I concerned about. When I watch my locals, I get the following, which really confuses me.
I don't see any problem with the getX() and getY() functions, because they actually show me where does the image begin.
As the documentation states, the getWidth() and getHeight methods return the width and height of the view but the watch window tells me that my getWidth() and getHeight are 300, which I really can't understand, because in my XML I've set them 100dp each, so do the functions return me them in a different measurement, and how do I convert it to dp.
And finally, it tells me that getTop() and getLeft are 700 and 300, and as the documentation says, they are the position of the image relative to it's parent. But isn't my parent the Linear Layout, so what do this numbers mean in sense of screen positioning?
This is a supplemental answer for future visitors.
Left, Top: When a parent view lays out a subview, left is the distance from the left side of the parent to the left side of the subview. Likewise, top is the distance from the top of the parent to the top of the subview. Thus, getLeft() and getTop() return the coordinates of the top left corner of the view relative to its parent view (not the absolute coordinates on the screen).
X, Y: Usually getX() and getY() will return the same thing as getLeft() and getTop(). However, sometimes it is useful to move the view a little after it has already been laid out. This can be done with setTranslationX() and setTranslationY(). If these have been set then x and y will be different from left and top, where
x = left + translationX
y = top + translationY
Width, Height: You can find the width and the height of the view with getWidth() and getHeight(). This is not affected by a translation.
The above values are all in pixel dimensions.
All these measurement methods return sizes in pixels( px ), not density-pixels ( dp ). If you want to convert it you can get the density by calling:
float density = getResources().getDisplayMetrics().density;
And then divide the values you get with the provided density, for example:
int widthDp = (int)(img.getWidth() / density);
You can get pixels from dp with
float ht_px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ht, getResources().getDisplayMetrics());
float wt_px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, wt, getResources().getDisplayMetrics());
As of the positioning question.
getTop and getLeft are relative values and are based on your parent. Since the only parent of your ImageView is LinearLayout you are effectively positioning your ImageView directly below the ActionBar/ToolBar
Also don't use an image for a circle, you can draw it easily with canvas.drawCircle it takes much less memory.
Related
Android Studio 2.0 beta 6
I am trying to use ViewPropertyAnimator to move a ImageView (ivSettings) inside a toolbar so that it is 20dp from the right and 20dp from the top, from is current location. And move the ImageView (ivSearch) 20dp from the left and top.
The imageViews are contained in a Toolbar.
This is the initial state and I want to move the icons into the upper corners inside the toolbar.
The code I am using is this to get the width and then subtract a value to get the ivSettings to be 20dp from the right.
final DisplayMetrics displayMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
final float widthPx = displayMetrics.widthPixels;
ivSearch.animate()
.setInterpolator(new AccelerateInterpolator())
.x(20)
.y(20)
.setDuration(250)
.start();
ivSettings.animate()
.setInterpolator(new AccelerateInterpolator())
.x(widthPx - 160)
.y(20)
.setDuration(250)
.start();
However, having tried this on different screen size I can't get the exact width calculation. Is there a better way of doing this?
Many thanks for any suggestions
You should be able to use the translationX and translationY properties to achieve the effect you want. These properties act as offsets from the original X and Y coordinates of the View.
Essentially translationX will displace the a View towards the right for a positive value and left for a negative value from it's X coordinate. Similarly translationY displaces the View towards the bottom for positive and top for negative values from it's Y coordinate.
Coming to your question, I am assuming that the final position you want to reach is the top left corner for the search icon, and the top right corner for the settings icon with zero padding for each view.
For the search icon I suggest you start by placing it in the top left corner of the toolbar. Then set both the translationX and translationY to 20p. That should place the search icon in the same place as your image. To move your search icon to the top left all you have to do is animate translationX & Y from 20dp to 0 dp.
Repeat the same for the settings icon, but set translationX to -20dp and translationY to 20dp. This way it'll be placed at the same position as your image. Animate both values to 0 to achieve your desired animation.
Here's the animation code for reference.
ivSearch.animate()
.setInterpolator(new AccelerateInterpolator())
.translationX(0) // Takes value in pixels. From 20dp to 0dp
.translationY(0) // Takes value in pixels. From 20dp to 0dp
.setDuration(250)
.start();
ivSettings.animate()
.setInterpolator(new AccelerateInterpolator())
.translationX(0) // Takes value in pixels. From -20dp to 0dp
.translationY(0) // Takes value in pixels. From 20dp to 0dp
.setDuration(250)
.start();
// To get pixels from dp
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
The great thing about this approach is that you no longer need to know the dimensions of the parent. All you care about is the offsets that you have specified via translationX and translationY.
I believe the problem is that the Animator methods are expecting the values to be in raw pixel values. It looks like the code would only work as you intend on a medium density screen. E.g. A hdpi screen would require 30px instead of 20px to be located at the same location. On higher density screens the code as it exists would push the icons closer to the edges...
That means we need to translate the expected dp value into raw pixel values and use those for animation. There is a handy method for converting dp to px so that it's correct on every device. I'm assuming that you want the left side of the gear to be 160dp from the right(Note I'm specifically saying the left side of the gear because the 160dp value should also include the width of the gear image as the x property is from the left of the image). So 160dp = gear image width + desired right margin distance.
Say you have a resource called ic_gear:
Drawable drawable = getResources().getDrawable(R.drawable.ic_gear);
float bitmapWidth = getIntrinsicWidth();
float rawPX20DPValue = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
float rawPXLeftOffset = rawPX20DPValue + bitmapWidth;
ivSearch.animate()
.setInterpolator(new AccelerateInterpolator())
.x(rawPX20DPValue)
.y(rawPX20DPValue)
.setDuration(250)
.start();
ivSettings.animate()
.setInterpolator(new AccelerateInterpolator())
.x(widthPx - rawPXLeftOffset)
.y(rawPX20DPValue)
.setDuration(250)
.start();
Also check on the offsets(padding) of the gear and search icon before the move as that will likely affect the end result as well.
I am adding a view using the WindowManager.
In the WindowManager.LayoutParameters I am setting both width and height to WRAP_CONTENT as the content should dictate how large the view should be.
However, I am allowing the user to adjust the overall size of the layout.
Then, when I create the layout, I simply apply the saved scale value and viola it shows the newly resized view.
The problem is that despite scaling the actual view I add as shown below:
myView.setScaleX(scaleFactor);
myView.setScaleY(scaleFactor);
mWindowManager.addView(myView, params);
There still seems to be some sort of "container" around my shrunk view.
So I figured instead of using setScaleX() and setScaleY(), to instead add a runnable to my view to run after its done drawing and calling getWidth() and getHeight() then using my scaleFactor value to compute the new height and width. This works, but now things are getting cut off because setScaleX() and setScaleY() actually shrinks everything inside the view. The entire Paint object is shrunk including text, space between text and everything.
Does anyone know how I can make the absolute dimensions the same size as the shrunk view?
EDIT: So after messing around with this. What I believe I need to do is figure out how to resize the parent layout and allowing its children to be clipped instead of resized.
For instance:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="#+id/parent_layout"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</RelativeLayout>
In the layout above, I scale the TextView object using setScaleX() and setScaleY() this causes the textView to be scaled but doesn't change its actual dimensions.
What I need to do then, is get the dimensions of the RelativeLayout and multiply it by the float scale value. This will get the new dimensions. Then I need to update those dimensions WITHOUT changing the dimensions of the TextView oject.
So I figured this out and posting answer and code.
When using setScaleX() and setScaleY() it would scale it according to the view's center. This would cause the view's top and left positions to shrink or grow without changing the actual dimensions of the view.
So to compensate, we must take the child view (textView1) and move it to the left and up how ever much we shrunk it by.
Here is the code to do this:
RelativeLayout parent; //THis is the parent view that you added with a WindowManager object.
TextView textView1; //This is the child view of our parent. For this example, I am using a TextView.
float factor; //This is the amount we scaled our view by.
parent.post(new Runnable() { //When you post a runnable to a view, it runs after the view is drawn and measured.
#Override
public void run() {
int newWidth = Math.round(parent.getWidth() * factor), newHeight = Math.round(parent.getHeight() * factor); //Calculate what the new height and width will be for the parent view to get resized to.
WindowManager.LayoutParams pp = (WindowManager.LayoutParams)parent.getLayoutParams(); //Get an instance of the parent LayoutParams so we can set the new height and width measurements.
RelativeLayout.LayoutParams ch = (RelativeLayout.LayoutParams)chatHead.getLayoutParams(); //Get the child's layout parameters so we can adjust the margins as needed.
int diffX = parent.getWidth() - newWidth, diffY = parent.getHeight() - newHeight; //Calculate the difference in sizes from the newly scaled size to the old size.
diffX = -Math.round(diffX / 2); //Calculate the amount of space needed to move the child view inside its parent. The negative sign is needed here depending how you calculate the differences.
diffY = -Math.round(diffY / 2); //Same as above, but for the height.
ch.setMargins(diffX, diffY, Integer.MIN_VALUE, Integer.MIN_VALUE); //Set the new margins for the child view. This will move it around so it is anchored at the top left of the parents view.
rootChatHead.updateViewLayout(chatHead, ch); //Apply the new parameters.
pp.width = newWidth; //Set the parent's new width.
pp.height = newHeight; //Set the parent's new height.
windowManager.updateViewLayout(rootChatHead, pp); //Update the parent view.
}
Now our view scaled down and the container's size has been adjusted to take up any extra space caused by the scaling of our child view.
This method ALSO works if you scale the child view higher. It will readjust the parent's size to accommodate the new size of the child.
My relative layout is named "highlight".
public static void selectText(float left, float right, float top, float bottom) {
highlight.getLayoutParams().width =(int) (right-left);
highlight.getLayoutParams().height=(int) (bottom - top);
highlight.setX(left);
highlight.setY(top);
}
This works great for highlighting text as far as setting the top left corner of the highlight box. But, the box expands all the way to the bottom right corner of the screen, no matter how small I make the .width and .height values.
You set your width and height as wrap_content. Your layout will have the size of it's content.
Instead of:
highlight.getLayoutParams().width =(int) (right-left);
highlight.getLayoutParams().height=(int) (bottom - top);
Try:
highlight.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Check this link:
http://developer.android.com/reference/android/view/ViewGroup.LayoutParams.html
I don't know why, but the simple 'get layout params' wasn't working. I needed to make a new layout params entirely.
int width = (int) (right-left);
int height = (int) (bottom - top);
RelativeLayout.LayoutParams rlMainlayoutParams = new RelativeLayout.LayoutParams((int) (right-left), (int) (bottom - top));
highlight.setLayoutParams(rlMainlayoutParams);
highlight.setX(left);
highlight.setY(top);
The above answer doesn't answer my question, perhaps because I was not clear that this relative layout doesn't hold any text, it only highlights certain text already present on the screen (in a PDF document, where I cannot necessarily just extract the text to a PDF document).
After you've modified a view's layout params, call requestLayout() on the view for the changes to take effect.
(Calling setLayoutParams() also implicity calls requestLayout().)
I've been searching for an alternative for the android.widget.ImageView.setTop# method in android which would work for api's under 11 or Honeycomb. Is such an alternative even available?
For anything below Honeycomb (API Level 11) you'll have to use setLayoutParams(...).
you can dynamically set the position of view in Android. for example if you have an ImageView in LinearLayout of your xml file.So you can set its position through LayoutParams.But make sure to take LayoutParams according to the layout taken in your xml file.There are different LayoutParams according to the layout taken.
Here is the code to set:
FrameLayout.LayoutParams layoutParams=new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(int left, int top, int right, int bottom);
imageView.setLayoutParams(layoutParams);
From android documentations: doc
Sets the top position of this view relative to its parent. This method is meant to be called by the layout system and should not generally be called otherwise, because the property may be changed at any time by the layout.
This method shouldn't be called directly, mainly because different ViewGroups have different approach to layout which makes this method not consistent (framelayout).
as #nitesh goel suggested, use Margin or padding to set the spacing between a child view and its original position in the ViewGroup. doc
Size, padding and margins
The size of a view is expressed with a width and a height. A view actually possess two >pairs of width and height values.
The first pair is known as measured width and measured height. These dimensions define how big a view wants to be within its parent (see Layout for more details.) The measured dimensions can be obtained by calling getMeasuredWidth() and getMeasuredHeight().
The second pair is simply known as width and height, or sometimes drawing width and drawing height. These dimensions define the actual size of the view on screen, at drawing time and after layout. These values may, but do not have to, be different from the measured width and height. The width and height can be obtained by calling getWidth() and getHeight().
To measure its dimensions, a view takes into account its padding. The padding is expressed in pixels for the left, top, right and bottom parts of the view. Padding can be used to offset the content of the view by a specific amount of pixels. For instance, a left padding of 2 will push the view's content by 2 pixels to the right of the left edge. Padding can be set using the setPadding(int, int, int, int) or setPaddingRelative(int, int, int, int) method and queried by calling getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom(), getPaddingStart(), getPaddingEnd().
Even though a view can define a padding, it does not provide any support for margins. However, view groups provide such a support. Refer to ViewGroup and ViewGroup.MarginLayoutParams for further information.
According to the docs for the View class:
The geometry of a view is that of a rectangle. A view has a location, expressed as a pair of left and top coordinates, and two dimensions, expressed as a width and a height. The unit for location and dimensions is the pixel.
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.
In addition, several convenience methods are offered to avoid unnecessary computations, namely getRight() and getBottom(). These methods return the coordinates of the right and bottom edges of the rectangle representing the view. For instance, calling getRight() is similar to the following computation: getLeft() + getWidth().
My interpretation of the above is that the View's position is controlled by its "Left" and "Top" values, while its width and height are controlled by its "Width" and "Height" values. This seems especially clear considering that last sentence, where "Right" is derived by adding Left and Width.
Despite this, when I use setLeft() and/or setTop() to change the position of the View, the SIZE of the View changes on screen! Meanwhile, the lower right corner of the View stays anchored to its original spot. This behavior implies that "Right" and "Bottom" are actual values, not derived as described in the docs.
So what is really going on here? The docs say one thing, but the behavior says the opposite. What is the proper way to reposition a View?
EDIT: I added a RelativeLayout:
myParams = new RelativeLayout.LayoutParams(300,300);
myParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
myParams.addRule(RelativeLayout.CENTER_VERTICAL);
myView.setLayoutParams(myParams);
...to create a View 300x300 centered on the screen. Works perfectly. But examining that RelativeLayout, the location seems to be controlled by leftMargin and topMargin - yet both are zero! That raises the questions of 1) how can you examine the LayoutParams to know where the View is right now, and 2) how can you alter the LayoutParams to move it to a different location?
EDIT: As an experiment, I added an onTouch method to the View and did this within it (excerpt):
if (MotionEvent.ACTION_MOVE == iAction) {
myParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
myParams.leftMargin = 0;
myParams.topMargin = 0;
v.setLayoutParams(myParams);
}
...on the theory that my vertically and horizontally centered View would then move to the upper left corner of the screen. Result: It didn't move at all. Not exactly surprising, since .leftMargin and .topMargin were already zero, but I wanted to try it just in case there was some magic hiding here.
Other suggestions?