I'm attempting to build a layout programmatically.
This layout is dynamic. Based on the data read from a database, it could look different every time.
I have a LinearLayout with its orientation set to vertical.
I want to fit as many TextViews with text (the data from database) as I can on a "row".
What I'm doing is building LinearLayouts that are the rows. These LinearLayouts are populated with TextViews. I build the TextView, set the text, and then check the width to see if it will fit on this row (by subtracting the sum of all TextView widths from the screen width and see if the new TextView will fit). If not, I create a new LinearLayout and start adding TextViews to that one.
The problem is myTextView.getWidth() and myTextView.getMeasuredWidth() both return 0.
Why? How can I get the width of the TextView?
Well first of all, please post your code. Second the getWidth/getHeight returning zero question gets asked A LOT so you could search SO for more answers.
Assuming your calling these methods during onCreate (which is probably the case), the UI hasn't been drawn yet so the returned value is zero. You can't get the width or height of the view until it has been drawn.
You can use this library to schedule the task of perform calculation on the width to the correct time after the view had been completely drawn
You can call it on onCreate() and from any Thread
https://github.com/Mohamed-Fadel/MainThreadScheduler
Sample of use:
MainThreadScheduler.scheduleWhenIdle(new Runnable() {
#Override
public void run() {
int width = textview.getWidth();
int height = textview.getHeight();
textview.setText( String.valueOf( width +","+ height ));
}
});
Related
I am designing a title row that has the item title in the middle, with a status description on the left and expiration date description on the right. The title should be centered at all times and expand width until it runs into either of the side views, at which time it should get ellipsized. I'm having trouble figuring out how to keep the title view centered while allowing for the two side views to have different sizes, while also trying to show as much of the text as possible in cases where the side views have only two or three characters, or as many as 20.
This is NOT a basic question of having the center view fill the space. The cases will outline the additional requirements I have that make it more tricky.
I tried two main approaches:
LinearLayout
I started with a horizontal LinearLayout. The first and third TextViews had widths of WRAP_CONTENT while the center title view had a width of 0dp and weight of 1, allowing it to fill the middle section of the row. The issue here was that the first and third views wouldn't have the same width with WRAP_CONTENT, so the title view would be shifted one way or the other. See here how the title is shifted to the left towards the smaller text:
After that, I assigned a weightSum of 20 to the LinearLayout, gave weights of 4 to the first and third views, a weight of 12 to the middle view, and width of 0dp for all 3. This worked better but wouldn't allow for longer text in the end views, even when there was plenty of room in the row for the view to expand:
Changing the views to use layout_weight and widths of WRAP_CONTENT didn't work either.
ConstraintLayout
I switch the row to a ConstraintLayout to try that as well.
I started this route with the end views using WRAP_CONTENT as their width and the title view using 0dp. The title view had its start linked to the end of the first view, and its end linked to the start of the last view. Of course, this would shift the title view towards whichever end text was shorter. Changing the end views to a width of 0dp yielded the same result.
From here I got closer. I linked the middle view's start and end to the parent LinearLayout so that it stretched the entire width. Then I linked the first view's end to the middle view's start, and the last view's start to the middle view's end, and set both of their widths to 0dp. By setting the middle view's width to 0dp and adding a marginStart and marginEnd of 64dp, I allowed some space for the first and last views to be shown. Still, this was like the LinearLayout solution with weights, as the gaps on each side of the middle view was of the fixed 64dp size, and didn't allow for expansion.
My last effort was so close! Changing the middle view's width to WRAP_CONTENT, and start and end both linked to the parent, allowed the end views to be of equal widths, and fully expanded. That is, UNTIL the title view became very long. With a width of WRAP_CONTENT, the middle view would push the end views off the screen with a very long text, even with a min_width set on the end views.
Changing the end views to have widths of WRAP_CONTENT didn't work either.
What I want to accomplish is most like this last try, except that I want the middle view to stop expanding and become ellipsized instead of pushing the end views out. Any other ideas to try?
The problem is if you want the first and third views equal width I don't think it is possible with just standard xml layouts as it's too late once a view has been sized, it won't be resized again without extra code.
Something like TableLayout has the extra code to resize all the rows to match, one it's sized them individually.
But you can to this yourself programmatically
e.g
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout mainLayout = findViewById(R.id.content);
// Wait until mainLayout has it's size calculated
ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Don't listen anymore
mainLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
TextView col1 = new TextView(getApplicationContext());
TextView col2 = new TextView(getApplicationContext());
TextView col3 = new TextView(getApplicationContext());
col1.setText("Much Long");
col1.setBackgroundColor(Color.CYAN);
col3.setText("Long");
col3.setBackgroundColor(Color.CYAN);
col3.setGravity(Gravity.RIGHT);
col2.setText("Really Really Really Really Really Really Really Really Really Long");
col2.setGravity(Gravity.CENTER_HORIZONTAL);
col2.setSingleLine(true);
// Set it to have Ellipsizes if too long
TextUtils.TruncateAt truncate = TextUtils.TruncateAt.END;
col2.setEllipsize(truncate);
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); // As big as the view wants to be
// Measure all the views
col1.measure(measureSpec, measureSpec);
col2.measure(measureSpec, measureSpec);
col3.measure(measureSpec, measureSpec);
// work which side is bigger
int maxWidth = Math.max(col1.getMeasuredWidth(), col3.getMeasuredWidth());
int parentWidth = mainLayout.getWidth();
// Probably should have some checks to make sure centre width does not go negative
int centreWidth = parentWidth - (2 * maxWidth);
col1.setWidth(maxWidth);
col2.setWidth(centreWidth);
col3.setWidth(maxWidth);
mainLayout.addView(col1);
mainLayout.addView(col2);
mainLayout.addView(col3);
}
};
mainLayout.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
}
Produces some examples
The centre of the middle column is always at the centre of the screen
I'm implementing a ListView where the elements are expanded by clicking on them to show all the text. I'm using this implementation.
As you can see that project was made considering hardcoded strings and one of the arguments is the height of the TextView after the expansion. Since I can't know the final height o the TV because my strings are fetched from the internet I set the expanded height to:
AbsListView.LayoutParams.WRAP_CONTENT;
As you may know WRAP_CONTENT to android is just a -2 Integer. My problem is that the -2 is passed to the animaton method which does the following calculation:
float height = (ToHeight - FromHeight) * interpolatedTime + FromHeight;
Because ToHeight is -2 (and assuming FromHeight is 200) the TextView animation height goes someting like this:
200.0
191.05173
175.801
158.36632
134.70096
105.341835
78.51846
53.146957
31.025742
9.73967
-1.203598
-2.0
So the animation gives the impression is closing but after that height gets just fine because is -2 (WRAP_CONTENT). How can I solve this?
You may need to show some more code or your XML file that contains the TextView you're trying to animate. A solution that might work for you is to dynamically calculate the height your TextView should be when expanded. You can use TextView.getLineCount() to get the number of lines of text and multiply that with TextView.getLineHeight() to get the size, or at least something close, of how tall the TextView should expand out to.
In general, the WRAP_CONTENT constant is used as a flag for Android's framework to specify dimensions during layout.
I have a ListView and each row is a RelativeLayout with 2 TextViews side-by-side (call them textViewLeft and textViewRight).
The textViewRight text can change between "xxxxxxx" and "yyy" (both different lengths). I want to set the textViewRight width to be able to fit the "xxxxxxx", and then if I change the text to "yyy", I DON'T want the textViewRight width to change.
I know that in my xml I can specify layout_width to a particular value, but devices have lots of different fonts. So is there a way that I can programmatically set the width of textViewwRight to ensure it always fits the string "xxxxxxx"?
Well, there is no easy way to answer that, you could set the width through code, using the width available, you coul change the text size depending on the screensize, there are several different solutions.
It really depends on how you are handling your app, do you already have different layout folders? If so why not try some sizes that will work well for the intended range?
Either way, you should calculate it to fit the bigger text from the start and not change it when the text changes.
To be more specific. One solution would be to have the text on the Textview be xxxxxx, and have the layout's width in xml be "wrap_content".
Now what you do is, get the view's width after it has been laid out, or else you get a 0 return.
One way to do exactly that is to add a viewtreeobserver to your TextView on your onCreate method:
ViewTreeObserver vto = yourTextView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
this.yourTextView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = layout.getMeasuredWidth();
int height = layout.getMeasuredHeight();
}
});
Now you have the width, all you have to do is change the width to that fixed value, the following should work:
yourTextView.getLayoutParams().width = theWidthYouGot;
I get following requirement.
I need to build a tree with leaves placed left and right, from the screen top to the bottom. I can not put the leaves in a ListView because tow leaves will be in same offset.
I don't know the height of the items.
I need to pre-calculate the height of dynamic content, such as strings with different lengths.
QUESTION:
How can i pre-calculate the height of a sting which will be put in a TextView widget described as follows:
<TextView
android:id="#+id/note_content"
android:layout_width="40sp"
android:layout_height="wrap_content"
android:maxLines="5" />
Create textView, set LayoutParams, set text, call it's measure method and read measured values. BTW you can measure text, not textView (see StaticLayout).
I think you better create your custom layout where you can implement all your "leaves measument" algorithms. Check this lesson. Custom layout is very easy )
Use this for calculate height and width your textview
textView.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
public void onGlobalLayout() {
h=textView.getHeight();
w=textView.getWidth();
textView.getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
}
But in xml do not assign textview as a wrap content. Because it will give you value 0.
I used Horizontal scroll view in which I add 'Child View'. But on HZ scroll view I want to arrange child some think like, whole screen will show 3.5 child at a time or 4.5 , 5.5 depends on screen size. The half of child indicate there is more child on Scroller.
For that I used different dimensions for child view depends on density. But still their is some device which show whole child at the end of screen.
So how I can mange this on scroll view. Please guide me in right direction.
For that I used different dimensions for child view depends on
density. But still their is some device which show whole child at the
end of screen.
That will not work well in a lot of devices.
Assuming that those orange child views are all of the same width(although not that important) and you want to show half of one only when the HorizontallScrollView is first laid out, you could simply post a Runnable(in the onCreate method) on a view to set their widths to a proper dimension so you make the proper appearance:
public void onCreate(Bundle saved) {
// ... work
horizontalScrollView.post(new Runnable() {
#Override
public void run() {
DisplayMetrics dm = getResources().getDisplayMetrics();
int screenWidth = dm.widthPixels;
// based on this width, the space between the children and their
// widths calculate new widths so you get a half clipped
// children on the right side.
}
});
}
But, I would avoid this and simply set a much more powerful indicator on the HorizontalScrollView(overriding its dispatchDraw method and drawing something there(maybe also hiding it a bit later with a Runnable)) if you really want to make sure that the user sees the extra content.