Fetching TextView Top/Y immediately after changing text size - android

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.

Related

Calculating if the text will take more than one line inside onBindViewHolder

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

Layout width and height being ignored

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

How to paginate/split a large string into multiple TextViews/Views

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());

Android: When to call methods that affect child layout upon parent view size change?

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)

When is the width of an Android View set?

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.

Categories

Resources