13

suppose I have the following text 'ADD TEST' inside TextView as shown below enter image description here

as you can see the text inside the TextView does not have the same width and height as TextView .

what I want is to get the x,y position of text inside the TextView

ppreetikaa
  • 1,149
  • 2
  • 15
  • 22
has19
  • 1,307
  • 16
  • 31
  • `android:includeFontPadding="false"` can remove that extra space then the x,y position of text will be same as textView – Ashwini Saini Sep 20 '18 at 16:32
  • @Ashwini Violet it tried it i didn't work – has19 Sep 20 '18 at 16:36
  • Can you tell me why your need that position.. I'm just curious if you don't mind – Ashwini Saini Sep 20 '18 at 16:38
  • @Ashwini Violet ,i want to send the x.y position of text to the server so i can draw them again from the serve side at the same position .So i either need the text to be exactly the same as textView (better solution) or the x,y position of text inside it . – has19 Sep 20 '18 at 16:41
  • @ Ashwini Violet it looks like android:includeFontPadding="false" decreased the height of the textview but it still not the same – has19 Sep 20 '18 at 16:45
  • `android:includeFontPadding="false" android:scaleY="1.5"` use these two..it can help you...see if this is what you want @has19 – Ashwini Saini Sep 20 '18 at 17:09
  • @AshwiniViolet although its better but its still not exactly the same ,i don't think you can do it like that – has19 Sep 20 '18 at 17:54
  • Yes i know that's way it's a comment not an answer... Let's see if someone come with more bright idea @has19 – Ashwini Saini Sep 20 '18 at 17:55
  • @Ashwini Violet haha i know man ,thx anyway – has19 Sep 20 '18 at 18:37

3 Answers3

12

Take a look at a couple of Paint methods: getTextBounds() and measureText. We can use these to determine the offset of the text within the TextView. Once the offset within the TextView is determined, we can add that to the location of the TextView itself to determine the screen coordinates of the text if that is desired.

I have also found the article "Android 101: Typography" to be useful in understanding some of the complexities of typography.

The following example finds the bounds of the text within three TextViews and draws a rectangle around the text. The rectangle contains the (x, y) coordinates of the text within the TextView.

activity_main.xml
A simple layout for demonstration.

<android.support.constraint.ConstraintLayout
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:background="@android:color/holo_blue_light"
        android:padding="24dp"
        android:text="Hello World"
        android:textColor="@android:color/black"
        android:textSize="50sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:background="@android:color/holo_blue_light"
        android:padding="24dp"
        android:text="Hello Worldly"
        android:textColor="@android:color/black"
        android:textSize="50sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textView1" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:background="@android:color/holo_blue_light"
        android:padding="24dp"
        android:text="aaaaaaaaaa"
        android:textColor="@android:color/black"
        android:textSize="50sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textView2" />

</android.support.constraint.ConstraintLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawTextBounds((TextView) findViewById(R.id.textView1));
        drawTextBounds((TextView) findViewById(R.id.textView2));
        drawTextBounds((TextView) findViewById(R.id.textView3));
    }

    private void drawTextBounds(TextView textView) {
        // Force measure of text pre-layout.
        textView.measure(0, 0);
        String s = (String) textView.getText();

        // bounds will store the rectangle that will circumscribe the text.
        Rect bounds = new Rect();
        Paint textPaint = textView.getPaint();

        // Get the bounds for the text. Top and bottom are measured from the baseline. Left
        // and right are measured from 0.
        textPaint.getTextBounds(s, 0, s.length(), bounds);
        int baseline = textView.getBaseline();
        bounds.top = baseline + bounds.top;
        bounds.bottom = baseline + bounds.bottom;
        int startPadding = textView.getPaddingStart();
        bounds.left += startPadding;

        // textPaint.getTextBounds() has already computed a value for the width of the text, 
        // however, Paint#measureText() gives a more accurate value.
        bounds.right = (int) textPaint.measureText(s, 0, s.length()) + startPadding;

        // At this point, (x, y) of the text within the TextView is (bounds.left, bounds.top)
        // Draw the bounding rectangle.
        Bitmap bitmap = Bitmap.createBitmap(textView.getMeasuredWidth(),
                                            textView.getMeasuredHeight(),
                                            Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint rectPaint = new Paint();
        rectPaint.setColor(Color.RED);
        rectPaint.setStyle(Paint.Style.STROKE);
        rectPaint.setStrokeWidth(1);
        canvas.drawRect(bounds, rectPaint);
        textView.setForeground(new BitmapDrawable(getResources(), bitmap));
    }
}

enter image description here

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
3

Y value

You can use textView.getTextSize() or textView.getPaint().getTextSize() to get the actual used text size in pixels (as Float).

Next, we need the total height of the text view, which we can find as follows:

textView.measure(0, 0); // We must call this to let it calculate the heights
int height = textView.getMeasuredHeight();

However, the final size that we need can also have decimals. So lets make it a float for more precision:

float totalHeight = (float) height;

Now that we know the values, we can calculate the y value of the text inside the view:

// The spacing between the views is `totalHeight - textSize`
// We have a spacing at the top and the bottom, so we divide it by 2
float yValue = (totalHeight - textSize) / 2

X value

Furthermore, the xValue is just the x value of the text view itself when using android:includeFontPadding="false".

Alex
  • 1,650
  • 16
  • 15
0

Here is the solution I came up with, it supports multiple lines text and also changing the gravity attribute. Below is the result image and the source code:

enter image description here

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView


class CustomTextView : AppCompatTextView {

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    val textPaint: Paint = Paint().apply {
        isAntiAlias = true
        style = Paint.Style.STROKE
        strokeWidth = 2f
    }

    val bounds = ArrayList<Rect>()
    val fullBounds = Rect()

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        getBound()
    }

    private fun getBound() {

        text.toString().let { string ->

            val lines = string.split("\n")
            var offset = 0

            lines.forEachIndexed { i, str ->

                // replace all tabs with _ char for measuring
                val s = str.replace('\t', '_')

                // get horizontal bound for each line
                val boundHorizontal = Rect()
                paint.getTextBounds(s, 0, s.length, boundHorizontal)
                boundHorizontal.offset(
                    paddingStart + (layout?.getPrimaryHorizontal(offset)?.toInt() ?: 0),
                    0
                )

                // get vertical bound for each line
                val boundVertical = Rect()
                getLineBounds(i, boundVertical)
                boundVertical.apply {
                    left = boundHorizontal.left
                    right = boundHorizontal.right
                }
                bounds.add(boundVertical)

                offset += (s.length + 1)
            }

            bounds.forEachIndexed { i, rect ->
                if (i == 0) {
                    fullBounds.set(rect)
                }
                fullBounds.intersectUnchecked(rect)
            }
        }
    }

    override fun onDraw(canvas: Canvas) {

        canvas.drawRect(fullBounds, textPaint.apply { color = Color.YELLOW })

        bounds.forEach {
            canvas.drawRect(it, textPaint.apply { color = Color.MAGENTA })
        }

        super.onDraw(canvas)
    }

    companion object {

        fun Rect.intersectUnchecked(other: Rect) {
            if (other.left < left) left = other.left
            if (other.right > right) right = other.right
            if (other.top < top) top = other.top
            if (other.bottom > bottom) bottom = other.bottom
        }
    }
}

And then add your custom view via XML:

<com.slaviboy.universaldictionarybg.ApostropheTextView
        android:layout_width="150dp"
        android:layout_height="200dp"
        android:background="#41BADF"
        android:gravity="end|bottom"
        android:includeFontPadding="false"
        android:lineSpacingExtra="0dp"
        android:text="This\nis\nmultiline\ntest :D"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
slaviboy
  • 1,407
  • 17
  • 27