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.
Related
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.
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);
}
I have a pair of vertically-arranged GridViews that are populated with buttons by an Adapter. I can get the GridViews to fill the width of the parent view, but I can't get the second GridView to fill the height of the parent view.
It appears that the height of the GridViews is fixed and can't be changed. If there's more than enough space for the GridViews, they don't fill the parent. If there's not enough space, they go into scroll mode -- and scroll mode can't be turned off.
I'm not sure, but GridView seems to be using a fixed height for the child button views, I can't figure out how the button (row) height is determined, or how I can change it or force GridView to fill the parent height.
Here's the XML for the GridViews:
<LinearLayout
android:id="#+id/keypads"
android:background="#color/keypad_background_color"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:isScrollContainer="false"
android:orientation="vertical" >
<GridView
android:id="#+id/grdMeasButtons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:numColumns="4"
android:scrollbars="none"
android:isScrollContainer="false"
android:stretchMode="columnWidth" />
<GridView
android:id="#+id/grdNumButtons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="3"
android:isScrollContainer="false"
android:scrollbars="none"
android:stretchMode="columnWidth" />
</LinearLayout>
Note that my original version sets a height of 0px for each GridView and weight ratio of 1:2 to set the height relationship of the GridViews. That doesn't fill the parent either, and if the weight of one of the views is too small, it goes into scroll mode. The "isScrollContainer" attribute doesn't turn that off.
Here's the getView() method from the Adapter:
// create a new ButtonView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
Button btn;
if ((convertView == null) || !initializing) { // if it's not recycled, initialize some
// attributes
btn = new Button(mContext);
KeypadButton keypadButton = mButtons[position];
switch(keypadButton.mCategory)
{
case NUMBER:
btn.setBackgroundResource(R.drawable.keypadnumber);
break;
case DECIMAL:
btn.setBackgroundResource(R.drawable.keypadnumber);
break;
case CLEAR:
btn.setBackgroundResource(R.drawable.keypadnumber);
break;
case WEIGHT:
case VOLUME:
switch (unitState[position]) {
case SELECTED:
btn.setEnabled(true);
btn.setBackgroundResource(R.drawable.keypad_measure_selected);
btn.setTextColor(mContext.getResources().getColor(R.color.units_text_enabled));
break;
case ENABLED:
btn.setEnabled(true);
btn.setBackgroundResource(R.drawable.keypad_measure_not_selected);
btn.setTextColor(mContext.getResources().getColor(R.color.units_text_enabled));
break;
case DISABLED:
btn.setEnabled(false);
btn.setBackgroundResource(R.drawable.keypad_measure_not_selected);
btn.setTextColor(mContext.getResources().getColor(R.color.units_text_disabled));
break;
default:
break;
}
break;
case DUMMY:
break;
default:
btn.setBackgroundResource(R.drawable.keypadnumber);
break;
}
// Set OnClickListener of the button to mOnButtonClick
if(keypadButton != KeypadButton.DUMMY)
btn.setOnClickListener(mOnButtonClick);
else
btn.setClickable(false);
// Set CalculatorButton enumeration as tag of the button so that we
// will use this information from our main view to identify what to do
btn.setTag(keypadButton);
} else {
btn = (Button) convertView;
}
btn.setText(mButtons[position].getText());
return btn;
}
(Don't worry about the the unitState array. It solves yet another problem.)
Even when the view is being recycled, the height (and width) of the button is zero, and setting it has no effect.
Can the height or fill attributes of the GridViews or buttons be set in Layout parameters before the GridViews are populated?
// Just replace your second GridView With This One
<GridView
android:id="#+id/grdNumButtons"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:isScrollContainer="false"
android:numColumns="3"
android:scrollbars="none"
android:stretchMode="columnWidth" />
If GridView not filled to parent, then you have to calculate, device Height programatically in getView() method. After calculating height of the device, set GridView's layoutParams to match parent or fill parent, it will solve your problem.
The solution proposed by pratik was on the right track, but setting the size of the GridViews in the adapter is no different from using "fill_parent" or weighted heights on the GridViews in XML. Sure, the GridViews are set to the correct height, but the buttons are always the same size. So if a GridView isn't big enough for its buttons, the GridView scrolls. Useless for a keypad.
As I suspected, the problem is the button size. I still don't know how Android determines the button height, but it appears to fixed -- i.e., it's not dependent on the button width, which is properly adjusted to fit the GridView width. I'm guessing this is a bug in GridView. It also seems to be a bug that you can't turn off scrolling in GridView.
Anyhow, the solution is for getView() in each GridView's adapter to set the button heights to the value that makes them all fit exactly in the height allocated to the GridView. I actually tried that before posting here, but used the construct:
btn.setHeight(number);
instead of setting the height in the button's layout parameters. That was due to my not realizing that the button height doesn't get set until after the layout has completed. Also, I hadn't considered getting the usable screen height so I could compute a device-independent height. Sorry for being new to this stuff.
I spent a lot of time trying to use the height of the LinearLayout that contains the GridViews, which would reduce the number of constants I need to adjust for different device sizes. But that got very messy due to timing of layout completion and the oddities of how tabbed fragments are implemented (and yes, I tried setting up an OnGlobalLayoutListener). Bottom line, there are some timing issues with that approach. The upshot was that the keypad area would disappear for a second or two and get redrawn when the fragment was tabbed into view after being destroyed and re-created. It can probably be done, but I didn't have time to figure it out.
Anyhow, here are the relevant additions to the code. First I added the following method that I found here to the MainActivity:
// Method to get the usable screen height and width, which will then be available through their getters and setters
private void setUsableScreenDimensions() {
DisplayMetrics dm = getResources().getDisplayMetrics();
float screen_w = dm.widthPixels;
float screen_h = dm.heightPixels;
int resId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
screen_h -= getResources().getDimensionPixelSize(resId);
}
TypedValue typedValue = new TypedValue();
if(getTheme().resolveAttribute(android.R.attr.actionBarSize, typedValue, true)){
screen_h -= getResources().getDimensionPixelSize(typedValue.resourceId);
}
screenWidth = (double) screen_w;
screenHeight = (double) screen_h;
}
Then I added this code to the adapter constructor:
double keypadParentHeight = (MainActivity.getScreenHeight() * Constants.KEYPAD_PARENT_HEIGHT_PERCENTAGE);
double rowHeight = keypadParentHeight / Constants.NUMBER_OF_KEYPAD_ROWS;
buttonHeight = (int) (rowHeight * Constants.BUTTON_HEIGHT_FACTOR);
Finally, the button height in getView is set thusly:
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, buttonHeight);
LayoutParams params = params;
The actual code is slightly more complicated because there are two sets of keys (two GridViews) with different weights (1:2), so the button sizes are different. In addition, the same keypads are drawn by the same adapter on three different fragments with different size parent LinearLayouts. But that all boiled down to a few extra constants and some conditional statements in the adapter.
I have a tableview inside of a scrollview. I want to reserve space for a listview 2/3 of the screen. I am getting the height of the screen, and will divide that in 2/3 and set the height of the ScrollView. But even with manually x and y numbers its blowing up.
App is crashing when I set the ScrollView width and height like this:
ScrollView sv = (ScrollView)findViewById(R.id.usdScroll);
ScrollView.LayoutParams layoutParams = new ScrollView.LayoutParams( -1, 550);
sv.setLayoutParams(layoutParams);
Any ideas what I did wrong?
You can use the layout_weight attribute to specify how much of a view the specific component will use, e.g. android:layout_weight=2. So for your listView set a weight of 1 and a weight of 2 for your tableview, I think. Anyway, play with the weight setting and you can split the view in any ratio you like.
LayoutParams is used to tell their parents how they want to be laid out. So the layout param's type which you set to must match its parent class type exactly.
and here is a better solution, using layout_weight instead. Try this please
<ScrollView android:layout_weight="1" ........></ScrollView>
<ListView android:layout_weight="2" ........></ListView>
Is it possible to create a view that is bigger than the screen?
I need a view that has a bigger width then the screen of the device. I use this view in a rotation animation. During the rotation the parts that were not on the screen before animating the view will become visible.
Is there a way to achieve this effect with the android framework?
Update
I tried to set my parent layout much bigger then the screen and it is working. This will make somethings a little bit uncomfortable but it could work. The next problem now is that my layout still starts at the left side of the screen. I can't think of a method to make the layout to expand itself to the left and the right of the screen.
Ok I got an answer. It is not very nice because it uses a deprecated View class but it works at least on my current testing screen resolution other resolutions are tested tomorrow.
I wrapped the view that I wanted to expand beyond the screen in an absolute layout like this:
<AbsoluteLayout
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true">
<ImageView
android:id="#+id/content"
android:layout_width="600dip"
android:layout_height="420dip"
android:scaleType="centerCrop"
android:layout_x="-200dip"
android:layout_y="60dip"
android:src="#color/testcolor" />
</AbsoluteLayout>
The -200 x coordinate makes the view stick 200dip out of the left side of the screen. If I'm animating the view those parts that are outside the screen will gradually become visible.
E.g. setting negative bottom margin together with setting extra large layout_height (large enough for you) solved the similar issue as for me.
Works fine at least using API 11+ animations/rotations.
Could look like:
android:layout_marginBottom="-1000dp"
android:layout_height="1000dp"
In case anyone still comes up on this page. The key is your root layout, it will only work with a FrameLayout (or the deprecated absolutelayout). Then you have two options to make your child view bigger.
through xml, this is quick and easy but you don't know the actual screen width & height in advance so your off with setting a ridiculously high value for layout_width & layout_height to cover all screens.
Calculate the screen size programatically and make the view's width/height proportional bigger to this..
Also be aware that your bigger view still starts in the top left corner of the screen so to account this you will have to give a negative top & left margin that's half of what you are adding to the view's width/height
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) viewToMakeBigger.getLayoutParams();
int marginLeft = (int) (viewToMakeBigger.getWidth()*0.1);
int marginTop = (int) (viewToMakeBigger.getHeight()*0.1);
params.width = (int) (viewToMakeBigger.getWidth()*1.2);
params.height = (int) (viewToMakeBigger.getHeight()*1.2);
params.leftMargin = -marginLeft;
params.topMargin = -marginTop;
viewToMakeBigger.setLayoutParams(params);
HorizontalScrollView:
http://developer.android.com/reference/android/widget/HorizontalScrollView.html
Layout container for a view hierarchy that can be scrolled by the user, allowing it to be larger than the physical display.
The simple axml below creates an ImageView that is 400dp wider than the screen (even though the layout_width is set to equal the parent's width) using a negative left and right margin of 200dp.
The ImageView is situated 250dp above the top of the screen using a negative top margin, with 450dp of 700dp vertical pixels visible on the screen.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:background="#FFFF0000"
android:layout_height="700dp"
android:layout_marginLeft="-200dp"
android:layout_marginRight="-200dp"
android:layout_marginTop="-250dp"
android:layout_width="match_parent" />
</RelativeLayout>
You can override the views in the onMeasure method. This will set your View dimensions to 1000x1000 px.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(1000, 1000);
}
Is it possible to create a view that is bigger than the screen?
Why not, you can define the layout_width and layout_height in px(or dip) as you want:
android:layout_width="10000px"
android:layout_height="20000px"
You need to change the size of the window, by getWindow().setLayout. This will increase the size for your window. Since the root layout can be as big as its parent you can then increase the size of the view you want to be bigger than the screen size. It works for me let me know
You can use ViewSwitcher to handle that. Used with Animation and a OnGestureListener looks pretty good.
You can do it programmatically:
FrameLayout.LayoutParams rootViewParams = (FrameLayout.LayoutParams) rootView.getLayoutParams();
rootViewParams.height=displayMetrics.heightPixels+(int)dpToPixels(60);
rootViewParams.width=displayMetrics.widthPixels+(int)dpToPixels(60);
rootView.setLayoutParams(rootViewParams);
rootView.setX(rootView.getX() - dpToPixels(30));
rootView.setY(rootView.getY() - dpToPixels(30));
MUST BE ONLY IN
"public void onWindowFocusChanged(boolean hasFocus)" method.
and
rootView = (RelativeLayout) findViewById(R.id.rootLayout);
Inside "protected void onCreate(Bundle savedInstanceState)" method.
Where yout .xml file is like this:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/rootLayout"
tools:context="com.example.Activity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_centerHorizontal="true"
android:layout_margin="30dp"
android:layout_centerVertical="true"
android:layout_height="match_parent">
// Bla bla bla
</RelativeLayout>
and:
public float dpToPixels(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
}