Why won't my LayoutParams change my layout? - android

I want to display a box with suggested entries next to an EditText.
I figured I'd have to set LayoutParams (like suggested in this question.
However, no matter what I try, the box always ends up in the top left corner.
What I tried:
- get LayoutParams as MarginLayoutParams (which they are), and set them. Set them back in the view for good measure.
- All the answers in the link mentioned above
Change the position with TranslationX and -Y (which works, but runs into other problems like status bar might not always be 24dp and seems a hack)
val layoutParams = box?.layoutParams
//val layoutParams = ConstraintLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply{
// setMargins(left, top, 0,0)
//}
if (layoutParams !is ViewGroup.MarginLayoutParams) return#onTextChanged
//layoutParams.leftMargin = left
layoutParams.marginStart = left
layoutParams.topMargin = top
box?.layoutParams = layoutParams // Don't think this is necessary
Log.d(TAG, "$layoutParams")
// works, but don't like it
// box?.translationX = left.toFloat()
// box?.translationY = top.toFloat()
//doesn't work
//box?.setMargins(left, top, right, 1000)
the function View.Setmargins is as follows:
fun View.setMargins(left: Int, top: Int, right: Int, bottom: Int) {
if (layoutParams is MarginLayoutParams) {
val p = layoutParams as MarginLayoutParams
p.setMargins(left, top, right, bottom)
requestLayout()
}
}
Complete source of the thing I want to do here (snippet above is at line 170, edited it a bit to show things I've tried)
Other things I have tried: add box to different layouts, which in all cases results in the box being drawn in the top-left corner of that layout

For anybody having the same problem as I had:
Mike M. 's suggestion to get the LayoutParams as ConstraintLayout.LayoutParams, and also set its topToTop and startToStart fields to ConstraintLayout.LayoutParams.PARENT_ID did the trick, ie.
(box?.layoutParams as ConstraintLayout.LayoutParams).apply{
topToTop = ConstraintLayout.LayoutParams.PARENT_ID
startToStart = ConstraintLayout.LayoutParams.PARENT_ID
topMargin = top
marginStart = left
}

Related

Setting icon for TextInputLayout helper text and centering it to top

So I need to set up 2 things:
Icon for helper text in TextInputLayout
Center it to TOP, since helper text might be multiline
It might look something like this:
Is there easy of doing working around this, without making my own TextInputLayout?
Solving this melts down to following 2 possible solutions:
First is to change the Rect of the Drawable itself by setting the bounds (coordinates X and Y), drawback of this solution is whenever layout is redrawn (orientation change etc.), this has to be done again.
findViewById<AppCompatTextView>(R.id.textinput_helper_text)?.run {
compoundDrawables.first()?.run {
setBounds(
bounds.left,
bounds.top - this#with.height.div(2),
bounds.right,
bounds.bottom - this#with.height.div(2)
)
bottom = this#with.height.div(2)
}
}
Second solution is to add new ImageView in front of the helper TextView and add padding to it, so it's pushed up.
It's not a one liner, but it can be formed into easy to use method as such:
fun TextInputLayout.setStartHelperTextIcon(
#DrawableRes drawableRes: Int,
paddingPx: Int
) {
with(findViewById<AppCompatTextView>(R.id.textinput_helper_text)) {
(this?.parent?.parent as? LinearLayout)?.run {
if(findViewById<ImageView>(R.id.text_input_layout_helper_icon) == null) {
addView(
ImageView(context).apply {
id = R.id.text_input_layout_helper_icon
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setPadding(
paddingPx,
0,
paddingPx,
paddingPx + this#with.height.div(2)
)
},
0
)
}
}
}
}

wrap_content breaks performance of my constraint layout

I have a single TextView which I am updating as a counter every second. If I use wrap_content for its width, I get performance warnings that it takes 50ms to draw the view update in the layout/measure phase. When I programmatically set its width and height, it addresses the alerts, but my views no longer resize (obviously :). What am I missing here? Is there way to minimize view invalidations / resizing to be only when the text changes to a greater width? Do I have to do that programmatically and calculate the new size each time I update the view?
This is not really a solution to why it was happening in the first place, but a workout around (Which feels very anti-android since it doesn't rely on ConstraintLayout to solve what hopefully is a simple system of equations to determine the proper intrinsic TextView width without breaking the whole UI thread):
fun TextView.conditionallyChangeWidthWithNewString(newString: CharSequence) {
val calculatedWidth = getWidthOfNewText(newString.toString())
if (calculatedWidth > layoutParams.width) {
val params = getLayoutParams()
params.width = calculatedWidth
setLayoutParams(params)
}
setTextIfChanged(newString)
}
fun TextView.getWidthOfNewText(s: String): Int {
val p = Paint()
val bounds = Rect()
val plain = typeface
p.setTypeface(plain)
p.textSize = textSize
p.getTextBounds(s, 0, s.length, bounds)
val mt = p.measureText(s)
return mt.toInt()
}

Programmatically set margin to ConstraintLayout

I need to change margin of toolbar, which was made of ConstraintLayout, in different cases. I tried to do it in following way
ConstraintLayout.LayoutParams newLayoutParams = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.WRAP_CONTENT);
ConstraintLayout.MarginLayoutParams layoutParams = new ConstraintLayout.MarginLayoutParams(newLayoutParams);
layoutParams.setMargins(0, 0, 0, 0);
toolbar.setLayoutParams(newLayoutParams);
but in second case layoutParams.setMargins(16, 16, 16, 16);
it did not change. So, can someone give other way or point to the mistake. Thanks for spending time to my problem.
I tried to use newLayoutParams.setMargins(54, 54, 54, 0); this puts margin to left and right, but I still need to put margin on top of it.
Finally with help of #Aksh I found my mistake and solve my problem. If someone find it useful, I will put my code bellow
ConstraintLayout.LayoutParams newLayoutParams = (ConstraintLayout.LayoutParams) toolbar.getLayoutParams();
newLayoutParams.topMargin = 0;
newLayoutParams.leftMargin = 0;
newLayoutParams.rightMargin = 0;
toolbar.setLayoutParams(newLayoutParams);
good way to add margin to a view in constraintlayout
val constraintSet = ConstraintSet()
constraintSet.clone(constraintLayoutId)
val marginTop = context.getDimension(10f)
constraintSet.setMargin(R.id.tv_user, ConstraintSet.TOP, marginTop)
constraintSet.applyTo(constraintLayoutId)
fun Context.getDimension(value: Float): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, value, this.resources.displayMetrics).toInt()
}
I once modified a ConstraintLayout with a ConstraintSet to change the elements it got linked with. I also changed the margin within that process which is the last parameter of the connect method:
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(layout);
constraintSet.connect(
R.id.layout_id,
ConstraintSet.START,
R.id.layout_id2,
ConstraintSet.END,
8);
constraintSet.applyTo(layout);
Maybe this gives you another hint to find a solution?
You might wanna try
ViewGroup.MarginLayoutParams params = yourConstraintLayout.getLayoutParams();
params.topMargin = x;
//others shows up in suggestion
I haven't tried it though I believe it should work as ConstraintLayout is a ViewGroup.
For layoutParams.setMargins(w, x, y, z) the reason it's not changing is because you're most likely treating the values as Dp's rather than Px's, for which the method accepts.
To get Px from the Dp, you want to use:
int pxValue = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());

Set ascent, descent, baseline of text in TextView

I need two TextViews to have same baseline, ascent, bottom, descent values. Is it possible to change the FontMetrics of TextView after text has been drawn?
It is so late, but I also confronted with this issue, and I solve with some tricks. (just modify baseline, not ascent, descent or something)
note that fontMetrics' values(ascent, descent, bottom, top) are depend on textSize.
so we must use fontMetrics' values after view is drawn. (if it is autoSizing or something)
to do something after view drawn, using Sa Qada's answer
baseline is always 0. descent, ascent, top, bottom is relative values.
refer this Suragch's answer.
change fontMetrics' values does not affect to UI.
to set custom baseline(text position of textview), just use padding.
below code is align to baseline with two TextView.
// this is kotlin code, but I'm sure you can understand
// align textview2 to textview1's baseline
val tv1Baseline = tv1.paddingBottom + tv1.paint.fontMetrics.bottom
val tv2Baseline = tv2.paddingBottom + tv2.paint.fontMetrics.bottom
val newPaddingOffset = tv1Baseline - tv2Baseline
tv2.setPadding(tv2.paddingLeft, tv2.paddingTop, tv2.paddingRight,
tv2.paddingBottom + newPaddingOffset)
Here's solution to align baselines (note the difference from bottom in Suragch answer and definition of return value of TextView.getBaseline()) which alignes tv2 baseline to tv1s':
private void alignWeatherTextBaseline(View tv1, View tv2) {
int tv1Baseline = tv1.getTop() + tv1.getBaseline();
int tv2Baseline = tv2.getTop() + tv2.getBaseline();
int newPaddingOffset = tv1Baseline - tv2Baseline;
if (newPaddingOffset != 0) {
tv2.setPadding(tv2.getPaddingLeft(),
tv2.getPaddingTop(), tv2.getPaddingRight(),
tv2.getPaddingBottom() - newPaddingOffset);
}
}
Be aware of the following:
if multiline text is possible, you need to write proper getLastLineBaseline() and use it instead of standard getBaseline()
you can get invalid value (0 or -1) from getBaseline() if a view hasn't been layout yet

How to know what size a TextView WOULD be with certain string

Maybe I'm not entering the right keywords, but I'm not finding an answer. I want to know what the dimensions of a TextView would be if I were set it with a certain string. However, I want to know before everything gets laid out in the activity.
My TextView has a fixed width and a variable height. I can get the height like this:
myTextView.setText(myString);
// ... UI gets laid out ...
myTextView.getHeight()
I want to change the width of the TextView if the height gets past a certain point. (But not before then.) And rather than waiting until after the UI gets laid out, I want to know beforehand what the height would be if it had myString and then change the width if I needed to.
I looked at the Layout class but I couldn't figure out what to do. I wonder if it might have something to do with overriding the TextView's onMeasure but I really don't know how to attempt that. Any help is appreciated.
Update
Thanks to both #user3249477 and #0xDEADC0DE for their answers. I'm marking #user3249477's answer as the solution for now (although since I need multiple resizes of the view I'm not sure about repeatedly turning the visibility on and off) but also +1 to #0xDEADC0DE for giving me the keywords I needed to further look into this problem.
I need to do more research and testing on this. Here are some links that I have found helpful so far:
OnLayoutChangeListener:
View.OnLayoutChangeListener
Capture Layout resize before API 11
After changing a property on a LayoutParams object, do I need to call setLayoutParams again?
measureText() and getTextBounds():
Android Paint: .measureText() vs .getTextBounds()
Paint.getTextBounds() returns to big height
Gettextbounds in android
Overriding onSizeChanged of the parent view also looks intriguing: https://stackoverflow.com/a/14399163/3681880
Set your TextView to invisible:
android:visibility="invisible"
and measure it. Once you're done set it to visible:
TextView myTextView = (TextView) findViewById(R.id.text);
final int maxHeight = 500;
myTextView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
v.removeOnLayoutChangeListener(this);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) v.getLayoutParams();
Log.e("TAG", "H: " + v.getHeight() + " W: " + v.getWidth());
if (v.getWidth() > maxHeight) {
params.width += 100;
v.setLayoutParams(params);
}
v.setVisibility(View.VISIBLE);
}
});
You could do it without overriding. If you get the TextViews Paint with getPaint(), you can use measureText(string) the get the minimal with of the TextView when it is drawn with that Paint. I looks like this:
TextView textView = new TextView(this);
float textWidth = textView.getPaint().measureText("Some Text");
Update
To get the height, you can call getTextBounds() on the Paint object like this:
String text = "Some Text";
Rect textBounds = new Rect();
textView.getPaint().getTextBounds(text, 0, text.length(), textBounds);
float height = textBounds.height();
float width = textBounds.width();

Categories

Resources