1

I have to build a layout like this:

I'm trying to build a layout like this

There are two TextView, the blue and the black one.

String inside "black" TextView should be placed after "blue" TextView and if the text is too long jump down like if it was constrained also to the right of the image. I can't use SpannableString and stuff because the "blue" TextView may be an ImageView in some cases.

I'm trying to use ConstraintLayout which I think is the most completed layout we have, but I can not figure it out.

I will appreciate if someone can show some component, approach or workaround to solve this. Thanks!

----- EDITED ---- Another example:

enter image description here

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
Ricard
  • 1,223
  • 1
  • 13
  • 17
  • Can you explain in detail why SpannableString was useless? – Rustam Samandarov Nov 02 '20 at 20:37
  • Can not be only one TextView, because the "blue" one can be an ImageView in some cases so the "black" TextView behaviour should be the same independent of the first View is anchored . Thanks! – Ricard Nov 02 '20 at 20:42

2 Answers2

3

The easiest solution would be to use a span to place the black text within the TextView of the blue text. If the black text is within an ImageView then constrain the black text ImageView to the leftmost ImageView. When you have black text which is not in an ImageView, make the ImageView's visibility equal to "gone".

But let's assume that you have two TextViews with text that cannot be combined.

Case 1 - Two TextViews

In addition to the leftmost ImageView, there are two TextViews. One holds blue text and one holds black text. The goal is to make these two TextViews appear like they are part of a single TextView such that the black text immediately follows the blue text.

To do this, position the black TextView to completely overlay the blue TextView. This can be easily accomplished with a ConstraintLayout.

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginStart="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/dandelion_flower" />

    <TextView
        android:id="@+id/textBlue"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="Blue text that spane more than one line of its TextView."
        android:textColor="@android:color/holo_blue_bright"
        app:layout_constrainedWidth="true"
        app:layout_constraintBottom_toBottomOf="@id/imageView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/imageView"
        app:layout_constraintTop_toTopOf="@+id/imageView" />

    <TextView
        android:id="@+id/textBlack"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="This is some black text"
        android:textColor="@android:color/black"
        app:layout_constrainedWidth="true"
        app:layout_constraintBottom_toBottomOf="@+id/imageView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/imageView"
        app:layout_constraintTop_toTopOf="@+id/imageView" />

</androidx.constraintlayout.widget.ConstraintLayout>

Without adjustment, the black text will just appear on top of the blue text. The goal now is to pad out the blue text TextView will enough space so that it moves to the end of the blue text. We can do this by adjusting the black text with newline characters (to move it down) and space characters (to move it over). One place to do this is in the onCreate() function. (See comments in the following code.)

// Wait for the layout to complete before getting measurements.
findViewById<TextView>(R.id.textBlue).doOnNextLayout {
    val blueText = it as TextView
    val blackText = findViewById<TextView>(R.id.textBlack)
    // The TextViews have been laid out. Determine how many lines are used for the blue text.
    val blueLayout = blueText.layout
    val blueLineCount = blueText.layout.lineCount
    // And the width in pixels of the last line.
    val lastLineWidth = blueLayout.getLineWidth(blueLineCount - 1)
    // We will skip over the full lines with newline characters and pad out the last line
    // with spaces.
    val newLines = "\n".repeat(blueLineCount - 1)
    val spaceWidth = blackText.paint.measureText(" ")
    val padChars = " ".repeat((lastLineWidth / spaceWidth).toInt() + 1)
    blackText.text = "$newLines$padChars${blackText.text}"
}

Here is the result:

enter image description here

You may need to make some adjustments to the views to make this work, but this is the general idea.

Case 2 - One TextView and an ImageView with Text

In this case, the ImageView with text is constrained to the other ImageView and positioned below the TextView. How this is accomplished depends on your requirements.

One layout can be used for both cases. In case #1, the second ImageView would have its visibility set to "gone" or "invisible". In case #2, the black text TextView would have its visibility set to "gone" or "invisible".

I originally thought that a LeadingMarginSpan would be the way to go, but it runs into difficulties if the black text should start on any line but the first.


If you want to flow text around an ImageView this Stack Overflow question and answer(s) should help.

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • Thanks a lot for your response! Case #1 is really original and works as expected! The problem comes in Case #2, when you already noticed it's not easy (or even imposible) anchor `TextView` to the left one view and if the text is too long jump below that view... Did you see my second screeshot of my Question? You can see better what I need. Thanks! – Ricard Nov 03 '20 at 16:47
  • @Ricard I have the black text as potentially an ImageView and not the blue as you do state.. I will correct that. I am unclear on what the second example is representing. Which part is a TextView and which the ImageView? If you are wanting the text of an ImageView to flow like text in a TextView then, I believe, that you are out of luck. – Cheticamp Nov 03 '20 at 18:28
  • sorry if I explained bad. In the second image I posted, the "comma" is an ImageView and the text a TextView, the text inside TextView starts at the end of the ImageView and goes below when it can without covering ImageView (the comma). – Ricard Nov 04 '20 at 16:24
  • 1
    @Ricard Let's see if I understand: The photo is one ImageView and the comma is another separate ImageView. The name is one TextView and the rest of the text is another TextView. Is that right? – Cheticamp Nov 04 '20 at 17:00
  • Yes, that's it! – Ricard Nov 05 '20 at 09:39
  • 1
    @Ricard See the last line of the answer. I think that is what can help. You will also need to make adjustments to the views depending upon whether the second ImageView is in use or not probably through the visibility property. – Cheticamp Nov 05 '20 at 11:35
0

Hi try Google's FlexboxLayout with flexWrap attribute set to wrap

  • Thanks for the option, but works half. I have been playing with `FlexBoxLayout` but I only achieve make "black" `TextView` jump below entirely, but not like the image I posted. I need the second line jump below not the entire `TextView` – Ricard Nov 02 '20 at 22:48
  • I added another example of what I need to achieve In my question! – Ricard Nov 02 '20 at 22:57