Programmatically setting height of LinearLayout inside ScrollView not working - android

I have a ScrollView whose unique child is a LinearLayout:
<ScrollView
android:id="#+id/d_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:id="#+id/d_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/transparency"
android:orientation="vertical"/>
</ScrollView>
I later dynamically add children to the LinearLayout by using a LayoutInflator (let's say 5 children in this example), and they all have a weight of 1 (so equal heights). I want to only have 4.5 children visible at a time. To do this, I have used a post method to do change the layout_height property of the LinearLayout once the height of the ScrollView is known:
if (n > visibleItems) { // here n = 5 and visibleItems = 4.5
dScroll.setFillViewport(false);
dScroll.post(() -> {
int height = dScroll.getHeight();
ViewGroup.LayoutParams lp = dLayout.getLayoutParams();
lp.height = (int) Math.round(n * height / visibleItems);
dLayout.setLayoutParams(lp);
});
}
However, when I do this, the LinearLayout and its children behave as if their height was set to wrap_content (which is not the case). When using the Layout Inspector in Android Studio, I find that the layout_height of the LinearLayout is set to 1071 (the correct computed value), but the getHeight() method returns 578.
I have tried several combinations of requestLayout(), invalidate() and forceLayout() but it didn't change anything.
Keeping the fillViewport property of the ScrollView to true makes the LinearLayout take the height of the ScrollView, so in this case 964px instead of the 1071 that I want.
Edit: the problem really comes from LinearLayout. If I add a RelativeLayout between the ScrollView and LinearLayout like so:
<ScrollView
android:id="#+id/d_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<RelativeLayout
android:id="#+id/d_rel"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/d_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/transparency"
android:orientation="vertical"/>
</RelativeLayout>
</ScrollView>
and apply the new height to the RelativeLayout instead:
if (n > visibleItems) { // here n = 5 and visibleItems = 4.5
dScroll.setFillViewport(false);
dScroll.post(() -> {
int height = dScroll.getHeight();
ViewGroup.LayoutParams lp = dRel.getLayoutParams();
lp.height = (int) Math.round(n * height / visibleItems);
dRel.setLayoutParams(lp);
});
}
then the RelativeLayout takes the correct height (1071px), but the LinearLayout within it (that is in match_parent height) is still only 578px high.

Related

Programmatically create a textview with 0dp and layout weight 1

I am working on an Android project and I have an issue with the layout.
What I have is a JSONArray that I loop round inflating a new TableRow and within the loop round another array within the JSON array to populate it with fields. The fields are populated by inflating an XML file and adding this view to the table row. However, at the moment nothing shows up in the row.
Below is my TableRow XML file:
<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"/>
Below is my TextView XML:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="#color/black"
android:padding="5dp"/>
Below is how I am populating the TableRow and the TextView:
for (int i = 0; i < result.length(); i++) {
final TableRow tr = (TableRow) getLayoutInflater(getArguments()).inflate(R.layout.result_table_row_light_theme, resultTable, false);
if (i == 0) {
tr.setBackgroundColor(getResources().getColor(R.color.appPrimaryColour));
} else if (i % 2 == 0) {
if (settings.getInt(Defines.SharedPreferenceSettings.APPLICATION_THEME,
com.BoardiesITSolutions.Library.R.style.LibAppTheme) == com.BoardiesITSolutions.Library.R.style.LibAppTheme) {
tr.setBackgroundColor(getResources().getColor(R.color.resultRowLightThemeAlternateRow));
}
}
JSONArray array = result.getJSONArray(i);
for (int j = 0; j < array.length(); j++) {
final TextView textView;
textView = (TextView) getLayoutInflater(getArguments()).inflate(R.layout.result_textview, tr, false);
textView.setText(array.getString(j));
if (i == 0) {
textView.setTypeface(null, Typeface.BOLD);
textView.setGravity(Gravity.CENTER_HORIZONTAL);
}
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
tr.addView(textView);
}
});
}
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
resultTable.addView(tr);
}
});
}
If I change my TextView XML file so that the layout_width is wrap_parent instead of 0dp then everything is shown on the screen.
However, when the textview is 0dp and the layout_weight is 1 then nothing is displayed, however I would have expected each text view to be evenly distributed across the width of the screen to fill the space.
What I should probably mention, don't know if it makes a difference, is the TableView is within a HorizontalScrollView. The row should fit in the width of the screen, if the data is smaller than the screen, but if the row won't fit, then the view will be horizontally scrollable.
UPDATE 1
Below is the XML that hosts the TableLayout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="horizontal">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableLayout android:id="#+id/resultTable"
android:stretchColumns="*"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</TableLayout>
</HorizontalScrollView>
</LinearLayout>
</ScrollView>
</RelativeLayout>
What I should probably mention, don't know if it makes a difference, is the TableView is within a HorizontalScrollView.
It is a logical error to tell a child view inside a scrolling container to match_parent or fill the available space in any way. A scrolling container, like ScrollView or HorizontalScrollView measures its one-and-only child view as UNSPECIFIED so that the child can grow beyond the parent bound in that one direction (and, thus, be scrollable).
The child of a scrolling container is not given what the "available space" would be for it to fill, and operations like layout weight have no effect if the parent's dimension is not well-defined.
The row should fit in the width of the screen, if the data is smaller than the screen, but if the row won't fit, then the view will be horizontally scrollable.
The functionality you are looking for is fillViewport (docs link), which tells the scrolling container to force the child to match it's size if the measured width (in the horizontal case) is less than the parent. Use this in place of applying a weight.
Try this :
public void createTextView(Context context, LinearLayout parent) {
final TextView v = new TextView(context);
parent.addView(v); // Mandatory!
v.post(new Runnable() { // In UI Thread. View Must be added to parent before getting layout params!
#Override
public void run() {
// Get params:
LinearLayout.LayoutParams lParams = (LinearLayout.LayoutParams) v.getLayoutParams();
// Set only target params:
lParams.width = 0;
lParams.weight = 1;
v.setLayoutParams(lParams);
}
});
}
Can you try to do your layout in XML (just to debug it, with dummy data)? Does it work as expected?
The docs mention that:
children of a TableRow do not need to specify the layout_width and layout_height attributes in the XML file. TableRow always enforces those values to be respectively MATCH_PARENT and WRAP_CONTENT.
Not sure if that means that it'll also mess stuff up if you try to set these values. As you've found, it's clearly not just ignoring them if they are set.
The other part says that the parent of the TableRow should be a TableLayout - is that the type of resultTable (you mentioned TableView) - otherwise it will behave as a LinearLayout with horizontal orientation.

GridLayout(not GridView) - Spaces between the cells

I am using GridLayout(support) for displaying ImageViews in my application. There are 3 columns and 5 rows. The problem is that the cells in the GridLayout automatically get some space between them. I am not setting any padding or margin for the cells. Please refer to the image below. All cells are added dynamically and here is how I add these cells.
Getting Screen Width and Height:
Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
screenWidth = size.x;
screenHeight = size.y;
rowHeight = (int) (screenHeight * 0.2);
Adding View to GridLayout:
GridLayout.LayoutParams params = new GridLayout.LayoutParams(
getSpec(rowColumn[0]), getSpec(rowColumn[1]));
params.height = rowHeight;
if (rowColumn[1].equalsIgnoreCase("col_full")) {
params.width = (int) (screenWidth);
} else if (rowColumn[1].equalsIgnoreCase("col_two_part")) {
params.width = (int) (screenWidth * 0.6);
} else {
params.width = (int) (screenWidth * 0.3);
}
ImageButton image = (ImageButton) imageHashMap
.get(reOrderedButtonsListCopy.get(i));
image.setLayoutParams(params);
gridLayout.addView(image, params);
XML Layout:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.xx.xxx"
android:id="#+id/scrollView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<android.support.v7.widget.GridLayout
xmlns:app="http://schemas.android.com/apk/res/com.xx.xxx"
android:id="#+id/gridlayout_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="2dp"
app:columnCount="3"
app:rowCount="5" >
</android.support.v7.widget.GridLayout>
</LinearLayout>
</ScrollView>
Current result:
The red lines show the spaces in between the cells. Also, there is some space on the left side of GridLayout. I have only given 2dp as layout_margin. Any reasons why this padding occurs?
[EDIT]
Making the following changes removed the spacings.
gridLayout = (GridLayout) findViewById(R.id.gridlayout_main);
gridLayout.setUseDefaultMargins(false);
gridLayout.setAlignmentMode(GridLayout.ALIGN_BOUNDS);
gridLayout.setRowOrderPreserved(false);
Refer to the image below.
Found the solution.
Making the following changes removed the spacings.
gridLayout = (GridLayout) findViewById(R.id.gridlayout_main);
gridLayout.setUseDefaultMargins(false);
gridLayout.setAlignmentMode(GridLayout.ALIGN_BOUNDS);
gridLayout.setRowOrderPreserved(false);
Refer to the image below.
The only solution that worked for me was to add extra columns in the GridLayout like android:columnCount="7" and then the column that needs more width set to 3 or more. The more space you want to give to that column. It then automatically reserves more space for those columns. The whole GridLayout works as a stretching thing. The columnWeight says how much a column can stretch.

Dynamically setting relative dimension of a linearlayout

I want to resize my LinearLayout (or a view) to a dimension which is relative to the parent or itself. For example, I want the width to be 1/3 of the parent's width. Or, the height should be same as its own width. I don't want to use any constants , so that it works for all devices.
adding code:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<LinearLayout android:id="#+id/ll_board"
android:gravity="center_horizontal"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
...
</LinearLayout>
code:
public class GMActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
LinearLayout board_layout = (LinearLayout)findViewById(R.id.ll_board);
// I wanted to resize board_layout here ..
// getParent().getWidth() returns 0
Log.d("gm", "layout: " + ((LinearLayout) board_layout.getParent()).getWidth());
// ..
}
}
getWidth() is giving 0. Is it too early to call this? If yes, what is the correct place to call this?
Basically my intention is to make the width of the layout a fraction of the screen size width, and, height same as its own width.
Considering layout your LinearLayout and that its parent it's another LinearLayout:
Get the parent's width:
int parentWidth = ((LinearLayout) layout.getParent()).getWidth();
Get the view's width:
int viewWidth = ((LinearLayout) layout).getWidth();
set the
view.setHeight(viewWidth );
view.setWidth(parentWidth / 3);
I found height=width solution (square shaped layout) in LinearLayout in Square Form

Android - Set FrameLayout width dynamically

I've got a FrameLayout which I want to grow as time continues.
I've implemented a Runnable-Interface.
public void run() {
time_value++;
FrameLayout fl_dateTime = (FrameLayout)findViewById(R.id.game_DateTime);
LayoutParams lp_fl_dateTime = fl_dateTime.getLayoutParams();
lp_fl_dateTime.width = time_value;
handler.postDelayed(this, 100);
}
Why doesn't this work? O_O
You need to set the layout params back after modifying them.
View#setLayoutParams(ViewGroup.LayoutParams lp)
I was able to do this by setting the initial size of the frame to 0dp and then just setting the minWidth to whatever I wanted.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:background="#android:color/black"
android:id="#id/layout"
android:minHeight="50dp"
android:measureAllChildren="false">
item.setMinimumWidth(10dp);

How do I calculate the required height of a GridView that is inside of a ScrollView on Android?

I have a GridView inside of a LinearLayout inside of a ScrollView that pages in data from the server. Beneath the GridView is a button to load more data. My GridView will have an ultimate height that is larger than the screen. If I set the height of my GridView to either wrap_content or parent_fill, it sizes itself to the exact available on-screen height and does not scroll at all, cropping out the extra rows. If I explicitly set the layout_height to something large, like 1000dip, scrolling behaves properly, however I cannot predict the final height of my scroll view apriori.
How do I programmatically determine the necessary height of a GridView to get the desired behaviour?
Here is my layout below. As you can see I set the height to 1000dip, but that is bogus, I need that value to get set automatically/programmatically:
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true"
android:layout_weight="1"
>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/grid"
android:layout_width="fill_parent"
android:layout_height="1000dip"
android:columnWidth="70dp"
android:numColumns="auto_fit"
android:verticalSpacing="0dp"
android:horizontalSpacing="0dp"
android:stretchMode="columnWidth"
android:gravity="center"
android:background="#000000"
android:layout_weight="1"
/>
<Button
android:id="#+id/load_more"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Load More Foo"
/>
</LinearLayout>
</ScrollView>
Here is one way to do this, if someone needs it. A bit of a hack but does the trick. You have to set GridView initially big enough for all the views (e.g. 10000dip)
final GridView imageContainer = // your GridView
imageContainer.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
imageContainer.getViewTreeObserver().removeGlobalOnLayoutListener( this );
View lastChild = imageContainer.getChildAt( imageContainer.getChildCount() - 1 );
imageContainer.setLayoutParams( new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT, lastChild.getBottom() ) );
}
});
I know it's an old case, but I had a similar problem where my ScrollView contained multiple LinearLayouts, which in their turn contained a header and a GridView.
Basically I made categorised sections with headers containing images belonging to that category.
The GridView had to have a flexible height.
I found a lot of answers about overriding onMeasure(), but it worked only on some devices, not all. The height would eventually be 1, or 3 or just 0, displaying only a few pixels of the image.
StretchingGridView class
I overrode the drawableStateChanged() method with this code, inspired by #Karitsa's solution:
#Override
public void drawableStateChanged() {
getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
getViewTreeObserver().removeOnGlobalLayoutListener( this );
View lastChild = getChildAt( getChildCount() - 1 );
if (lastChild != null) {
int height = Math.max(lastChild.getBottom(), getColumnWidth());
float child = getAdapter().getCount();
float col = getNumColumns();
int rows = (int) Math.ceil(child / col);
height = rows * getColumnWidth() + (getHorizontalSpacing() * rows-1);
setLayoutParams( new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, height ) );
}
}
});
}
Note: My GridView uses square images, so I base the height on their width. I don't think it works well with flexible grid item heights.
Apparently GridViews inside ScrollViews are not kosher in Android-land. Switching to ListView with custom-made rows. That seems to behave better.

Categories

Resources