Align baseline of TextView to Guideline in Constraint Layout - android

Hopefully I'm not being completely thick here, but I'm unable to align the baseline of a TextView to a Guideline in a ConstraintLayout. It appears the guideline does not have a baseline, which is pretty annoying. Does anyone know how I might achieve this? Here's a bit of layout xml that doesn't work (this is within a ConstraintLayout):
<TextView
android:id="#+id/textToAlignBaseline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:maxLines="1"
android:paddingRight="6dp"
android:textAppearance="#style/TextStyles.Body"
app:layout_constraintBaseline_toBaselineOf="#+id/guidelineBottomMargin"
app:layout_constraintLeft_toLeftOf="parent" />
<Button
android:id="#+id/buttonToAlignBottom"
android:layout_width="wrap_content"
android:layout_height="39dp"
android:layout_marginRight="20dp"
android:background="#drawable/selector_button_bg"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:text="#string/clickme"
android:textAppearance="#style/TextStyles.Body"
app:layout_constraintBottom_toTopOf="#+id/guidelineBottomMargin"
app:layout_constraintRight_toRightOf="parent" />
<android.support.constraint.Guideline
android:id="#+id/guidelineBottomMargin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_end="20dp" />

I had this question when I tried to implement a two-line list according to Material Design, where the "Two-line item" baseline should be 32dp from the top:
It looks like a plain View or Guideline doesn't have a baseline because it returns -1 from getBaseline. So what I ended up with is using an invisible TextView like this:
<TextView
android:id="#+id/guideline"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="32dp"
android:textSize="0sp"
android:visibility="invisible"
app:layout_constraintTop_toTopOf="parent" />
And then use app:layout_constraintBaseline_toBaselineOf="#id/guideline" for aligning the other view to it.

I think I might have come up with a solution, I tried extending Baseline and returning 0 from 'getBaseline' but that was never called, so I tried extending AppCompatButton instead and returning 'getMeasuredHeight' from 'getBaseline' instead (just like ImageView does when 'baselineAlignBottom' is used) and this seems to work correctly now. The TextView just needs changing to align its baseline to the button instead of the guideline. I need to clean it up with attributes similar to ImageView but this is what I've got for now:
public class ButtonBottomBaseline extends android.support.v7.widget.AppCompatButton {
public ButtonBottomBaseline(Context context) {
super(context);
}
public ButtonBottomBaseline(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ButtonBottomBaseline(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public int getBaseline() {
return getMeasuredHeight();
}
}

I've found a workaround to align the text following exactly the Material Design guideline as mentioned by #robinst .
Firstly add an ImageView, with 0 as layout_width, your desired baseline as layout_height, no src and baselineAlignBottom set to true:
<ImageView
android:id="#+id/typeTextBaseline"
android:layout_width="0dp"
android:layout_height="24dp"
android:baselineAlignBottom="true"
android:visibility="invisible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Then you can align your TextView's baseline to this ImageView's baseline:
<TextView
android:id="#+id/typeTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textAppearance="#style/TextAppearance.MaterialComponents.Overline"
app:layout_constraintBaseline_toBaselineOf="#id/typeTextBaseline"
app:layout_constraintStart_toStartOf="parent" />
I found this workaround when searching all implementations of View's getBaseline method, and the ImageView's implementation is as follow:
public int getBaseline() {
if (mBaselineAlignBottom) {
return getMeasuredHeight();
} else {
return mBaseline;
}
}
So, once you set baselineAlignBottom to true, the ImageView's baseline becomes its bottom edge and it is magically served as a guideline now:)

Related

How to set minimum percentage width of View in a ConstraintLayout

I have two Buttons organized in a row in a ContraintLayout. By default, I need them to be evenly distributed each taking 50% of the ConstraintLayout width (easy enough with a vertical Guideline set to 50%):
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
<Button
android:id="#+id/secondaryButton"
style="#style/Button.Secondary"
android:text="Secondary Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/primaryButtonBarrier"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail" />
<Button
android:id="#+id/primaryButton"
style="#style/Button.Primary"
android:layout_width="0dp"
android:text="Primary Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/middleGuideline"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintTop_toBottomOf="#id/detail" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/middleGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/primaryButtonBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="primaryButton, middleGuideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail" />
</androidx.constraintlayout.widget.ConstraintLayout>
Desired behavior (currently achieved programmatically with code snippet below):
I also need to support "overflow" text in the primary Button on the right.
So, if I have a larger amount of text, the Button width will push past the 50% guideline. I've tried using app:layout_constraintWidth_min, but it doesn't take a percentage (wrap or dimen).
The best solution I can come up with is setting the Button width to wrap_content and then the min width programmatically after the ConstraintLayout has been measured:
doOnLayout { constraintLayout ->
val layoutParams = primaryButton.layoutParams as ConstraintLayout.LayoutParams
layoutParams.matchConstraintMinWidth = constraintLayout.measuredWidth / 2
primaryButton.layoutParams = layoutParams
}
I feel like there has to be a combo of settings here that I'm missing to achieve this. Thanks in advance!
There is no straightforward way to do what you want that I know of with any standard XML code and the standard ConstraintLayout attributes. You might be able to do something with a nested layout, but, in general, that should be avoided with ConstraintLayout. I think that the way you devised is probably going to be the way to go.
However, if you want to do this all in XML, you could create a shadow button that is a replica of the primary button in all aspects (position, style, text, etc.) except that it is invisible. A barrier can then ride to the left of the shadow button and the center guideline. The visible primary button can now be constrained to the barrier since the barrier is not dependent upon the primary button. The primary button will at least be 1/2 the width of the ConstraintLayout but can expand beyond.
A second approach would be to create a custom view for the primary button. This custom view can implement an attribute that specifies a minimum width for the button based on the width of the ConstraintLayout.
ButtonMinWidthOfParent.kt
class ButtonMinWidthOfParent #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MaterialButton(context, attrs) {
private var minWidthOfParent = 0f
init {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.ButtonMinWidthOfParent,
0, 0
).apply {
try {
minWidthOfParent = getFloat(R.styleable.ButtonMinWidthOfParent_minWidthOfParent, 0f)
} finally {
recycle()
}
}
}
#Override
override fun getSuggestedMinimumWidth(): Int {
return maxOf(
super.getSuggestedMinimumWidth(),
((parent as ViewGroup).measuredWidth * minWidthOfParent).toInt()
)
}
}
attrs.xml
<resources>
<declare-styleable name="ButtonMinWidthOfParent">
<attr name="minWidthOfParent" format="float"/>
</declare-styleable>
</resources>
activity_main
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="#+id/detail"
android:layout_width="0dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/secondaryButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Secondary Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#id/primaryButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail" />
<com.example.secondarybutton.ButtonMinWidthOfParent
android:id="#+id/primaryButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Primary Button that is a little bit longer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/detail"
app:minWidthOfParent="0.5" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/middleGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<View
android:id="#+id/view"
android:layout_width="2dp"
android:layout_height="0dp"
android:background="#android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
There are undoubtedly other ways, but the XML way has the advantage of being a pure XML implementation with a little trickery while the custom view is a more standard way to extend view capabilities.

Create a Styled LinearLayout Custom View

Imagine you had the following design for a card that you'd be using through an app. A title, button, a divider, and then space for dynamic content indicated by the blue box. We can add anything we'd need inside the blue region but the frame for holding the content would be consistent. For example:
Card with Placeholder Region
If I was going to put two TextViews inside it might look like this, with the (trimmed down) view layout below:
Card with Two TextViews
<com.google.android.material.card.MaterialCardView>
<LinearLayout android:orientation="vertical">
<LinearLayout android:orientation="horizontal">
<TextView android:text="My Title" />
<com.google.android.material.button.MaterialButton android:text="ACTION" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#000" />
<LinearLayout android:id="+#id/contentGoesHere">
<TextView android:text="First element" />
<TextView android:text="Second element" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
Ideally I'd like a custom view so that developers can just do the following and get the consistent stying, or add to the view programatically:
<com.customview.CustomView>
<TextView android:text="First element" />
<TextView android:text="Second element" />
</com.customview.CustomView>
My problem is that just extending LinearLayout and making a custom view wont work - it'll create the layout but there's no way to indicate that the inner LinearLayout is what I want the views to be added to, so any subviews added in the XML are ignored.
Do I need to make a custom ViewGroup and manually inflate the custom holder for the LinearLayout (help! onMeasure and onLayout!?) or is there an easier way to make a custom LinearLayout view with this styled frame around it?
My problem is that just extending LinearLayout and making a custom
view wont work - it'll create the layout but there's no way to
indicate that the inner LinearLayout is what I want the views to be
added to, so any subviews added in the XML are ignored.
You can manually move the child views into nested LinearLayout.
For example:
public class CustomView extends LinearLayout {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
final ViewGroup container = inflate(getContext(), R.layout.card_layout, this)
.findViewById(R.id.inner_content_container);
while (getChildCount() > 1) {
final View child = getChildAt(0);
removeView(child);
container.addView(child, child.getLayoutParams());
}
}
}
card_layout.xml
<com.google.android.material.card.MaterialCardView>
<LinearLayout android:orientation="vertical">
<LinearLayout android:orientation="horizontal">
<TextView android:text="My Title" />
<com.google.android.material.button.MaterialButton android:text="ACTION" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#000" />
<LinearLayout
android:id="#+id/inner_content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- content goes here -->
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
...
<com.customview.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:text="First element" />
<TextView android:text="Second element" />
</com.customview.CustomView>

Android layout: Make RadioGroup occupy screen width next to a square ImageView

I have this layout:
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioGroup
android:id="#+id/MyTabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton ... />
<RadioButton ... />
</RadioGroup>
<SquareImageView
android:layout_height="match_parent" />
</LinearLayout>
and here's the code for the SquareImageView:
public class SquareImageView extends ImageView {
public SquareImageView(Context context) {
super(context);
}
public SquareImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int size = getMeasuredHeight();//Math.min(getMeasuredWidth(), getMeasuredHeight());
setMeasuredDimension(size, size);
}
}
Now, what I want to achieve is this:
I want the RadioGroup to occupy as much space as possible. The height of the LinearLayout should adapt to the required height of the RadioGroup. Each RadioButton should occupy 50% of the RadioGroup. The SquareImageView should adapt it's height to match the height of the LinearLayout, and it's width should also adapt to make it square.
Is this even possible?
The RadioGroup is no problem on it's own, but the tricky part is the square ImageView because I cannot get it to adapt it's width automatically. It is square, but I need to set the width of it manually. If I do not set a width manually it seems to be square (in the Eclipse Preview Layout Manager) but it ends up outside of the screen and the RadioGroup occupies the entire screen width. If I set a too wide width there is dead space to the right of it.
Tried fiddling with all kinds of settings but nothing gets it quite right. I would like to avoid setting dimensions in absolute values, and rather let the height of the RadioGroup determine the rest of the Views' dimensions...
Try this:
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioGroup
android:id="#+id/MyTabs"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weig="1" >
<RadioButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
... />
<RadioButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
... />
</RadioGroup>
<SquareImageView
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
You can try below steps:
1, Set the layout as below:
<RadioGroup
android:id="#+id/MyTabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="1" >
<RadioButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.5"
... />
<RadioButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.5"
... />
</RadioGroup>
<SquareImageView
android:layout_width="0dp"
android:layout_height="match_parent" />
2, Once the view loads, programatically find the height of linear layout and then programatically set the width of the squareview to this same value to make it a square
3, Find the width of the screen and then set the width of the radio group to the difference between screen width and above sqaureview width so that it occupies the remaining screen width.

Custom view not filling parent

I have a custom view like this
public class ButtonBar extends HorizontalScrollView
{
public View mButtonRows;
public ButtonBar(Context context, AttributeSet attrs)
{
super(context, attrs);
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mButtonRows = inflater.inflate(R.layout.toolbar, null);
// button click handling code goes here
addView(mButtonRows);
}
}
which is included in my main xml like this
<com.example.ButtonBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_below="#+id/pagecontent" />
and inflates an xml file like this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/ButtonsRow"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="#+id/button1"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_weight="0.3"
android:text="button1"
/>
<Button
android:id="#+id/button2"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_weight="0.3"
android:text="button2"
/>
<Button
android:id="#+id/button3"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_weight="0.3"
android:text="button3"
/>
</LinearLayout>
(It currently only has three buttons, but more are going to be needed in later versions, hence the HorizontalScrollView.)
Looking in hierarchyviewer, the custom view does seem to be screen wide, but the LinearLayout is only as wide as the buttons it contains (about 2/3 of the screen at the current button size), despite having the fill_parent width set; the buttons don't stretch. If I set the LinearLayout's background to #android:drawable/bottom_bar (which is a png the width of the screen), the buttons properly resize; I realise I could do the same thing by creating my own images to match, but I'd much rather do it without if possible.
What am I doing wrong?
ETA: if I change HorizontalScollView to ScrollView, it works fine. Do HSVs just not allow their children to "fill_parent"?
ETA2: Setting android:fillViewport="true" in the main xml fixed it!
Setting android:fillViewport="true" in the main xml fixed it!
If you change to this, for each of the buttons, does it work?
android:layout_width="0px"
android:layout_weight="1"

Two TextViews side by side, only one to ellipsize?

I want to have two TextView elements appear side by side (in a list item), one aligned to the left, one to the right. Something like:
|<TextView> <TextView>|
(the | represent the screen's extremities)
However, the TextView on the left can have content that is too long to fit on the screen. In this case, I want to have it ellipsize but still show the entire right TextView. Something like:
|This is a lot of conte...<TextView>|
I have had numerous attempts at this, using both LinearLayout and RelativeLayout, and the only solution I have come up with is to use a RelativeLayout and put a marginRight on the left TextView big enough to clear the right TextView. As you can imagine, though, this is not optimal.
Are there any other solutions?
Final, LinearLayout solution:
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal"
>
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:inputType="text"
/>
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_weight="0"
android:layout_gravity="right"
android:inputType="text"
/>
</LinearLayout>
Old, TableLayout solution:
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"
android:shrinkColumns="0"
>
<TableRow>
<TextView android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
/>
<TextView android:id="#+id/date"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="none"
android:gravity="right"
/>
</TableRow>
</TableLayout>
Just an idea, why don't you declare first in the xml layout the textview on the right and set its width as wrap content, android:layout_alignParentRight="true" and android:gravity="right". Then declare the textview on the left, set its width as fill parent, android:layout__toLeftOf={the id of the textview on the right} having RelativeView as the root view.
By declaring first the right textview, its required width will be computed first and occupy the view while the textview on the left will occupy the remaining space of the view.
I still have not tried this though it might give you some idea.
[Update]
I tried creating an xml resource layout... and it somehow works...
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="#+id/right"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:text="right"
>
</TextView>
<TextView
android:id="#+id/left"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="#id/right"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:singleLine="true"
android:maxLines="1"
android:text="too looooooooooong ofskgjo sdogj sdkogjdfgds dskjgdsko jgleft"
>
</TextView>
</RelativeLayout>
The LinearLayout answer worked for me with this same problem. Posted as a separate answer because it wasn't clear what did and didn't work for the asker.
One difference. TableLayout was less ideal for me because I had two rows of data, and I wanted the bottom row to behave as this question describes, and the top row to span the area. That question's been answered in another SO question: Colspan in TableLayout, but LinearLayout was simpler.
Though getting the widths right took me a bit. I included the android lint tweak of using 0dp width on the scaling item for performance.
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal"
>
<TextView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:ellipsize="end"
android:inputType="text"
/>
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_weight="0"
android:layout_gravity="right"
android:inputType="text"
/>
</LinearLayout>
Use TableLayout and put both TextView in table row, have a try. I haven't tried
There are many answers to this and practically equivalent, duplicate questions on SO. The suggested approaches usually work, sort of. Putting it into a LinearLayout, wrap the whole in an extra RelativeLayout, use a TableLayout; all these seem to solve it for a simpler layout but if you need these two TextViews inside something more complicated, or the same layout will be reused, for instance, by a RecyclerView, things get broken very quickly.
The only solution I found that really works all the time, regardless of what bigger layout you put it into, is a custom layout. It's very simple to implement, and being as lean as it possibly gets, it will keep the layout reasonably flat, it's easy to maintain—so in the long run, I consider this the best solution to the problem.
public class TwoTextLayout extends ViewGroup {
public TwoTextLayout(Context context) {
super(context);
}
public TwoTextLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TwoTextLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
if (count != 2)
throw new IllegalStateException("TwoTextLayout needs exactly two children");
int childLeft = this.getPaddingLeft();
int childTop = this.getPaddingTop();
int childRight = this.getMeasuredWidth() - this.getPaddingRight();
int childBottom = this.getMeasuredHeight() - this.getPaddingBottom();
int childWidth = childRight - childLeft;
int childHeight = childBottom - childTop;
View text1View = getChildAt(0);
text1View.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
int text1Width = text1View.getMeasuredWidth();
int text1Height = text1View.getMeasuredHeight();
View text2View = getChildAt(1);
text2View.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
int text2Width = text2View.getMeasuredWidth();
int text2Height = text2View.getMeasuredHeight();
if (text1Width + text2Width > childRight)
text1Width = childRight - text2Width;
text1View.layout(childLeft, childTop, childLeft + text1Width, childTop + text1Height);
text2View.layout(childLeft + text1Width, childTop, childLeft + text1Width + text2Width, childTop + text2Height);
}
}
The implementation couldn't be simpler, it just measures the two texts (or any other child views, actually) and if their combined width exceeds the layout width, reduces the width of the first view.
And if you need modifications, eg. to align the second text to the baseline of the first, you can solve that easily, too:
text2View.layout(childLeft + text1Width, childTop + text1Height - text2Height, childLeft + text1Width + text2Width, childTop + text1Height);
Or any other solution, like shrinking the second view in relation to the first, aligning to the right, etc.
Why don't you put a left margin on the right TextView? I'm using this approach for a
|<TextView> <ImageButton>|
and it works.
Solution with ConstraintLayout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="#+id/leftText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
app:layout_constraintEnd_toStartOf="#id/rightText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="This is a lot of content that should be cut" />
<TextView
android:id="#+id/rightText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Right text" />
</androidx.constraintlayout.widget.ConstraintLayout>
When I faced with the sililar issue, I did following:
I needed:
|<TextView, may be long> <TextViewFixedSize> |
|<TextView, may be longer ...> <TextViewFixedSize>|
|<TextViewLong> <TextViewFixedSize> |
You may use a solution like this:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/layoutRecommendedServiceDescription"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="#+id/textViewRecommendedServiceTitle"
app:layout_constraintTop_toBottomOf="#id/textViewRecommendedServiceTitle">
<TextView
android:id="#+id/textViewRecommendedService1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:ellipsize="end"
android:lines="1"
android:maxLines="1"
app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintEnd_toStartOf="#+id/textViewRecommendedServicePopular"
app:layout_constraintStart_toStartOf="parent"
tools:text="Long text"
tools:visibility="visible" />
<TextView
android:id="#+id/textViewRecommendedServicePopular"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:lines="1"
android:text="#string/services_popular"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/textViewRecommendedService1"
app:layout_goneMarginStart="0dp"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

Categories

Resources