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.
Related
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.
EDIT: I just realized that if I want to add buttons or anything below, I want to be able to drag on top of them. So maybe my solution would be changing the Z-Index of the cards so they can appear outside the FrameLayout?
I have a FrameLayout as such:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/stack"
/>
I put a RelativeLayout in it that contains some items, but has the formatting of so:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:font="http://schemas.android.com/apk/res-auto"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#drawable/card_background"
android:layout_centerInParent="true"
>
The only reason I have my FrameLayout set to match_parent is because the user can drag the relative layout anywhere on the screen, and if the width and height of the FrameLayout are wrap_content, the RelativeLayout gets cropped when dragged. However, when leaving it as match_parent touching anywhere in the FrameLayout trigger's the LinearLayout's touch event.
For context, I'm trying to display a stack of cards, each the same size, and let the user drag the top one off the screen one by one.
I ended up adding directly to the RelativeLayout. Unfortunately, when creating a class by hand some of the gravity settings were lost, so I did something like this:
int size = ScreenMetricUtil.convertPixelToDp(getActivity().getBaseContext(), 300);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(size, size);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
card.setLayoutParams(params);
container.addView(card); // add to LinearLayout
And my helper method (source):
public static int convertPixelToDp(Context context, int pixels) {
float density = context.getResources().getDisplayMetrics().density;
return (int) (pixels * density + 0.5f);
}
One of my DialogFragment's layouts uses a HorizontalScrollView to horizontally scroll its child LinearLayout. Is there a way to lock its width into place so that it doesn't expand the width of the DialogFragment once the LinearLayout has been populated with items?
Here's what the DialogFragment looks like when it first loads:
And here's what happens when the LinearLayout under the Kanji header is populated with items:
Everything scrolls fine once the items are populated, but I want to prevent the DialogFragment from expanding. Setting a fixed width on the root of the HorizontalScrollView prevented it from expanding, but I can't think of a good way to do so while taking into account the highly variable size of Android screens.
Here's some code for the area under Kanji:
<HorizontalScrollView
android:layout_height="48dp"
android:layout_width="match_parent">
<LinearLayout
android:id="#+id/grid_kanji"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />
</HorizontalScrollView>
I got the desired effect by setting the width of the HorizontalScrollView to whatever it was when the dialog first opened:
// Fix the size of the HorizontalScrollView so it doesn't expand
HorizontalScrollView hsv = (HorizontalScrollView) getDialog().findViewById(R.id.grid_kanji_root);
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, (float) hsv.getHeight(), getResources().getDisplayMetrics());
int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, (float) hsv.getWidth(), getResources().getDisplayMetrics());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(width, height);
hsv.setLayoutParams(lp);
getWidth() and getHeight() return measurements in pixels, which is why my TypedValue's use COMPLEX_UNIT_PX instead of COMPLEX_UNIT_DP.
Note that you can't do this in onCreateDialog() or onResume() since the width returned will be 0 (zero). I ended up running this code in the function that loads cursor results into the child LinearLayout with a global boolean that keeps track of whether or not I've already set the width - if it's FALSE (as when the dialog first loads), then I set the width and set the boolean to TRUE.
I also discovered that the LayoutParams you apply to a control must match the type of the parent element. Since my HorizontalScrollView is located inside of a LinearLayout, I had to apply LinearLayout.LayoutParams to it instead of HorizontalScrollView.LayoutParams. You'll get a ClassCast Exception otherwise.
I have 5 LinearLayouts. For ease of writing and understanding, I'll refer to them as Views A - E.
View A is the parent of Views B, C, D, and E.
I have set the layout_weight to 8.0f for View A and the weights to 1.0f, 3.0f, 1.0f, 3.0f for Views B - E respectively.
I understand that this is for managing the empty space in the parent view, but I was hoping to size my View and subviews to own a percentage of the screen, rather than compete just for the free space.
However, this is all done programmatically, so should I set the height of the LinearLayouts to a coefficient of the getHeight() method/accessor of it's parent (View A)? If so, then how can I get the height of the parent if the view hasn't yet been added to it's parent, which would set its height with MATCH_PARENT?
Since the getHeight() method will return 0 for onCreate, onStart, and the first onResume, I had to keep looking for an answer. I found ViewTreeObserver and OnGlobalLayoutListener, which will let me know when the Layouts have been set. However, I would really appreciate having the height before any drawing occurs. Is that not possible?
If you don't need to do this programatically, you can do it in xml. In order to have the child LinearLayouts take up a percentage of the Parent LinearLayout (LinearLayout A) then you need to set the parent's weightSum=(Total layout_weight of child LinearLayouts) and then set the child LinearLayouts width/height property to "0dip" and set their layout_weight to the desired percentage.
Sample code for a vertical orientation would be:
<LinearLayout
android:id="#+id/A"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:weightSum="8.0"
android:orientation="vertical">
<LinearLayout
android:id="#+id/B"
android:layout_height="0dip"
android:layout_width="fill_parent"
android:layout_weight="1.0"/>
<LinearLayout
android:id="#+id/C"
android:layout_height="0dip"
android:layout_width="fill_parent"
android:layout_weight="3.0"
</LinearLayout>
Unfortunately, it's not possible to get the size of a view before it's drawn (not unless there is a hack out there that I'm unaware of). The view doesn't hold any information about its size until it until it is ready to be laid out. It would be nice if, in a future release, they designed the system hold the view dimensions prior to the view being drawn.
You could create a custom class, extend the View class, and then override a method that is called by the system and return the dimensions to an object reference in your activity. However, this is likely to be more of a headache then a help and doesn't really give you any real advantage.
I'd recommend using ViewTreeObserver and the OnGlobalLayoutListener.
Here's how to do this with Fragments as mentioned above.
First save a reference to context in the onAttach method (you will probably want to setup a callback to the activity anyway:
Context context;
#Override
public void onAttach(Activity activity){
super.onAttach(activity);
context = activity;
}
Then in onViewCreate, you take the measurements and use them:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Display display = ((WindowManager)
context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
float pixHeight = metrics.heightPixels;
float pixWidth = metrics.widthPixels;
float density = context.getResources().getDisplayMetrics().density;
float dpHeight = pixHeight / density;
float dpWidth = pixWidth / density;
// make various layouts using your measurements, send the
// measure of each parent to each custom drawn child, or
// send the bounds of each child as determined by the size of each parent
return someView;
}
I have a bunch of images with different sizes. Each of them should be presented on the top part of the screen and must take the space in height equal to 60% of screen height. Width of the image will be dependent on it's height to save initial proportions. I tried to use weightsum property in layout and weight property in ImageView, but I don't know what to put in the height property of my image view. If it is "wrap_content", every image resizes my ImageView and all mark-up crushes.
Any advices?
If you want, you can set the image's dimensions by code.
Just set the width with the weight_sum method and then do something like:
WindowManager manager = (WindowManager) context.getSystemService(Activity.WINDOW_SERVICE);
int screenHeight = manager.getDefaultDisplay().getHeight();
YOUR_VIEW.getLayoutParams().height = (int) (screenWidth * 0.6);
Please mind that you can do so only AFTER your ImageView has been drawn on screen - so calling it within the onCreate() method will not work.
You can either call it delayed (postDelayed) or set a layout listener to one of your view.
Hope this helps.