Hi I did a research on google, and tried so many solutions on stackoverflow on how to split a large text into multiple TextViews; but none gave me the desired result, not even near to it.
I tried Ellipsis, Measure and so on to calculate the length of the text that fits to a TextView, but they all failed because they were inaccurate, , , and because I use the methods in a BaseAdapter class, I couldn't use them directly, because the view is not rendered on inflation and the sizes are not available before rendering, so I had to use TextView.post(RUNNABLE), to get the width and height of the TextView after the rendering happens.
As I said, the above methods failed, besides of being in accurate, they messed up things a bit and my app became volatile (Exception HELL).
So I thought if there was a way to, render the layout (inflate) in a dummy View/ViewGroup/Activity (I don't know which one), then access the TextView and measure the size of it, this way, I'll make the code more stable, still the methods may be inaccurate, but it worth not doing further changes.
So I make it clear, I need to inflate my XML Layout in a dummy view, to access a child view's width/height.
UPDATE
I can now get the size using following code.
public class MyViewGroup extends LinearLayout {
public MyViewGroup(Context context) {
super(context);
}
}
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
MyViewGroup vg = new MyViewGroup(this);
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
vg.setLayoutParams(new LayoutParams(width, height));
View child = (View)ViewGroup.inflate(this, R.layout.content_layout_front_fa, vg);
vg.setVisibility(View.VISIBLE);
int measuredWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int measuredHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
vg.measure(measuredWidth, measuredHeight);
vg.layout(0, 0, vg.getMeasuredWidth(), vg.getMeasuredHeight());
vg.requestLayout();
TextView tv = (TextView)child.findViewById(R.id.content);
Log.e("tvW", "" + tv.getWidth());
Related
I have an Android application that extensively uses PopupWindows. I've found that when the layouts of the contents of the PopupWindow use WRAP_CONTENT the dialog will only grow to a specific width before it begins truncating the content. I've traced this down to a config.xml dimension:
<dimen name="config_prefDialogWidth">580dp</dimen>
This dimension is used to create a maximum width in the measureHierarchy method of ViewRootImpl when determining the dialog size. The following code is used to access the value:
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
The config_prefDialogWidth seems to have values for different device configurations, for example, the one I listed is for sw600dp. The one for default devices has 320dp as a value.
It would seem that this dimension is tuned for portrait orientation. Since my app forces landscape orientation this width is too small.
How do I override the config_prefDialogWidth dimension?
What you need is to make the width of your PopupWindow(wrap_content) larger than config_prefDialogWidth.
So here is my solution.
Choose one child View from the contentView of your PopupWindow.
A TextView, for example.
Override its onMeasure, add View.MEASURED_STATE_TOO_SMALL if necessary:
TextView textView = new TextView(context) {
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Text breaks into at least 2 lines, maybe the width is too small
if (getLineCount() > 1) {
int measuredWidth = getMeasuredWidth();
final int desiredMaxWidth = (getResources().getDisplayMetrics().widthPixels >> 2) * 3;
// Compare with the desired max width you want, for example: 3/4 * screenWidth
if (measuredWidth < disiredMaxWidth) {
// Tell ViewRootImpl that the width used to measure is too small
measuredWidth |= View.MEASURED_STATE_TOO_SMALL;
// ViewRootImpl will remeasure contentView with screem width.
setMeasuredDimension(measuredWidth, getMeasuredHeight());
}
}
}
};
Generally speaking:
Override onMeasure of one child View
Check whether the measured width is too small
Ask for remeasurement if necessary (Using View.MEASURED_STATE_TOO_SMALL)
You cannot override nor change config_prefDialogWidth's value because this dimension is a internal resource. A workaround is you must create your own class. You can copy PopupWindows from its source code, and modify it.
I am trying to translate a TextView based on the difference of its Y-value, and another Views Y-value. Before i translate the TextView, i change its text size, effectively changing its Y value. The problem that occurs is the TextViews Y-value isn't being updated immediately after a change in text size
E.G. Both log statements below yield same results
Log.i(TAG, String.valueOf(productTotal.getY()));
productTotal.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
Log.i(TAG, String.valueOf(productTotal.getY()));
However, if i were to run this code again, i would receive a updated Y-value. My guess is because the logic below is called at the press of a button, so perhaps the TextViews Y-value is being cached until the next Frame Update? Anyways, getTop, getY, getHeight are all giving me improper values. What suggestions do you have to fetch the updated Y-value immediately after changing the TextViews text size? Thank you.
If you need to find the value for getY() immediately, you will have to anticipate some of the work that will occur when the view undergoes the layout processing. This will primarily involve making a call to View#measure() as follows:
TextView productTotal = (TextView) findViewById(R.id.textView1);
int textSize = 100;
Log.i(TAG, String.valueOf(productTotal.getY()));
productTotal.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
Log.i(TAG, String.valueOf(productTotal.getY()));
ViewGroup parent = ((ViewGroup) productTotal.getParent());
int widthMeasureSpec =
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.UNSPECIFIED);
int heightMeasureSpec =
View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
productTotal.measure(widthMeasureSpec, heightMeasureSpec);
Log.i(TAG, String.valueOf(productTotal.getY()));
// No getMeasuredY(), so we have to back into the new value for "Y".
Log.i(TAG, String.valueOf(productTotal.getBottom() - productTotal.getMeasuredHeight()));
See the doc for measure().
[measure] is called to find out how big a view should be. The parent supplies constraint information in the width and height parameters.
I've got a text of an undetermined length and a TextView with android:layout_width="match_parent" and android:layout_height="wrap_content". Inside my RecyclerView.Adapter onBindViewHolder I need to calculate whether the given text will fit into the given TextView as a single line, or whether more than one line is needed (in which case I have the requirement to change the UI).
I know how to figure out the text width:
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
HeaderViewHolder holder = (HeaderViewHolder) viewHolder;
String text = mSelectedItem.getText();
Paint paint = holder.textview.getPaint();
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
Log.d(TAG,"bounds w: "+bounds.width());
Log.d(TAG,"bounds h: "+bounds.height());
}
However, I'm not sure what would be an efficient way forward from this. The text height is not of much use to me here as the bounds will only show the height of a single line of text.
I was thinking of measuring the width of the TextView's parent element and compare if the text width is larger than the parent width:
View v = (View) holder.textview.getParent();
v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
Log.e(TAG,"parent width "+v.getMeasuredWidth());
However, that returns a weird result of 522 when my whole layout hierarchy has the width set to match_parent and I'm testing on Nexus 5, so the value should be around 1080. The same value I get if I attempt to measure my RecyclerView. Also I think that's inefficient as it calls for an extra measurement cycle.
I've also tried:
ViewGroup parentView = ((ViewGroup) holder.textview.getParent());
double parentWidth = parentView.getWidth() - parentView.getPaddingLeft() - parentView
.getPaddingRight();
which works ok, but only after the first pass, which is obviously not good enough.
Any suggestions on a better way of determining whether the given text will fit a single line of the TextView?
EDIT:
Updated the code snippet
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 have a very simple RelativeLayout subclass that adds an image view with a text view on top of it. I have a method, show(), which creates and adds the child views and sets the initial text.
At the point I call show() for the first time, the view does not know how big it is, so I can't set the textSize nor the padding for the textView.
I have a solution that mostly works, where I call setTextSize() and setPadding() for the textView within the overridden method, onSizeChanged(). The text does not show the first time it is displayed. However, it shows every time after that, perfectly sized and placed.
Here is the code for onSizeChanged():
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.e(TAG, "onSizeChanged() called");
if (_childTextView != null) {
float textSize = h / 2.0f;
int topPadding = (int)(h / 3.0f);
Log.e(TAG, "setting textSize = " + textSize);
Log.e(TAG, "topPadding = " + topPadding);
_childTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
_childTextView.setPadding(0, topPadding, 0, 0);
}
super.onSizeChanged(w, h, oldw, oldh);
Log.e(TAG, "end onSizeChanged()");
}
The code for show() is as follows:
public void show(int val) {
_val = val;
Log.e(TAG, "in show(), val = " + val);
// create and add background image if not already there
if (_backgroundImageView == null) {
_backgroundImageView = new ImageView(_context);
_backgroundImageView.setImageResource(R.drawable.background);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
params.addRule(CENTER_IN_PARENT);
addView(_backgroundImageView, params);
}
// create and add text view if not already there
if (_childTextView == null) {
_childTextView = new TextView(_context);
_childTextView.setTextColor(Color.BLACK);
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(CENTER_HORIZONTAL);
addView(_childTextView, params);
}
Log.e(TAG, "setting text to: " + _val);
// update value and make visible
_childTextView.setText(String.valueOf(_val));
setVisibility(VISIBLE);
Log.e(TAG, "end show()");
}
The background image displays correctly every time. The textView only displays correctly the second time show() is called and afterwards. Logging in onSizeChanged() shows that the calculated numbers are correct the first time. As expected, onSizeChanged() only gets called the first time, a bit after we return from show(). Subsequent calls to show() just set the value and visibility, and the text is displayed correctly.
My question is: is there a better way to do this? Or a better callback method to override?
Trying to set these values in show() doesn't work because the main view doesn't yet know its own size (at least the first time). I have tried putting invalidate() at the end of onSizeChanged(). I have also tried putting the call to setText() there.
I need to be able to do this based on size, because this class is reused in different contexts where the image needs to be smaller or larger.
Thank you for any insight you can give. I'd really like to keep this simple if possible.
Edit: What I am trying to do is size some text to be about 1/2 the size of the child image (which is the same as the parent size), and to have top padding set to about 1/3 of the image size. This would be easy if I just wanted it to be one size. However, I want it to be size-adjustable based on the needs of the display.
Imagine a postage stamp, where you want to place the value somewhere precisely in the image. So far so good. But what if this postage stamp needs to be displayed at different sizes on the same phone? You'd want both the placement offset (the padding) and the text size to adjust accordingly. If I hardcode this into the xml, then the text size and placement will not be adjusted when I size the layout. The text will be too big on the small version, and will be placed too far from the top of the image.
i have no idea why you override onSizeChanged(), normaly android handles all this nicely if you use it the way it is intendet.
can you pls explain what you want to achive - maybe with example picture?
however i wondered that you don't override onMesure() when you override the rest and if a delayed call to show() helps it also might be because of onMesure is called in between.
edit:
in android you should never want to know a real size of some views. nearly every device has other sizes and there is portrait/landscape mode too. if you start coding vs real sizes you can give up at the start. instead you should use something more relative like dp and sp than you should never again worry about text sizes and similar.
you may also want and you should use LayoutInflater and xml files as much as possible in your application. in a Activity you can call setContentView(). in other cases there might be methods to overload like onCreateView. and if you have nothing else you can do it like this:
LayoutInflater inflater = LayoutInflater.from(contextEgActivity);
View rootView = inflater.inflate(R.layout.highscore_daily, parentCanBeNull);
edit II:
so this is what you want - right? (on the ImageView and the TextView it would be even better to use wrap_content for height and width)
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:id="#+id/imageView"
android:layout_gravity="center"
android:background="#ff0000" />
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:text="New Text"
android:id="#+id/textView"
android:layout_gravity="center"
android:background="#00ff00"
android:textSize="20sp" />
</FrameLayout>
if you only have ~3 different sizes i would write 3 different xml files to match what you want. otherwise i think this code will fit your needs.
//http://stackoverflow.com/questions/4605527/converting-pixels-to-dp
public static float convertDpToPixel(float dp, Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float px = dp * (metrics.densityDpi / 160f);
return px;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//loads the xml above
ImageView v = (ImageView) findViewById(R.id.imageView);
int dp = 200;
int px = (int) convertDpToPixel(dp, this);
v.setMaxHeight(px);//no need for it
v.setMinimumHeight(px);//should do more or less the same as next line
v.setLayoutParams(new LinearLayout.LayoutParams(px, px));//is like android:layout_width="200dp" android:layout_height="200dp"
v.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, px));//is like android:layout_width="wrap_content" android:layout_height="200dp"
//basically you can do the same with the TextView + the Text styling
TextView tv = (TextView) findViewById(R.id.textView);
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 50);
tv.setPadding(30,30,30,30);//don't forget, this is also px so you may need dp to px conversion
}
this is the normal way, nice clean and easy. if you why ever still want to react on size changes of your parent you can try this but i don't suggest it. btw changing view stuff should only be executed from ui/main thread so if the method gets called from a other thread thry a Handler like new Handler(getMainLooper)