I have a little issue on what sequence things are being called when adding stuff to a RelativeLayout. I have a class extending Activity (name it RelActivity) where I want to create a RelativeLayout and put several custom Views (name it cusView) into that RelativeLayout. The topMargin and leftMargin of a custom View is calculated by using the position of another custom View (i.e. the first custom View has to be positioned directly by setting a number to topMargin and leftMargin). Please note that the Rules of RelativeLayout is not sufficient in this case.
So, over to the problem. In my RelActivity I do this:
Create a RelativeLayout (name it relLayout)
Iterate a cursor with cusViews recieved from a database
For the first cusView -> Set position by topMargin and leftMargin using a LayoutParameter
For the other cusViews -> calculate their topMargin and leftMargin by using one of the other cusViews and a LayoutParameter
Set RelActivity's contentView to relLayout
What happens is that all cusViews but the first one are squeezed in the top left corner because both leftMargin and topMargin are always calculated to be zero. This happens because I use the width of the cusViews to calculate the topMargin and leftMargin, and the width of the cusView has not given a value yet.
Is the width first calculated in the cusView's overrided method onSizeChanged()? Is the onSizeChanged() method get called first when the layout is presented on the screen? If so, how do I work around this issue? Do I have to calculate the positionings after onSizeChanged() is done?
Edit: Here is a minimum working example:
Here is my onCreate in RelActivity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
relLayout = new RelativeLayout(this);
cusViews = new ArrayList<CusView>();
listParams = new ArrayList<RelativeLayout.LayoutParams>();
readDBandSetLayout();
setContentView(relLayout);
}
There is too much information in the readDBandSetLayout() method to present it all here. below are the most important details. If I create the LayoutParams in the following way it works fine, the cusViews are listed downwards and rightwards of eachother:
queryCursor = customApplication.customData.query( number); //Fetches cursor
for ( int i = 0; i < numberOfRows; i++ ){
if ( i == 0 ){
LayoutParams p = new LayoutParams(this.getResources().getDimensionPixelSize(R.dimen.small), this.getResources().getDimensionPixelSize(R.dimen.small));
p.topMargin = 50;
p.leftMargin = 50;
listParams.add(p);
}
else{
LayoutParams p = new LayoutParams(this.getResources().getDimensionPixelSize(R.dimen.large),this.getResources().getDimensionPixelSize(R.dimen.large));
p.addRule(RelativeLayout.BELOW, cusViews.get(i-1).getId());
p.addRule(RelativeLayout.RIGHT_OF, cusViews.get(i-1).getId());
listParams.add(p);
}
relLayout.addView(cusViews.get(i), listParams.get(i));
}
However, what I want to do in the else statement is something like:
else{
LayoutParams p = new LayoutParams(this.getResources().getDimensionPixelSize(R.dimen.large),this.getResources().getDimensionPixelSize(R.dimen.large));
//Here I want to calculate cusView2Topmargin and cusView2Leftmargin based on the widths of the first or previosly positioned cusViews. But here the widths are 0 since they haven't been calculated yet.
p.topMargin = cusView2Topmargin; //Always zero
p.leftMargin = cusView2Leftmargin; //Always zero
listParams.add(p);
}
So the problem lies in that the widths of the cusViews are zero at the point I need them to calculate the layout parameters topMargin and leftMargin.
Unfortunately I cannot use the RelativeLayout's Rules for what I want to achieve. If there were some way to create rules like RelativeLayout.RIGHT_OF and RelativeLayout.BELOW I could do it like that. Is this possible?
Its not very clear what your goal is for this layout. It might well be possible to use a simple LinearLayout to get what you want.
If you want to size these from a database lookup then try simply adding each of the views, using addView() first, storing a reference to each, then go back and sett the margins to place them in the proper positions.
Related
I made a linear layout and it contain some Image views . when i move one with (set X(view.get x + 10)) function, it moves... but it moves behind other views. that the view become hidden.
and the other problem is when i get X & Y of the view, its always 0,0. but the view is in the middle of the screen. what should I do??? should i give up with linear layout??
if(wichmov == "right" ){
if(martin.getX() < width){
martin.setX(martin.getX()+ deltax);
}
else if(wichmov == "left"){
if(martin.getX() > 0){
martin.setX(martin.getX()- deltax );
}
}
}
this is how i move it.
When are you trying to call getX()? This might be something to look into: View getX() and getY() return 0.0 after they have been added to the Activity
If you're making the call in onCreate, it'll be zero.
I just figured it out.
I use a relative layout and then set the linear layout as match parent. After designing the background with the linear layout, I define an image view after the linear layout and inside of the relative layout, and then in Java, I set the position of it in the exact place I want it to (front of the special box of linear layout that I wanted it move), and width and height of it too.
The following code will help you to place your view to the exact place of your screen you want it, and set its width and height:
DisplayMetrics metrics = this.getResources().getDisplayMetrics(); //getting screen size
width = metrics.widthPixels;
height = metrics.heightPixels;
view.setX(width *5/8); //setting the place
view.setY(height/4);
LayoutParams para = view.getLayoutParams(); //set size of view
para.height = (int) (height/2);
para.width = (int) (width/4);
view.setLayoutParams(para);
I have a dialog with a layout inside and a SurfaceTexture with a video stream. When I receive the width and height from the video, I resize my layout like this:
private void resizeView(final VideoFormatInfo info) {
final Size size = calculateSize(info.getWidth(), info.getHeight());
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
final ViewGroup.LayoutParams layoutParams = mInnerLayout.getLayoutParams();
layoutParams.width = size.x;
layoutParams.height = size.y;
Log.i(TAG, String.format("run: setting innerlayout bounds to %d,%d", size.x, size.y));
mInnerLayout.setLayoutParams(layoutParams);
}
});
}
Now I have a fullscreen button that is supposed to resize the layout to the whole screen. But when I press it, the layout remains in a small area of the screen.
When I check the log the proper value on size.x and size.y is there (the bounds of the screen), yet the layout is not properly resized.
The innerlayout is added to a customView named "VideoPlayer". I set the color of the videoplayer background to red so when I switch to fullscreen the whole screen turns red, except for the video stream in the middle. This means that the underlying view is being properly resized but the innerLayout is not for some reason.
Funny thing is, I have another layout over the video render that creates a "flash effect" to simulate a camera flash when taking a snapshot. When that flash effect is triggered, then the video is resized to the whole screen.
So this is my layout tree:
VideoPlayerView (CustomView, not VideoView)
innerLayout (RelativeLayout)
videoSurfaceTexture (SurfaceTextureView)
flashLayout (RelativeLayout)
I also set this for debugging:
#Override
public void onSurfaceTextureSizeChanged(final SurfaceTexture surfaceTexture, final int width, final int height) {
Log.d(TAG, "onSurfaceTextureSizeChanged size=" + width + "x" + height + ", st=" + surfaceTexture);
Log.i(TAG, String.format("innerlayout bounds are %d,%d", mInnerLayout.getLayoutParams().width, mInnerLayout.getLayoutParams().height));
}
And the values on the inner layout are the proper values (those of the whole screen) when I press fullscreen, but the layout is not resized. I can tell it's the layout not being resized because I changed its background color to green and added some padding and I can see it in the center of screen taking a small space.
It looks as though somehow the view is not being updated with the layout changes.
I am running out of ideas here. I tried invalidate(), postInvalidate() and forceLayout() but those dont work.
You missed one important part of forceLayout():
This method does not call requestLayout() or forceLayout() on the parent.
So make the parent do a layout as well:
mInnerLayout.setLayoutParams(layoutParams);
mInnerLayout.forceLayout();
mInnerLayout.getParent().requestLayout();
final ViewGroup.LayoutParams layoutParams = mInnerLayout.getLayoutParams();
layoutParams.width = size.x;
layoutParams.height = size.y;
Log.i(TAG, String.format("run: setting innerlayout bounds to %d,%d", size.x, size.y));
ViewGroup parent = ((ViewGroup)mInnerLayout.getParent());
parent.removeView(mInnerLayout);
mInnerLayout.setLayoutParams(layoutParams);
parent.addView(mInnerLayout);//you might need to get the index so you slot it in there.
This will do. -(all thoughts)
EDIT
i didnt want to add explanation because it was all thoughts and i needed verifying if it will work
But the explanation for my code is LayoutParams are what the Parent uses to layout its children hence it is useful only in the laying out pulse or time.
Changing the layoutParams object makes the View object dirty, other factors need to be met before a dirty View is layed out, so that is why the values change but the View is not changed.
you could have also just called View.invalidate() and View.requestLayout() on that particular View or Parent and it will also solve your problem, calling View.invalidate() alone will not do instantly for you. eg
layoutParams.width = size.x;
layoutParams.height = size.y;
Log.i(TAG, String.format("run: setting innerlayout bounds to %d,%d", size.x, size.y));
//re-setting the layout params is also not neccessary
mInnerLayout.invalidate();
mInnerLayout.requestLayout();
The reason the first approach solves your problem is because the View is remove and added which calls for a Laying out to be processed
:) also you should have just accepted it and let the bounty period elapsed :)
use Inflator like
View view = inflater.inflate( R.layout.item /* resource id */,
MyView.this /* parent */,
false /*attachToRoot*/);
for more check Layout params of loaded view are ignored
I need a dynamic gridlayout that can be toggled between 3 by 3 and 4 by 4. I can setRowCount and setColumnCount from 3 to 4 but not from 4 to 3. It will display following issue:
Caused by: java.lang.IllegalArgumentException: rowCount must be
greater than or equal to the maximum of all grid indices (and spans)
defined in the LayoutParams of each child.
Is there any work around to achieve this using gridlayout?
I realize this question is quite old, but for people who are still encountering this exception today, I'll offer an explanation that may shed some light upon how downsizing a GridLayout works and why I believe it is/was throwing an exception for the OP.
In Short:
Child views of the GridLayout can, after downsizing, occupy cells that are not within the GridLayout's grid, which is causing the IllegalArgumentException mentioned by the OP. To avoid this, remove child views that will occupy cells outside of the GridLayout's grid before actually calling setRowCount() or setColumnCount(). This can be done via GridLayout.removeView(aboutToBeIllegalChild); or by wiping the entire layout using GridLayout.removeAllViews();.
In Long:
All that calling GridLayout.setRowCount() does, is specify a new number of rows that the layout should contain. It does not, however, mess with the child views that the GridLayout currently contains, nor it's specified Spec (what column(s) and row(s) the child view occupies).
What the exception is basically telling us, and the docs confirm, is that a GridLayout does not allow any of its child views to occupy cells that are outside of the GridLayouts grid. As an example, the layout will not allow a child view to occupy cell (5, 1) when the grid is only 4 x 1.
This leads us to why the original poster was successful at dynamically increasing the GridLayout's dimensions, while being unsuccessful at decreasing it. When enlarging the dimensions, any child views that were already attached to the GridLayout with specified cells, would still be placed in legal cells if the grid received extra rows or columns dynamically. When reducing the dimensions of the grid, child views that were placed in cells that would disappear as a consequence of removing rows or columns, would now be considered illegal.
To work around this, you must either remove those (about to be) illegal child views from its parent GridLayout beforehand by calling GridLayout.removeView(aboutToBeIllegalChild); or simply wipe the entire GridLayout by calling GridLayout.removeAllViews();.
Hope this helps!
Based on Teun Kooijman answer you can just change Spec in GridLayout.LayoutParams and keep all Views inside the GridLayout:
private void changeColumnCount(int columnCount) {
if (gridLayout.getColumnCount() != columnCount) {
final int viewsCount = gridLayout.getChildCount();
for (int i = 0; i < viewsCount; i++) {
View view = gridLayout.getChildAt(i);
//new GridLayout.LayoutParams created with Spec.UNSPECIFIED
//which are package visible
view.setLayoutParams(new GridLayout.LayoutParams());
}
gridLayout.setColumnCount(columnCount);
}
}
You can also change Spec in other way by accessing GridLayout.LayoutParams.rowSpec and GridLayout.LayoutParams.columnSpec
For me, the issue was to change the number of columns of the GridView when the app changes the orientation. I achieved it by putting the bellow code in public void onConfigurationChanged(Configuration newConfig).
if (mGridLayout.getColumnCount() != getResources().getInteger(R.integer.nav_columns)) {
final int viewsCount = mGridLayout.getChildCount();
for (int i = 0; i < viewsCount; i++) {
View view = mGridLayout.getChildAt(i);
GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams();
int colIndex = i%getResources().getInteger(R.integer.nav_columns);
int rowIndex = i/getResources().getInteger(R.integer.nav_columns);
layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
layoutParams.width = 0;
layoutParams.columnSpec = GridLayout.spec(colIndex,1,GridLayout.FILL,1f);
layoutParams.rowSpec = GridLayout.spec(rowIndex);
view.setLayoutParams(layoutParams);
}
mGridLayout.setColumnCount(getResources().getInteger(R.integer.nav_columns));
}
The layout parameter values may need change depending on your need.
According to #Hensin 's answer, I have modify his codes for show how to copy the previous grid items layout parameters as following:
if (gridLayout.getColumnCount() != columnCount) {
final int viewsCount = gridLayout.getChildCount();
for (int i = 0; i < viewsCount; i++) {
View view = gridLayout.getChildAt(i);
GridLayout.LayoutParams oldParams = (GridLayout.LayoutParams) view.getLayoutParams();
GridLayout.LayoutParams newParams = new GridLayout.LayoutParams();
newParams.width = oldParams.width;
newParams.height = oldParams.height;
newParams.setMargins(oldParams.leftMargin, oldParams.topMargin, oldParams.rightMargin, oldParams.bottomMargin);
view.setLayoutParams(newParams);
}
gridLayout.setColumnCount(columnCount);
}
You can now re order your items with almost the same layout parameters
I'm trying to create Buttons in LinearLayout dynamically, and I want to add those in vertical and horizontal way.
At first, add a button A in the layout, and if there's enough space between button A and screen edge, add button B to the right of button A (horizontally). Otherwise, add button B below button A (vertically).
My current layout :
<LinearLayout
android:id="#+id/btn_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
</LinearLayout>
in class :
LinearLayout btnLayout = (LinearLayout) findViewById(R.id.btn_layout);
btnLayout.removeAllViewsInLayout();
for(Tag tag : tagList.getChildTags()) {
Button button = new Button(this);
button.setId(tag.getId());
button.setText(tag.getName());
btnLayout.addView(button);
}
In this case, if I set orientation as horizontal, then some of buttons are not showing (cut-off by screen), and if I set as vertical, it looks pretty bad.
Is there any way to solve this problem? Thanks everyone in advance!
You can achieve this but not in a trivial way. I'll explain how I do something similar (in my case, I add TextViews) to TableRows, if they fit.
With this approach you'll have to use a TableLayout and add TableRows to it with your Buttons. So you might replace your "#+id/btn_layout" LinearLayout to be a TableLayout instead.
Firstly, to get the screen's width, use something like this:
final Display display = getWindowManager().getDefaultDisplay();
final Point size = new Point();
display.getSize(size);
final WindowManager.LayoutParams params = getWindow().getAttributes();
// Your screen's width will be stored within your params.width value
You'll use this to know if the current Button still fits the screen's width within the current TableRow or it has to be added to a new one. So now, use something like this to create your buttons:
int currentRowsWidth = 0;
TableLayout tl = (TableLayout) findViewById(R.id.my_table_layout);
TableRow currentRow = new TableRow();
for (Tag tag : tagList.getChildTags()) {
Button button = new Button(this);
button.setId(tag.getId());
button.setText(tag.getName());
// There's where you check whether it still fits the current `TableRow` or not
if (currentRowsWidth + button.getWidth() < params.width) {
currentRowsWidth += button.getWidth();
currentRow.addView(button);
}
else {
// It doesn't fit, add the currentRow to the table and start a new one
tl.add(currentRow);
currentRow = new TableRow();
currentRow.addView(button);
currentRowsWidth = button.getWidth();
}
}
It might happen that once you get out of the loop there are still Buttons to add in the currentView, simply test it:
if (currentRow.getChildCound() > 0)
tl.add(currentRow);
I'm writing this from head, so some things might not compile at first time, but I hope you get the idea.
I am getting list of phone companies from web service and i have to set it to textview but the problem is i am not getting alignment as above image.How to achieve it.
From what I understand, you want to add text views one beside the other, but when they overflow (go out of the screen) the next text view should be placed in the next line.
Doing this is not trivial. Implementing something like this (optimally and correctly) requires understanding of how android draws views (onMeasure and onLayout). However if you do not care about efficiency that much (mainly because you are going to do it only for a small portion of the view) then here is my quick hack:
mContainer = (RelativeLayout) findViewById(R.id.container);
// first layout all the text views in a relative layout without any params set.
// this will let the system draw them independent of one another and calculate the
// width of each text view for us.
for (int i = 0; i < 10; i++) {
TextView tv = new TextView(getApplicationContext());
tv.setText("Text View " + i);
tv.setId(i+1);
tv.setPadding(10, 10, 20, 10);
mContainer.addView(tv);
}
// post a runnable on the layout which will do the layout again, but this time
// using the width of the individual text views, it will place them in correct position.
mContainer.post(new Runnable() {
#Override
public void run() {
int totalWidth = mContainer.getWidth();
// loop through each text view, and set its layout params
for (int i = 0; i < 10; i++) {
View child = mContainer.getChildAt(i);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
// this text view can fit in the same row so lets place it relative to the previous one.
if(child.getWidth() < totalWidth) {
if(i > 0) { // i == 0 is in correct position
params.addRule(RelativeLayout.RIGHT_OF, mContainer.getChildAt(i-1).getId());
params.addRule(RelativeLayout.ALIGN_BOTTOM, mContainer.getChildAt(i-1).getId());
}
}
else {
// place it in the next row.
totalWidth = mContainer.getWidth();
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.addRule(RelativeLayout.BELOW, mContainer.getChildAt(i-1).getId());
}
child.setLayoutParams(params);
totalWidth = totalWidth - child.getWidth();
}
mContainer.requestLayout();
}
});
Basically, I let the system do the layout and measurement for me in the first round(s) of drawing. Then using the widths of each text view now available, I reset the layout params based on the wrapping logic and do the layout again.
Try it with text of different size, it will auto adjust. I would say this solution is pretty hacky but it works. If you are not satisfied with it take a look at this.
use
android:textAlignment="textStart"