Android studio как изменить размер textview текста

Is there any way in android to adjust the textsize in a textview to fit the space it occupies? E.g. I'm using a TableLayout and adding several TextViews to each row. Since I don't want the TextVie...

The solution below incorporates all of the suggestions here. It starts with what was originally posted by Dunni. It uses a binary search like gjpc’s, but it is a bit more readable. It also include’s gregm’s bug fixes and a bug-fix of my own.

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
        initialise();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialise();
    }

    private void initialise() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) 
    { 
        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        float hi = 100;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            mTestPaint.setTextSize(size);
            if(mTestPaint.measureText(text) >= targetWidth) 
                hi = size; // too big
            else
                lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
        if (w != oldw) {
            refitText(this.getText().toString(), w);
        }
    }

    //Attributes
    private Paint mTestPaint;
}

answered Oct 24, 2011 at 12:32

speedplane's user avatar

speedplanespeedplane

15.4k16 gold badges86 silver badges137 bronze badges

17

I’ve written a class that extends TextView and does this. It just uses measureText as you suggest. Basically it has a maximum text size and minimum text size (which can be changed) and it just runs through the sizes between them in decrements of 1 until it finds the biggest one that will fit. Not particularly elegant, but I don’t know of any other way.

Here is the code:

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
        initialise();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialise();
    }

    private void initialise() {
        testPaint = new Paint();
        testPaint.set(this.getPaint());
        //max size defaults to the intially specified text size unless it is too small
        maxTextSize = this.getTextSize();
        if (maxTextSize < 11) {
            maxTextSize = 20;
        }
        minTextSize = 10;
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) { 
        if (textWidth > 0) {
            int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
            float trySize = maxTextSize;

            testPaint.setTextSize(trySize);
            while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth)) {
                trySize -= 1;
                if (trySize <= minTextSize) {
                    trySize = minTextSize;
                    break;
                }
                testPaint.setTextSize(trySize);
            }

            this.setTextSize(trySize);
        }
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
        if (w != oldw) {
            refitText(this.getText().toString(), w);
        }
    }

    //Getters and Setters
    public float getMinTextSize() {
        return minTextSize;
    }

    public void setMinTextSize(int minTextSize) {
        this.minTextSize = minTextSize;
    }

    public float getMaxTextSize() {
        return maxTextSize;
    }

    public void setMaxTextSize(int minTextSize) {
        this.maxTextSize = minTextSize;
    }

    //Attributes
    private Paint testPaint;
    private float minTextSize;
    private float maxTextSize;

}

answered Jul 31, 2010 at 14:04

dunni's user avatar

dunnidunni

4084 silver badges12 bronze badges

3

This is speedplane’s FontFitTextView, but it only decreases font size if needed to make the text fit, and keeps its font size otherwise. It does not increase the font size to fit height.

public class FontFitTextView extends TextView {

    // Attributes
    private Paint mTestPaint;
    private float defaultTextSize;

    public FontFitTextView(Context context) {
        super(context);
        initialize();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    private void initialize() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        defaultTextSize = getTextSize();
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) {

        if (textWidth <= 0 || text.isEmpty())
            return;

        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();

        // this is most likely a non-relevant call
        if( targetWidth<=2 )
            return;

        // text already fits with the xml-defined font size?
        mTestPaint.set(this.getPaint());
        mTestPaint.setTextSize(defaultTextSize);
        if(mTestPaint.measureText(text) <= targetWidth) {
            this.setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
            return;
        }

        // adjust text size using binary search for efficiency
        float hi = defaultTextSize;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be
        while (hi - lo > threshold) {
            float size = (hi + lo) / 2;
            mTestPaint.setTextSize(size);
            if(mTestPaint.measureText(text) >= targetWidth ) 
                hi = size; // too big
            else 
                lo = size; // too small

        }

        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            refitText(this.getText().toString(), w);
        }
    }

}

Here is an example how it could be used in xml:

<com.your.package.activity.widget.FontFitTextView
    android:id="@+id/my_id"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="My Text"
    android:textSize="60sp" />

This would keep the font size to 60sp as long as the text fits in width. If the text is longer, it will decrease font size. In this case, the TextViews height will also change because of height=wrap_content.

If you find any bugs, feel free to edit.

Community's user avatar

answered Oct 24, 2012 at 9:47

sulai's user avatar

sulaisulai

5,1442 gold badges27 silver badges44 bronze badges

10

Here is my solution which works on emulator and phones but not very well on Eclipse layout editor. It’s inspired from kilaka’s code but the size of the text is not obtained from the Paint but from measuring the TextView itself calling measure(0, 0).

The Java class :

public class FontFitTextView extends TextView
{
    private static final float THRESHOLD = 0.5f;

    private enum Mode { Width, Height, Both, None }

    private int minTextSize = 1;
    private int maxTextSize = 1000;

    private Mode mode = Mode.None;
    private boolean inComputation;
    private int widthMeasureSpec;
    private int heightMeasureSpec;

    public FontFitTextView(Context context) {
            super(context);
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
    }

    public FontFitTextView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);

            TypedArray tAttrs = context.obtainStyledAttributes(attrs, R.styleable.FontFitTextView, defStyle, 0);
            maxTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_maxTextSize, maxTextSize);
            minTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_minTextSize, minTextSize);
            tAttrs.recycle();
    }

    private void resizeText() {
            if (getWidth() <= 0 || getHeight() <= 0)
                    return;
            if(mode == Mode.None)
                    return;

            final int targetWidth = getWidth();
            final int targetHeight = getHeight();

            inComputation = true;
            float higherSize = maxTextSize;
            float lowerSize = minTextSize;
            float textSize = getTextSize();
            while(higherSize - lowerSize > THRESHOLD) {
                    textSize = (higherSize + lowerSize) / 2;
                    if (isTooBig(textSize, targetWidth, targetHeight)) {
                            higherSize = textSize; 
                    } else {
                            lowerSize = textSize;
                    }
            }
            setTextSize(TypedValue.COMPLEX_UNIT_PX, lowerSize);
            measure(widthMeasureSpec, heightMeasureSpec);
            inComputation = false;
    }

    private boolean isTooBig(float textSize, int targetWidth, int targetHeight) {
            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
            measure(0, 0);
            if(mode == Mode.Both)
                    return getMeasuredWidth() >= targetWidth || getMeasuredHeight() >= targetHeight;
            if(mode == Mode.Width)
                    return getMeasuredWidth() >= targetWidth;
            else
                    return getMeasuredHeight() >= targetHeight;
    }

    private Mode getMode(int widthMeasureSpec, int heightMeasureSpec) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY)
                    return Mode.Both;
            if(widthMode == MeasureSpec.EXACTLY)
                    return Mode.Width;
            if(heightMode == MeasureSpec.EXACTLY)
                    return Mode.Height;
            return Mode.None;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if(!inComputation) {
                    this.widthMeasureSpec = widthMeasureSpec;
                    this.heightMeasureSpec = heightMeasureSpec;
                    mode = getMode(widthMeasureSpec, heightMeasureSpec);
                    resizeText();
            }
    }

    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
            resizeText();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            if (w != oldw || h != oldh)
                    resizeText();
    }

    public int getMinTextSize() {
            return minTextSize;
    }

    public void setMinTextSize(int minTextSize) {
            this.minTextSize = minTextSize;
            resizeText();
    }

    public int getMaxTextSize() {
            return maxTextSize;
    }

    public void setMaxTextSize(int maxTextSize) {
            this.maxTextSize = maxTextSize;
            resizeText();
    }
}

The XML attribute file :

<resources>
    <declare-styleable name="FontFitTextView">
        <attr name="minTextSize" format="dimension" />
        <attr name="maxTextSize" format="dimension" />
    </declare-styleable>
</resources>

Check my github for the latest version of this class.
I hope it can be useful for someone.
If a bug is found or if the code needs explaination, feel free to open an issue on Github.

answered Nov 4, 2012 at 15:13

yDelouis's user avatar

yDelouisyDelouis

2,09417 silver badges18 bronze badges

3

Thanks a lot to https://stackoverflow.com/users/234270/speedplane. Great answer!

Here is an improved version of his response that also take care of height and comes with a maxFontSize attribute to limit font size (was useful in my case, so I wanted to share it) :

package com.<your_package>;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;


public class FontFitTextView extends TextView
{

    private Paint mTestPaint;
    private float maxFontSize;
    private static final float MAX_FONT_SIZE_DEFAULT_VALUE = 20f;

    public FontFitTextView(Context context)
    {
        super(context);
        initialise(context, null);
    }

    public FontFitTextView(Context context, AttributeSet attributeSet)
    {
        super(context, attributeSet);
        initialise(context, attributeSet);
    }

    public FontFitTextView(Context context, AttributeSet attributeSet, int defStyle)
    {
        super(context, attributeSet, defStyle);
        initialise(context, attributeSet);
    }

    private void initialise(Context context, AttributeSet attributeSet)
    {
        if(attributeSet!=null)
        {
            TypedArray styledAttributes = context.obtainStyledAttributes(attributeSet, R.styleable.FontFitTextView);
            maxFontSize = styledAttributes.getDimension(R.styleable.FontFitTextView_maxFontSize, MAX_FONT_SIZE_DEFAULT_VALUE);
            styledAttributes.recycle();
        }
        else
        {
            maxFontSize = MAX_FONT_SIZE_DEFAULT_VALUE;
        }

        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth, int textHeight)
    {
        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
        float hi = maxFontSize;
        float lo = 2;
//      final float threshold = 0.5f; // How close we have to be
        final float threshold = 1f; // How close we have to be

        mTestPaint.set(this.getPaint());

        Rect bounds = new Rect();

        while ((hi - lo) > threshold)
        {
            float size = (hi + lo) / 2;
            mTestPaint.setTextSize(size);

            mTestPaint.getTextBounds(text, 0, text.length(), bounds);

            if (bounds.width() >= targetWidth || bounds.height() >= targetHeight)
                hi = size; // too big
            else
                lo = size; // too small

//          if (mTestPaint.measureText(text) >= targetWidth)
//              hi = size; // too big
//          else
//              lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth, height);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after)
    {
        refitText(text.toString(), this.getWidth(), this.getHeight());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        if (w != oldw)
        {
            refitText(this.getText().toString(), w, h);
        }
    }
}

Corresponding /res/values/attr.xml file:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="FontFitTextView">
        <attr name="maxFontSize" format="dimension" />
    </declare-styleable>

</resources>

Example:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:res-auto="http://schemas.android.com/apk/res-auto"
    android:id="@+id/home_Layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/background"
    tools:ignore="ContentDescription" >
...

 <com.<your_package>.FontFitTextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:singleLine="true"
                    android:text="Sample Text"
                    android:textSize="28sp"
                    res-auto:maxFontSize="35sp"/>

...
</RelativeLayout>

To use the new maxFontSize attribute, don’t forget to add xmlns:res-auto="http://schemas.android.com/apk/res-auto" as show in the example.

Community's user avatar

answered Mar 10, 2013 at 14:02

Pascal's user avatar

PascalPascal

14k2 gold badges48 silver badges64 bronze badges

6

You can now do this without a third party library or a widget. It’s built into TextView in API level 26. Add android:autoSizeTextType="uniform" to your TextView and set height to it. That’s all.

https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html

<?xml version="1.0" encoding="utf-8"?>
<TextView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:autoSizeTextType="uniform" />

You can also use TextViewCompat or app:autoSizeTextType="uniform" for backward compatibility.

answered Oct 17, 2017 at 20:02

arsent's user avatar

arsentarsent

6,8242 gold badges32 silver badges31 bronze badges

1

I had the same problem and wrote a class that seems to work for me. Basically, I used a static layout to draw the text in a separate canvas and remeasure until I find a font size that fits. You can see the class posted in the topic below. I hope it helps.

Auto Scale TextView Text to Fit within Bounds

Community's user avatar

answered Apr 4, 2011 at 7:35

Chase's user avatar

ChaseChase

11.1k8 gold badges42 silver badges39 bronze badges

Use app:autoSizeTextType="uniform" for backward compatibility because android:autoSizeTextType="uniform" only work in API Level 26 and higher.

answered Aug 27, 2018 at 10:19

Suraj Vaishnav's user avatar

Suraj VaishnavSuraj Vaishnav

7,4374 gold badges42 silver badges46 bronze badges

3

Slight modification to onMeasure:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
    int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
    refitText(this.getText().toString(), parentWidth);
    this.setMeasuredDimension(parentWidth, parentHeight);
}

And binary search on refitText:

private void refitText(String text, int textWidth) 
{ 
    if (textWidth > 0) 
    {
        int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();         
        int trySize = (int)maxTextSize;
        int increment = ~( trySize - (int)minTextSize ) / 2;

        testPaint.setTextSize(trySize);
        while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth)) 
        {
            trySize += increment;
            increment = ( increment == 0 ) ? -1 : ~increment / 2;
            if (trySize <= minTextSize) 
            {
                trySize = (int)minTextSize;
                break;
            }
            testPaint.setTextSize(trySize);
        }

        this.setTextSize( TypedValue.COMPLEX_UNIT_PX, trySize);
    }
}

answered Feb 15, 2011 at 16:36

gjpc's user avatar

gjpcgjpc

1,41814 silver badges21 bronze badges

1

I found the following to work nicely for me. It doesn’t loop and accounts for both height and width. Note that it is important to specify the PX unit when calling setTextSize on the view. Thanks to the tip in a previous post for this!

Paint paint = adjustTextSize(getPaint(), numChars, maxWidth, maxHeight);
setTextSize(TypedValue.COMPLEX_UNIT_PX,paint.getTextSize());

Here is the routine I use, passing in the getPaint() from the view. A 10 character string with a ‘wide’ character is used to estimate the width independent from the actual string.

private static final String text10="OOOOOOOOOO";
public static Paint adjustTextSize(Paint paint, int numCharacters, int widthPixels, int heightPixels) {
    float width = paint.measureText(text10)*numCharacters/text10.length();
    float newSize = (int)((widthPixels/width)*paint.getTextSize());
    paint.setTextSize(newSize);

    // remeasure with font size near our desired result
    width = paint.measureText(text10)*numCharacters/text10.length();
    newSize = (int)((widthPixels/width)*paint.getTextSize());
    paint.setTextSize(newSize);

    // Check height constraints
    FontMetricsInt metrics = paint.getFontMetricsInt();
    float textHeight = metrics.descent-metrics.ascent;
    if (textHeight > heightPixels) {
        newSize = (int)(newSize * (heightPixels/textHeight));
        paint.setTextSize(newSize);
    }

    return paint;
}

answered Sep 2, 2011 at 19:32

Glenn's user avatar

GlennGlenn

1,9362 gold badges22 silver badges30 bronze badges

3

Works with modification

You need to set the text view size like this because otherwise setTextSize assumes the value is in SP units:

setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);

And you needed to explicitly add this code.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
    int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
    refitText(this.getText().toString(), parentWidth);
}

Grantland Chew's user avatar

answered Dec 10, 2010 at 19:32

gregm's user avatar

gregmgregm

11.9k7 gold badges55 silver badges77 bronze badges

I had this pain in my projects for soooo long until I found this library:

compile 'me.grantland:autofittextview:0.2.+'

You just need to add the xml by your needs and it’s done. For example:

<me.grantland.widget.AutofitTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:maxLines="2"
android:textSize="40sp"
autofit:minTextSize="16sp"
/>

answered Jul 3, 2017 at 13:38

Giedrius Šlikas's user avatar

I used a variation of Dunni solution above, but that particular code didn’t work for me. In particular, when trying to use the Paint object set to have the traits of the view’s Paint object, and then calling measureText(), it doesn’t return the same value as directly calling the view’s Paint object. Perhaps there are some differences in the way my views are set up that make the behavior different.

My solution was to directly use the view’s Paint, even though there might be some performance penalties in changing the font size for the view multiple times.

answered Nov 19, 2010 at 9:56

ThomasW's user avatar

ThomasWThomasW

16.8k4 gold badges78 silver badges106 bronze badges

I’ve been working on improving the excellent solution from speedplane, and came up with this. It manages the height, including setting the margin such that the text should be centered correctly vertically.

This uses the same function to get the width, as it seems to work the best, but it uses a different function to get the height, as the height isn’t provided anywhere. There are some corrections that need to be made, but I figured out a way to do that, while looking pleasing to the eye.

import android.content.Context;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
        initialize();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    private void initialize() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());

        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth,int textHeight) 
    { 
        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
        float hi = Math.min(targetHeight,100);
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be

        Rect bounds = new Rect();

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            mTestPaint.setTextSize(size);
            mTestPaint.getTextBounds(text, 0, text.length(), bounds);
            if((mTestPaint.measureText(text)) >= targetWidth || (1+(2*(size+(float)bounds.top)-bounds.bottom)) >=targetHeight) 
                hi = size; // too big
            else
                lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX,(float) lo);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth,height);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth(),this.getHeight());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {

        if (w != oldw) {
            refitText(this.getText().toString(), w,h);
        }
    }

    //Attributes
    private Paint mTestPaint;
}

answered Nov 23, 2012 at 2:31

PearsonArtPhoto's user avatar

PearsonArtPhotoPearsonArtPhoto

38.4k17 gold badges112 silver badges141 bronze badges

Inspired by the previous posters I wanted to share my solution. It works with a scale factor which is applied to the previous font size to make it fit the available space. In addition to prevent unexpected behaviour of TextViews onDraw method, it simply draws the text on its own.

public class FontFitTextView extends TextView {

    // How much of the available space should be used in percent.
    private static final float MARGINHEIGHT = 0.8f;
    private static final float MARGINWIDTH = 0.8f;

    private Paint paint;
    private int viewWidth;
    private int viewHeight;
    private float textHeight;
    private float textWidth;

    public FontFitTextView(Context c) {
        this(c, null);
    }

    public FontFitTextView(Context c, AttributeSet attrs) {
        super(c, attrs);
        initComponent();
    }

    // Default constructor override
    public FontFitTextView(Context c, AttributeSet attrs, int defStyle) {
        super(c, attrs, defStyle);
        initComponent();
    }

    private void initComponent() {
        paint = new Paint();
        paint.setTextSize(30);
        paint.setTextAlign(Align.CENTER);
        paint.setAntiAlias(true);
    }

    public void setFontColor(int c) {
        paint.setColor(c);
    }

    private void calcTextSize(String s, Canvas c) {

        float availableHeight = viewHeight;
        float availableWidth = viewWidth;

        // This value scales the old font up or down to match the available
        // space.
        float scale = 1.0f;

        // Rectangle for measuring the text dimensions
        Rect rect = new Rect();
        float oldFontSize = paint.getTextSize();

        // Calculate the space used with old font size
        paint.getTextBounds(s, 0, s.length(), rect);
        textWidth = rect.width();
        textHeight = rect.height();

        // find scale-value to fit the text horizontally
        float scaleWidth = 1f;
        if (textWidth > 0.0f) {
            scaleWidth = (availableWidth) / textWidth * MARGINWIDTH;
        }

        // find scale-value to fit the text vertically
        float scaleHeight = 1f;
        if (textHeight > 0.0f) {
            scaleHeight = (availableHeight) / textHeight * MARGINHEIGHT;
        }

        // We are always limited by the smaller one
        if (scaleWidth < scaleHeight) {
            scale = scaleWidth;
        } else {
            scale = scaleHeight;
        }

        // We apply the scale to the old font size to make it bigger or smaller
        float newFontSize = (oldFontSize * scale);
        paint.setTextSize(newFontSize);
    }

    /**
     * Calculates the origin on the Y-Axis (width) for the text in this view.
     * 
     * @return
     */
    private float calcStartDrawingPosX() {
        float left = getMeasuredWidth();
        float centerY = left - (viewWidth / 2);
        return centerY;
    }

    /**
     * Calculates the origin on the Y-Axis (height) for the text in this view.
     * 
     * @return
     */
    private float calcStartDrawingPosY() {
        float bottom = getMeasuredHeight();
        // The paint only centers horizontally, origin on the Y-Axis stays at
        // the bottom, thus we have to lift the origin additionally by the
        // height of the font.
        float centerX = bottom - (viewHeight / 2) + (textHeight / 2);
        return centerX;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        String text = getText().toString();
        if (text.length() > 0) {
            calcTextSize(text, canvas);
            canvas.drawText(text, calcStartDrawingPosX(),
                    calcStartDrawingPosY(), paint);
        }
    };

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        viewWidth = w;
        viewHeight = h;
        super.onSizeChanged(w, h, oldw, oldh);
    }
}

answered Aug 27, 2012 at 11:11

unSinn's user avatar

unSinnunSinn

3013 silver badges8 bronze badges

0

/* get your context */
Context c = getActivity().getApplicationContext();

LinearLayout l = new LinearLayout(c);
l.setOrientation(LinearLayout.VERTICAL);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0);

l.setLayoutParams(params);
l.setBackgroundResource(R.drawable.border);

TextView tv=new TextView(c);
tv.setText(" your text here");

/* set typeface if needed */
Typeface tf = Typeface.createFromAsset(c.getAssets(),"fonts/VERDANA.TTF");  
tv.setTypeface(tf);

// LayoutParams lp = new LayoutParams();

tv.setTextColor(Color.parseColor("#282828"));

tv.setGravity(Gravity.CENTER | Gravity.BOTTOM);
//  tv.setLayoutParams(lp);

tv.setTextSize(20);
l.addView(tv);

return l;

sulai's user avatar

sulai

5,1442 gold badges27 silver badges44 bronze badges

answered Mar 15, 2013 at 11:47

Dhwanik Gandhi's user avatar

1

This should be a simple solution:

public void correctWidth(TextView textView, int desiredWidth)
{
    Paint paint = new Paint();
    Rect bounds = new Rect();

    paint.setTypeface(textView.getTypeface());
    float textSize = textView.getTextSize();
    paint.setTextSize(textSize);
    String text = textView.getText().toString();
    paint.getTextBounds(text, 0, text.length(), bounds);

    while (bounds.width() > desiredWidth)
    {
        textSize--;
        paint.setTextSize(textSize);
        paint.getTextBounds(text, 0, text.length(), bounds);
    }

    textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}

answered Sep 23, 2013 at 11:51

Hamzeh Soboh's user avatar

Hamzeh SobohHamzeh Soboh

7,4845 gold badges41 silver badges54 bronze badges

Extend TextView and override onDraw with the code below. It will keep text aspect ratio but size it to fill the space. You could easily modify code to stretch if necessary.

  @Override
  protected void onDraw(@NonNull Canvas canvas) {
    TextPaint textPaint = getPaint();
    textPaint.setColor(getCurrentTextColor());
    textPaint.setTextAlign(Paint.Align.CENTER);
    textPaint.drawableState = getDrawableState();

    String text = getText().toString();
    float desiredWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2;
    float desiredHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2;
    float textSize = textPaint.getTextSize();

    for (int i = 0; i < 10; i++) {
      textPaint.getTextBounds(text, 0, text.length(), rect);
      float width = rect.width();
      float height = rect.height();

      float deltaWidth = width - desiredWidth;
      float deltaHeight = height - desiredHeight;

      boolean fitsWidth = deltaWidth <= 0;
      boolean fitsHeight = deltaHeight <= 0;

      if ((fitsWidth && Math.abs(deltaHeight) < 1.0)
          || (fitsHeight && Math.abs(deltaWidth) < 1.0)) {
        // close enough
        break;
      }

      float adjustX = desiredWidth / width;
      float adjustY = desiredHeight / height;

      textSize = textSize * (adjustY < adjustX ? adjustY : adjustX);

      // adjust text size
      textPaint.setTextSize(textSize);
    }
    float x = desiredWidth / 2f;
    float y = desiredHeight / 2f - rect.top - rect.height() / 2f;
    canvas.drawText(text, x, y, textPaint);
  }

answered Dec 8, 2014 at 3:50

Greg Bacchus's user avatar

Greg BacchusGreg Bacchus

2,1172 gold badges21 silver badges26 bronze badges

I wrote a short helper class that makes a textview fit within a certain width and adds ellipsize «…» at the end if the minimum textsize cannot be achieved.

Keep in mind that it only makes the text smaller until it fits or until the minimum text size is reached. To test with large sizes, set the textsize to a large number before calling the help method.

It takes Pixels, so if you are using values from dimen, you can call it like this:


float minTextSizePx = getResources().getDimensionPixelSize(R.dimen.min_text_size);
float maxTextWidthPx = getResources().getDimensionPixelSize(R.dimen.max_text_width);
WidgetUtils.fitText(textView, text, minTextSizePx, maxTextWidthPx);

This is the class I use:


public class WidgetUtils {

    public static void fitText(TextView textView, String text, float minTextSizePx, float maxWidthPx) {
        textView.setEllipsize(null);
        int size = (int)textView.getTextSize();
        while (true) {
            Rect bounds = new Rect();
            Paint textPaint = textView.getPaint();
            textPaint.getTextBounds(text, 0, text.length(), bounds);
            if(bounds.width() < maxWidthPx){
                break;
            }
            if (size <= minTextSizePx) {
                textView.setEllipsize(TextUtils.TruncateAt.END);
                break;
            }
            size -= 1;
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
        }
    }
}

answered Jul 24, 2015 at 14:04

Björn Kechel's user avatar

Björn KechelBjörn Kechel

7,5983 gold badges52 silver badges57 bronze badges

1

If a tranformation like allCaps is set, speedplane’s approach is buggy. I fixed it, resulting in the following code (sorry, my reputation does not allow me to add this as a comment to speedplane’s solution):

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
        initialise();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialise();
    }

    private void initialise() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) 
    { 
        if (getTransformationMethod() != null) {
            text = getTransformationMethod().getTransformation(text, this).toString();
        }

        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        float hi = 100;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            if(mTestPaint.measureText(text) >= targetWidth) 
                hi = size; // too big
            else
                lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
        if (w != oldw) {
            refitText(this.getText().toString(), w);
      }
    }

    //Attributes
    private Paint mTestPaint;
}

answered Sep 12, 2015 at 23:19

dpoetzsch's user avatar

dpoetzschdpoetzsch

7471 gold badge7 silver badges19 bronze badges

I don’t known this is correct way or not bt its working …take your view and check OnGlobalLayoutListener() and get textview linecount then set textSize.

 yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (textView.getLineCount()>=3) {
                textView.setTextSize(20);
            }else{
                //add somthing
              }
        }
    });

Its very simple few line code..

answered Oct 25, 2016 at 10:05

Mathan Chinna's user avatar

In my case using app:autoSize was not solving all cases, for example it doesn’t prevent word breaking

This is what I ended up using, it will resize down the text so that there are no word breaks on multiple lines

/**
 * Resizes down the text size so that there are no word breaks
 */
class AutoFitTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {

    private val paint = Paint()
    private val bounds = Rect()

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        var shouldResize = false
        paint.typeface = typeface
        var textSize = textSize
        paint.textSize = textSize
        val biggestWord: String = text.split(" ").maxByOrNull { it.count() } ?: return

        // Set bounds equal to the biggest word bounds
        paint.getTextBounds(biggestWord, 0, biggestWord.length, bounds)

        // Iterate to reduce the text size so that it makes the biggest word fit the line
        while ((bounds.width() + paddingStart + paddingEnd + paint.fontSpacing) > measuredWidth) {
            textSize--
            paint.textSize = textSize
            paint.getTextBounds(biggestWord, 0, biggestWord.length, bounds)
            shouldResize = true
        }
        if (shouldResize) {
            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
        }
    }
}

answered Jan 13, 2021 at 14:15

Mohamed Khaled's user avatar

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".MainActivity">

    <LinearLayout

        android:id="@+id/wrapper"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:layout_alignParentBottom="true"

        android:gravity="center_horizontal"

        android:orientation="horizontal">

        <Button

            android:id="@+id/increase"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="Increase" />

        <Button

            android:id="@+id/decrease"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="Decrease" />

    </LinearLayout>

    <ScrollView

        android:id="@+id/scroll"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:layout_above="@id/wrapper"

        android:layout_centerInParent="true">

        <LinearLayout

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:orientation="vertical">

        </LinearLayout>

    </ScrollView>

    <TextView

        android:id="@+id/tv_text"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_alignParentEnd="true"

        android:layout_marginTop="1dp"

        android:layout_marginEnd="0dp"

        android:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis sem augue,

                      aliquam bibendum fringilla quis, volutpat ut arcu. Sed nulla metus, gravida

                      id pulvinar quis, rhoncus in velit. Pellentesque semper mollis leo,

                      vitae molestie risus. Curabitur nec suscipit tortor. Quisque non purus eu

                      quam pretium mollis sed in turpis. Duis elit magna, ullamcorper vitae elementum

                      in, auctor eget ligula. Maecenas ultricies diam non nisl facilisis porta.

                      Suspendisse diam ante, accumsan sit amet enim nec, bibendum semper arcu.

                      Nunc a imperdiet odio. Morbi id est finibus ex mollis interdum vulputate non

                      eros. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vestibulum 

                      sit amet dictum ante, vitae condimentum augue. Proin ultricies enim nisl,

                      eu pharetra arcu venenatis sit amet. Pellentesque sodales, justo eu iaculis

                      rhoncus, magna mi ullamcorper enim, a mattis neque sapien eu nisi. Duis a 

                      turpis euismod nibh mattis egestas sed vel sem. Maecenas non tempor tellus,

                      id facilisis erat. Nullam id commodo nisi. Ut sed arcu lectus. Mauris lacus 

                      libero, pharetra et neque vitae, tincidunt dapibus magna. Sed non scelerisque 

                      leo, non pharetra mi. In sollicitudin metus ut lacus vestibulum efficitur.

                      Sed cursus pellentesque ante at vehicula. Nunc eros metus, mattis at aliquet at,

                      euismod et libero.!"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintLeft_toLeftOf="parent"

        app:layout_constraintRight_toRightOf="parent"

        app:layout_constraintTop_toTopOf="parent" />

</RelativeLayout>

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".MainActivity">

    <LinearLayout

        android:id="@+id/wrapper"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:layout_alignParentBottom="true"

        android:gravity="center_horizontal"

        android:orientation="horizontal">

        <Button

            android:id="@+id/increase"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="Increase" />

        <Button

            android:id="@+id/decrease"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="Decrease" />

    </LinearLayout>

    <ScrollView

        android:id="@+id/scroll"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:layout_above="@id/wrapper"

        android:layout_centerInParent="true">

        <LinearLayout

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:orientation="vertical">

        </LinearLayout>

    </ScrollView>

    <TextView

        android:id="@+id/tv_text"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_alignParentEnd="true"

        android:layout_marginTop="1dp"

        android:layout_marginEnd="0dp"

        android:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis sem augue,

                      aliquam bibendum fringilla quis, volutpat ut arcu. Sed nulla metus, gravida

                      id pulvinar quis, rhoncus in velit. Pellentesque semper mollis leo,

                      vitae molestie risus. Curabitur nec suscipit tortor. Quisque non purus eu

                      quam pretium mollis sed in turpis. Duis elit magna, ullamcorper vitae elementum

                      in, auctor eget ligula. Maecenas ultricies diam non nisl facilisis porta.

                      Suspendisse diam ante, accumsan sit amet enim nec, bibendum semper arcu.

                      Nunc a imperdiet odio. Morbi id est finibus ex mollis interdum vulputate non

                      eros. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vestibulum 

                      sit amet dictum ante, vitae condimentum augue. Proin ultricies enim nisl,

                      eu pharetra arcu venenatis sit amet. Pellentesque sodales, justo eu iaculis

                      rhoncus, magna mi ullamcorper enim, a mattis neque sapien eu nisi. Duis a 

                      turpis euismod nibh mattis egestas sed vel sem. Maecenas non tempor tellus,

                      id facilisis erat. Nullam id commodo nisi. Ut sed arcu lectus. Mauris lacus 

                      libero, pharetra et neque vitae, tincidunt dapibus magna. Sed non scelerisque 

                      leo, non pharetra mi. In sollicitudin metus ut lacus vestibulum efficitur.

                      Sed cursus pellentesque ante at vehicula. Nunc eros metus, mattis at aliquet at,

                      euismod et libero.!"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintLeft_toLeftOf="parent"

        app:layout_constraintRight_toRightOf="parent"

        app:layout_constraintTop_toTopOf="parent" />

</RelativeLayout>

TextView font size

TextView widget display text on android application. we can set or change TextView font size statically by declarative syntax
in xml layout file or programmatically at run time in java file. even we can use an xml file source to define font size.

the following example code demonstrate us how can we define TextView font size in xml layout file and how can
we uses dimens.xml to reference font size. in this example we did not changes any coding in java file, so here
we only include the layout xml file and dimens.xml file.

activity_main.xml


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="25dp"
    tools:context=".MainActivity"
    >

    <TextView
        android:id="@+id/text_view1"
        android:text="A default font size text view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

    <TextView
        android:id="@+id/text_view2"
        android:text="Font size 25dp or dip (Density-independent-Pixels)"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#FF0000"
        android:textSize="25dp"
        />

    <TextView
        android:id="@+id/text_view3"
        android:text="Font size 0.20in (inches)"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#FF5500"
        android:textSize="0.20in"
        />

    <TextView
        android:id="@+id/text_view4"
        android:text="Font size 4mm (millimeters)"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#DC143C"
        android:textSize="4mm"
        />

    <TextView
        android:id="@+id/text_view5"
        android:text="Font size 15pt (points)"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#8A2BE2"
        android:textSize="15pt"
        />

    <TextView
        android:id="@+id/text_view6"
        android:text="Font size 50px (pixels)"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#A52A2A"
        android:textSize="50px"
        />

    <TextView
        android:id="@+id/text_view7"
        android:text="Font size 25sp (Scale-independent-Pixels)"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#1E90FF"
        android:textSize="25sp"
        />

    <TextView
        android:id="@+id/text_view8"
        android:text="Font size small (from dimens.xml)"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#4B0082"
        android:textSize="@dimen/font_size_small"
        />

    <TextView
        android:id="@+id/text_view9"
        android:text="Font size medium (from dimens.xml)"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#4B0082"
        android:textSize="@dimen/font_size_medium"
        />

    <TextView
        android:id="@+id/text_view10"
        android:text="Font size large (from dimens.xml)"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#4B0082"
        android:textSize="@dimen/font_size_large"
        />

</LinearLayout>

dimens.xml file allow us to declare dimension variables. dimension type resources are as like string resources.
we can declare different dimens.xml files for different types of screens. in this example, we uses dimens.xml file to decalre
three diffrent font size variables small, medium and large.

res/values/dimens.xml


<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    <dimen name="font_size_small">10sp</dimen>
    <dimen name="font_size_medium">20sp</dimen>
    <dimen name="font_size_large">30sp</dimen>
</resources>

the following image shows the initial state of this application. this app describe how can we define different font size of TextView widgets
in xml layout file and dimens.xml file.

the following image displays the result of this android app. the image shows different font size of different TextView widgets.

Dimension
we defined dimension value in xml layout file as example 50sp, 20pt, 30dp. dimension is specified with a number followed by a unit
of measure. android supported the following units of measure, those are dp, sp, pt, px, mm, in.

dp means Density-independent-Pixels. this is an abstract unit that is based on the physical density of the screen.

sp means Scale-independent-Pixels. SP as like dp but it is also scaled by the user’s font size preference. Sp is recommended
unit for measure.

pt means Points — 1/72 of an inch based on the physical size of screen. px describe Pixels which corresponds to actual pixels on the screen.
mm is Millimeters which is based on the physical size of the screen. in describe inches which also based on the physical size of the screen.

Programmatically define the font size

android app developers can also define the TextView font size programmatically at run time. to define text size
programmatically we can use setTextSize() method. we also can define the complex unit (sp, dp, pt, px, mm, in)
by importing the android.util.TypedValue class.

this class works with setTextSize() method as example
setTextSize(TypedValue.COMPLEX_UNIT_SP, 50). setTextSize() method has two parameters unit and size. unit is the desired
dimension unit and size is the desired size in the given units.

activity_main.xml


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="25dp"
    tools:context=".MainActivity"
    >

    <TextView
        android:id="@+id/text_view1"
        android:text="A default font size text view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

    <TextView
        android:id="@+id/text_view2"
        android:text="A default font size text view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"

        android:textColor="#00008B"
        />

    <TextView
        android:id="@+id/text_view3"
        android:text="A default font size text view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#9932CC"
        />

    <TextView
        android:id="@+id/text_view4"
        android:text="A default font size text view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#00008B"
        />

    <TextView
        android:id="@+id/text_view5"
        android:text="A default font size text view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#9932CC"
        />

    <TextView
        android:id="@+id/text_view6"
        android:text="A default font size text view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#00008B"
        />

    <TextView
        android:id="@+id/text_view7"
        android:text="A default font size text view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#9932CC"
        />

    <Button
        android:id="@+id/push_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="30sp"
        android:text="Apply Font Size"
        android:onClick="perform_action"
        />

</LinearLayout>

MainActivity.java


package com.cfsuman.me.myapplication5;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.util.TypedValue;


public class MainActivity extends ActionBarActivity {

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

    //button click handler
    public void perform_action(View v){

        TextView tv1 = (TextView) findViewById(R.id.text_view1);
        //set the font size 50
        tv1.setTextSize(50);
        tv1.setText("Font size 50");

        TextView tv2 = (TextView) findViewById(R.id.text_view2);
        //define font size 35 dp or dip (Device Independent Pixels)
        tv2.setTextSize(TypedValue.COMPLEX_UNIT_DIP,35);
        tv2.setText("Font size 35 DIP (Device Independent Pixels)");

        TextView tv3 = (TextView) findViewById(R.id.text_view3);
        //define font size 0.25 Inches
        tv3.setTextSize(TypedValue.COMPLEX_UNIT_IN,0.25f);
        tv3.setText("Font size 0.25 Inches");

        TextView tv4 = (TextView) findViewById(R.id.text_view4);
        //define text size 5 Millimeters
        tv4.setTextSize(TypedValue.COMPLEX_UNIT_MM,5);
        tv4.setText("Font size 5 Millimeters");

        TextView tv5 = (TextView) findViewById(R.id.text_view5);
        //define text size 25 Points
        tv5.setTextSize(TypedValue.COMPLEX_UNIT_PT,25);
        tv5.setText("Font size 25 PT (Points)");

        TextView tv6 = (TextView) findViewById(R.id.text_view6);
        //set the text size 50 Pixels
        tv6.setTextSize(TypedValue.COMPLEX_UNIT_PX,50);
        tv6.setText("Font size 50 Pixels");

        TextView tv7 = (TextView) findViewById(R.id.text_view7);
        //set the text view text size 50 SP (Scale Independent Pixels)
        tv7.setTextSize(TypedValue.COMPLEX_UNIT_SP,50);
        tv7.setText("Font size 50 SP (Scale Independent Pixels)");
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

the following image display the initial states of this application. this example demonstrate how
can we define text size programmatically at run time in java file.

when someone click the push button, the different TextView changes its text size as defined in java file. in the java file we uses the
settextSize() method to set different text size for different TextView widget. following image shows the result.

Overview

Every Android device comes with a collection of standard fonts: Droid Sans, Droid Sans Mono and Droid Serif. They were designed to be optimal for mobile displays, so these are the three fonts you will be working with most of the time and they can be styled using a handful of XML attributes. You might, however, see the need to use custom fonts for special purposes.

This guide will take a look at the TextView and discuss common properties associated with this view as well as how to setup custom typefaces.

Text Attributes

Typeface

As stated in the overview, there are three different default typefaces which are known as the Droid family of fonts: sans, monospace and serif. You can specify any one of them as the value for the android:typeface attribute in the XML:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="This is a 'sans' demo!"
    android:typeface="sans"
/>

Here’s how they look:

fonts

In addition to the above, there is another attribute value named «normal» which defaults to the sans typeface.

Text Style

The android:textStyle attribute can be used to put emphasis on the text. The possible values are: normal, bold, italic. You can also specify bold|italic.

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="This is bold!"
    android:textStyle="bold"
/>

A sampling of styles can be seen below:

style

Text Size

android:textSize specifies the font size. Its value must consist of two parts: a floating-point number followed by a unit. It is generally a good practice to use the sp unit so the size can scale depending on user settings.

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="14sp is the 'normal' size."
    android:textSize="14sp"
/>

A sampling of styles can be seen below:

style

Too many type sizes and styles at once can wreck any layout. The basic set of styles are based on a typographic scale of 12, 14, 16, 20, and 34. Refer to this typography styles guide for more details.

Text Truncation

There are a few ways to truncate text within a TextView. First, to restrict the total number of lines of text we can use android:maxLines and android:minLines:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minLines="1"
    android:maxLines="2"
/>

In addition, we can use android:ellipsize to begin truncating text

<TextView
    ...
    android:ellipsize="end"
    android:singleLine="true"
/>

Following values are available for ellipsize: start for ...bccc, end for aaab..., middle for aa...cc, and marquee for aaabbbccc sliding from left to right. Example:

style

There is a known issue with ellipsize and multi-line text, see this MultiplelineEllipsizeTextView library for an alternative.

Text Color

The android:textColor and android:textColorLink attribute values are hexadecimal RGB values with an optional alpha channel, similar to what’s found in CSS:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="A light blue color."
    android:textColor="#00ccff"
    android:textColorLink="#8DE67F"
/>

The android:textColorLink attribute controls the highlighting for hyperlinks embedded within the TextView. This results in:

We can edit the color at runtime with:

// based on hex value
textView.setTextColor(Color.parseColor("#000000"));
// based on a color resource file
textView.setTextColor(ContextCompat.getColor(context, R.color.your_color));
// based on preset colors
textView.setTextColor(Color.RED);
// based on hex value
textView.setTextColor(Color.parseColor("#000000"))
// based on a color resource file
textView.setTextColor(ContextCompat.getColor(this, R.color.your_color))
// based on preset colors
textView.setTextColor(Color.RED)

Text Shadow

You can use three different attributes to customize the appearance of your text shadow:

  • android:shadowColor — Shadow color in the same format as textColor.
  • android:shadowRadius — Radius of the shadow specified as a floating point number.
  • android:shadowDx — The shadow’s horizontal offset specified as a floating point number.
  • android:shadowDy — The shadow’s vertical offset specified as a floating point number.

The floating point numbers don’t have a specific unit — they are merely arbitrary factors.

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="A light blue shadow."
    android:shadowColor="#00ccff"
    android:shadowRadius="2"
    android:shadowDx="1"
    android:shadowDy="1"
/>

This results in:

Various Text Properties

There are many other text properties including android:lineSpacingMultiplier, android:letterSpacing, android:textAllCaps, android:includeFontPadding and many others:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:lineSpacingMultiplier="1.1"
    android:textAllCaps="true"
/>

android:includeFontPadding removes the extra padding around large fonts. android:lineSpacingMultiplier governs the spacing between lines with a default of «1».

Inserting HTML Formatting

TextView natively supports HTML by translating HTML tags to spannable sections within the view. To apply basic HTML formatting to text, add text to the TextView with:

TextView view = findViewById(R.id.sampleText);

String formattedText = "This <i>is</i> a <b>test</b> of <a href='http://foo.com'>html</a>";
// or getString(R.string.htmlFormattedText);

view.setText(HtmlCompat.fromHtml(formattedText, HtmlCompat.FROM_HTML_MODE_LEGACY));
val view: TextView = findViewById(R.id.sampleText)

val formattedText = "This <i>is</i> a <b>test</b> of <a href='http://foo.com'>html</a>"
// or getString(R.string.htmlFormattedText)

view.text = HtmlCompat.fromHtml(formattedText, HtmlCompat.FROM_HTML_MODE_LEGACY)

You can read more about the html modes here.

This results in:

Note that all tags are not supported. See this article for a more detailed look at supported tags and usages.

Setting Font Colors

For setting font colors, we can use the <font> tag as shown:

HtmlCompat.fromHtml("Nice! <font color='#c5c5c5'>This text has a color</font>. This doesn't", HtmlCompat.FROM_HTML_MODE_LEGACY); 
HtmlCompat.fromHtml("Nice! <font color='#c5c5c5'>This text has a color</font>. This doesn't", HtmlCompat.FROM_HTML_MODE_LEGACY)

And you should be all set.

Storing Long HTML Strings

If you want to store your HTML text within res/values/strings.xml, you have to use CDATA to escape such as:

<?xml version="1.0" encoding="utf-8"?>
<string name="htmlFormattedText">
    <![CDATA[
        Please <a href="http://highlight.com">let us know</a> if you have <b>feedback on this</b> or if 
        you would like to log in with <i>another identity service</i>. Thanks!   
    ]]>
</string>

and access the content with getString(R.string.htmlFormattedText) to load this within the TextView.

For more advanced cases, you can also check out the html-textview library which adds support for almost any HTML tag within this third-party TextView.

Autolinking URLs

TextView has native support for automatically locating URLs within the their text content and making them clickable links which can be opened in the browser. To do this, enable the android:autolink property:

<TextView
     android:id="@+id/custom_font"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:autoLink="all"
     android:linksClickable="true"
/>

This results in:

Issues with ListView

One known issue when using android:autoLink or the Linkify class is that it may break the ability to respond to events on the ListView through setOnItemClickListener. Check out this solution which extends TextView in order to modify the onTouchEvent to correctly propagate the click. You basically need to create a LinkifiedTextView and use this special View in place of any of your TextView’s that need auto-link detection.

In addition, review these alternate solutions which may be effective as well:

  • This stackoverflow post or this other post
  • This android issue for additional context.

Displaying Images within a TextView

A TextView is actually surprisingly powerful and actually supports having images displayed as a part of it’s content area. Any images stored in the «drawable» folders can actually be embedded within a TextView at several key locations in relation to the text using the android:drawableRight and the android:drawablePadding property. For example:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"     
    android:gravity="center"
    android:text="@string/my_contacts"
    android:drawableRight="@drawable/ic_action_add_group"
    android:drawablePadding="8dp"
/>

Which results in:

Contacts View

In Android, many views inherit from TextView such as Buttons, EditTexts, RadioButtons which means that all of these views support the same functionality. For example, we can also do:

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/user_name"
    android:drawableLeft="@drawable/ic_action_person"
    android:drawablePadding="8dp"
/>

Which results in:

EditText with drawable

The relevant attributes here are drawableLeft, drawableRight, drawableTop and drawableBottom along with drawablePadding. Check out this TextView article for a more detailed look at how to use this functionality.

Note that if you want to be able to better control the size or scale of the drawables, check out this handy TextView extension or this bitmap drawable approach. You can also make calls to setCompoundDrawablesWithIntrinsicBounds on the TextView.

Using Fonts

The easiest way to add font support is to upgrade to Android Studio 3.0, which provides the ability to use other fonts provided by Google. You can visit https://fonts.google.com/ to see the ones that are free to use. See the FAQ section for more information.

Android Studio v3.0 provides built-in support for these fonts and will automatically handles generating the XML and necessary metadata. Next to the Attributes section of a TextView, look for the fontFamily and click on More Fonts:

More fonts

You will then see these choices:

Fonts

Once you choose a font, you will notice that a font directory will be created and a similar XML file will be generated. Notice that Android Studio automatically takes care of adding the necessary font provider certificates required to request from Google:

<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
        app:fontProviderAuthority="com.google.android.gms.fonts"
        app:fontProviderPackage="com.google.android.gms"
        app:fontProviderQuery="name=Advent Pro&amp;weight=100"
        app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

Adding custom fonts

We can actually use any custom font that we’d like within our applications. Check out fontsquirrel for an easy source of free fonts. For example, we can download Chantelli Antiqua as an example.

Fonts are stored in the «assets» folder. In Android Studio, File > New > folder > Assets Folder. Now download any font and place the TTF file in the assets/fonts directory:

We’re going to use a basic layout file with a TextView, marked with an id of «custom_font» so we can access it in our code.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
 
    <TextView
            android:id="@+id/custom_font"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="This is the Chantelli Antiqua font."
    />
</LinearLayout>

To set the custom font manually, open your activity file and insert this into the onCreate() method:

// Get access to our TextView
TextView txt = findViewById(R.id.custom_font);
// Create the TypeFace from the TTF asset
Typeface font = Typeface.createFromAsset(getAssets(), "fonts/Chantelli_Antiqua.ttf");
// Assign the typeface to the view
txt.setTypeface(font);
// Get access to our TextView
val txt: TextView = findViewById(R.id.custom_font)
// Create the TypeFace from the TTF asset
val font = Typeface.createFromAsset(assets, "fonts/Chantelli_Antiqua.ttf")
// Assign the typeface to the view
txt.typeface = font

Alternatively, you can use the third-party calligraphy library:

<TextView fontPath="fonts/Chantelli_Antiqua.ttf"/>

Either method will will result in:

custom

You’ll also want to keep an eye on the total size of your custom fonts, as this can grow quite large if you’re using a lot of different typefaces.

Using Spans to Style Sections of Text

Spans come in really handy when we want to apply styles to portions of text within the same TextView. We can change the text color, change the typeface, add an underline, etc, and apply these to only certain portions of the text. The full list of spans shows all the available options.

As an example, let’s say we have a single TextView where we want the first word to show up in red and the second word to have a strikethrough:

Custom

We can accomplish this with spans using the code below:

String firstWord = "Hello";
String secondWord = "World!";

TextView tvHelloWorld = findViewById(R.id.tvHelloWorld);

// Create a span that will make the text red
ForegroundColorSpan redForegroundColorSpan = new ForegroundColorSpan(
        ContextCompat.getColor(this, android.R.color.holo_red_dark));

// Use a SpannableStringBuilder so that both the text and the spans are mutable
SpannableStringBuilder ssb = new SpannableStringBuilder(firstWord);

// Apply the color span
ssb.setSpan(
        redForegroundColorSpan,            // the span to add
        0,                                 // the start of the span (inclusive)
        ssb.length(),                      // the end of the span (exclusive)
        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // behavior when text is later inserted into the SpannableStringBuilder
                                           // SPAN_EXCLUSIVE_EXCLUSIVE means to not extend the span when additional
                                           // text is added in later

// Add a blank space
ssb.append(" ");

// Create a span that will strikethrough the text
StrikethroughSpan strikethroughSpan = new StrikethroughSpan();

// Add the secondWord and apply the strikethrough span to only the second word
ssb.append(secondWord);
ssb.setSpan(
        strikethroughSpan,
        ssb.length() - secondWord.length(),
        ssb.length(),
        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

// Set the TextView text and denote that it is Editable
// since it's a SpannableStringBuilder
tvHelloWorld.setText(ssb, TextView.BufferType.EDITABLE);
val firstWord  = "Hello"
val secondWord = "World!"

val tvHelloWorld: TextView = findViewById(R.id.tvHelloWorld)

// Create a span that will make the text red
val redForegroundColorSpan = ForegroundColorSpan(
    ContextCompat.getColor(this, android.R.color.holo_red_dark)
)

// Use a SpannableStringBuilder so that both the text and the spans are mutable
val ssb = SpannableStringBuilder(firstWord)

// Apply the color span
ssb.setSpan(
    redForegroundColorSpan,             // the span to add
    0,                                  // the start of the span (inclusive)
    ssb.length,                         // the end of the span (exclusive)
    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)   // behavior when text is later inserted into the SpannableStringBuilder
                                        // SPAN_EXCLUSIVE_EXCLUSIVE means to not extend the span when additional
                                        // text is added in later

// Add a blank space
ssb.append(" ")

// Create a span that will strikethrough the text
val strikethroughSpan = StrikethroughSpan()

// Add the secondWord and apply the strikethrough span to only the second word
ssb
    .append(secondWord)
    .setSpan(
        strikethroughSpan,
        ssb.length - secondWord.length,
        ssb.length,
        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

// Set the TextView text and denote that it is Editable
// since it's a SpannableStringBuilder
tvHelloWorld.setText(ssb, TextView.BufferType.EDITABLE)

Note: There are 3 different classes that can be used to represent text that has markup attached. SpannableStringBuilder (used above) is the one to use when dealing with mutable spans and mutable text. SpannableString is for mutable spans, but immutable text. And SpannedString is for immutable spans and immutable text.

Creating Clickable Styled Spans

In certain cases, we might want different substrings in a TextView to different styles and then clickable to trigger an action. For example, rendering tweet items where @foo can be clicked in a message to view a user’s profile. For this, you should copy over the PatternEditableBuilder.java utility into your app. You can then use this utility to make clickable spans. For example:

// Set text within a `TextView`
TextView textView = findViewById(R.id.textView);
textView.setText("Hey @sarah, where did @jim go? #lost");
// Style clickable spans based on pattern
new PatternEditableBuilder().
    addPattern(Pattern.compile("\@(\w+)"), Color.BLUE,
       new PatternEditableBuilder.SpannableClickedListener() {
            @Override
            public void onSpanClicked(String text) {
                Toast.makeText(MainActivity.this, "Clicked username: " + text,
                    Toast.LENGTH_SHORT).show();
            }
       }).into(textView);
// Set text within a `TextView`
val textView: TextView = findViewById(R.id.textView)
textView.text = "Hey @sarah, where did @jim go? #lost"

// Style clickable spans based on pattern
PatternEditableBuilder()
    .addPattern(Pattern.compile("@(\w+)"), Color.BLUE) { text ->
        Toast.makeText(this@MainActivity, "Clicked username: $text",
            Toast.LENGTH_SHORT).show()
}.into(textView)

and this results in the following:

For more details, view the README for more usage examples.

References

  • https://tutorialwing.com/android-textview-using-kotlin-example/
  • https://tutorialwing.com/create-an-android-textview-programmatically-in-kotlin/
  • https://code.tutsplus.com/tutorials/customize-android-fonts—mobile-1601
  • https://www.androidhive.info/2012/02/android-using-external-fonts/
  • https://stackoverflow.com/questions/3651086/android-using-custom-font
  • https://www.tutorialspoint.com/android/android_custom_fonts.htm
  • https://antonioleiva.com/textview_power_drawables/
  • https://www.cronj.com/frontend-development/html.html

This example demonstrates how do I change the font size of TextView in android.

Step 1 − Create a new project in Android Studio, go to File ⇒ New Project and fill all required details to create a new project.

Step 2 − Add the following code to res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   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:text="Im Huge"
      android:fontFamily="sans-serif-smallcaps"
      android:layout_centerInParent="true"
      android:textSize="64sp" />
   <TextView
      android:id="@+id/textView2"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_below="@id/textView1"
      android:layout_marginBottom="10dp"
      android:text="Im small"
      android:layout_centerInParent="true"
      android:fontFamily="serif-monospace"
      android:textSize="36sp" />
   <TextView
      android:id="@+id/textView3"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_below="@id/textView2"
      android:layout_marginBottom="10dp"
      android:text="Im tiny"
      android:layout_centerInParent="true"
      android:fontFamily="serif-monospace"
      android:textSize="24sp" />
</RelativeLayout>

Step 3 − Add the following code to src/MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
   }
}

Step 4 − Add the following code to androidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.com.sample">
   <application
      android:allowBackup="true"
      android:icon="@mipmap/ic_launcher"
      android:label="@string/app_name"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:supportsRtl="true"
      android:theme="@style/AppTheme">
      <activity android:name=".MainActivity">
         <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
         </intent-filter>
      </activity>
   </application>
</manifest>

Let’s try to run your application. I assume you have connected your actual Android Mobile device with your computer. To run the app from android studio, open one of your project’s activity files and click Run  icon from the toolbar. Select your mobile device as an option and then check your mobile device which will display your default screen −

Click here to download the project code.

Отображение текстовой информации — наверное, самая базовая и важная часть многих Android-приложений. В данной статье пойдет речь о TextView. Каждый разработчик, начиная с «Hello World», постоянно сталкивается с этим элементом пользовательского интерфейса. Периодически в работе с текстом приходится задумываться о реализации различных дизайнерских решений или улучшении производительности при отрисовке экрана.

Я расскажу об устройстве TextView и некоторых тонкостях работы с ним. Основные советы были взяты из докладов прошедших Google I/O.

TextView под капотом

Для отрисовки текста в Android под капотом используется целый стек из различных библиотек. Их можно разделить на две основные части — java-код и нативный код:

Java-код по сути является частью Android SDK, доступной разработчикам приложений, и новые возможности из него могут быть перенесены в support library.

Само ядро TextView написано на C++, что ограничивает портирование в support library реализованных там новых возможностей из новых версий операционной системы. Ядро представляет из себя следующие библиотеки:

  • Minikin используется для измерения длины текста, переноса строк и слов по слогам.
  • ICU обеспечивает поддержку Unicode.
  • HarfBuzz находит для символов юникода соответствующие графические элементы (глифы) в шрифтах.
  • FreeType делает растровые изображения глифов.
  • Skia – движок для рисования 2D графики.

Измерение длины текста и перенос строк

Если передать строку библиотеке Minikin, которая используется внутри TextView, то первым делом она определяет, из каких глифов строка состоит:

Как можно заметить из данного примера, сопоставление символов юникода с глифами не всегда будет один к одному: здесь сразу 3 символа будут соответствовать одному глифу ffi. Кроме того, стоит обратить внимание, что нужные глифы могут быть найдены в различных системных шрифтах.

Поиск глифов только в системных шрифтах может повлечь за собой сложности, особенно если через символы отображаются иконки или эмодзи, а в одной строке предполагается комбинировать символы из разных шрифтов. Поэтому, начиная с Android Q (29), появилась возможность сделать свой список шрифтов, поставляемых с приложением. Этот список будет использоваться для поиска глифов:

textView.typeface = TypeFace.CustomFallbackBuilder(
  FontFamily.Builder(
    Font.Builder(assets, “lato.ttf”).build()
  ).build()
).addCustomFallback(
  FontFamily.Builder(
    Font.Builder(assets, “kosugi.ttf”).build()
  ).build()
).build()

Теперь с использованием CustomFallbackBuilder при сопоставлении символов с глифами SDK будет перебирать указанные font family по порядку, и если не удастся найти соответствие, поиск продолжится в системных шрифтах (а через метод setSystemFallback() можно указать предпочитаемый системный font family). CustomFallbackBuilder имеет ограничение на количество font family – можно добавить не более 64 шрифтов.

Библиотека Minikin разделяет строки на слова и делает измерение отдельных слов. Для ускорения работы, начиная с Lollipop (21), используется системный LRU кэш из слов. Такой кэш дает огромный выигрыш в производительности: вызов Paint.measureText() для закешированного слова займет в среднем 3% от времени первого расчета его размеров.

Если текст не помещается в заданную ширину, Minikin расставляет переносы строк и слов в тексте. Начиная с Marshmallow (23) можно управлять ее поведением, указав у TextView специальные атрибуты breakStrategy и hyphenationFrequency.

При значении breakStrategy=simple библиотека просто будет расставлять переносы последовательно, проходя по тексту: как только строка перестает помещаться, ставится перенос перед последним словом.

В значении balanced библиотека постарается сделать переносы строк так, чтобы строки оказались выровнены по ширине.

high_quality имеет почти такое же поведение, что и balanced, за исключением некоторых отличий (одно из них: на предпоследней строке перенос может быть не только отдельных слов, но и слова по слогам).

Атрибут hyphenationFrequency позволяет управлять стратегией переноса слов по слогам. Значение none не будет делать автоматический перенос слов, normal сделает небольшую частоту переносов, а full, соответственно, задействует максимальное количество слов.

Производительность отрисовки текста в зависимости от выбранных флагов (измерялась на Android P (28)):

Учитывая достаточно сильный удар по производительности, разработчики Google, начиная с версии Q (29) и AppCompat 1.1.0, решили по умолчанию выключить перенос слов (hyphenation). Если перенос слов важен в приложении, то теперь его надо включать явно.

При использовании переноса слов надо учитывать, что на работу библиотеки будет влиять текущий выбранный язык в операционной системе. В зависимости от языка система будет выбирать специальные словари с правилами переноса.

Стили текста

В Android есть несколько способов стилизации текста:

  • Единый стиль (single style), который применяется для всего элемента TextView.
  • Мультистиль (multi style) — сразу несколько стилей, которые могут быть применены к тексту, на уровне параграфа или отдельных символов. Для этого есть несколько способов:
    • рисование текста на канве
    • html-теги
    • специальные элементы разметки – span’ы

Единый стиль подразумевает под собой использование XML-стилей или XML-атрибутов в разметке TextView. При этом система будет применять значения из ресурсов в следующем порядке: TextAppearance, тема (Theme), стиль по умолчанию (Default style), стиль из приложения, и наибольший приоритет — значения атрибутов View.

Использование ресурсов — это достаточно простое решение, но, к сожалению, оно не позволяет применить стиль к части текста.

Html-теги – еще одно простое решение, которое дает такие возможности, как сделать стиль отдельных слов жирным, курсивным, или даже выделить в тексте списки при помощи точек. Все что нужно разработчику — сделать вызов метода Html.fromHtml(), который превратит текст с тегами в текст, размеченный span’ами. Но такое решение имеет ограниченные возможности, так как распознает только часть html-тегов и не поддерживает CSS стили.

val text = "My text <ul><li>bullet one</li><li>bullet two</li></ul>"

myTextView.text = Html.fromHtml(text)

Различные способы стилизации TextView можно комбинировать, но стоит помнить о приоритете того или иного метода, что будет влиять на конечный результат:

Еще один способ — рисование текста на канве — дает разработчику полный контроль над выводом текста: например, можно нарисовать текст вдоль кривой линии. Но такое решение в зависимости от требований может быть достаточно сложным в реализации и выходит за рамки этой статьи.

Spans

Для тонкой настройки стилей в TextView используются span’ы. С помощью span’ов можно изменить цвет диапазона символов, сделать часть текста в виде ссылок, изменить размер текста, нарисовать точку перед параграфом и т.д.

Можно выделить следующие категории span’ов:

  • Character spans – применяются на уровне символов строки.
    • Appearance affecting – не меняют размер текста.
    • Metric affecting – изменяют размер текста.
  • Paragraph spans – применяются на уровне параграфа.

В Android фреймворке есть интерфейсы и абстрактные классы с методами, которые вызываются во время onMeasure() и отрисовки TextView, эти методы дают доступ span’ам к более низкоуровневым объектам вроде TextPaint и Canvas. Android фреймворк, применяя span, проверяет, какие интерфейсы этот объект реализует, чтобы вызвать нужные методы.

В android фреймворке определено порядка 20+ span’ов, так что прежде чем делать свой собственный, лучше проверить, нет ли в SDK подходящего.

Appearance vs metric affecting spans

Первая категория span’ов влияет на то, как будут выглядеть символы в строке: цвет символов, цвет фона, подчеркнутые или зачеркнутые символы и т.д. Эти span’ы имплементируют интерфейс UpdateAppearance и наследуются от класса CharacterStyle, который предоставляет доступ к объекту TextPaint.

Metric affecting span влияет на размер текста и layout’а, следовательно применение такого span’а потребует не только перерисовку TextView, но и вызов onMeasure()/onLayout(). Эти span’ы обычно наследуются от класса MetricAffectingSpan, который наследуется от упомянутого выше CharacterStyle.

Character vs paragraph affecting spans

Paragraph span влияет на целый блок текста: может изменить выравнивание, отступ или даже вставить точку в начале параграфа. Такие span’ы должны наследоваться от класса ParagraphStyle и вставляться в текст ровно с начала параграфа до его конца. Если диапазон окажется неверным, то span не будет работать.

В Android параграфами считается часть текста, отделённая символами перевода строки (n).

Написание своих span’ов

При написании собственных span’ов надо определиться, что будет затрагивать span, чтобы выбрать, от какого класса надо наследоваться:

  • Затрагивает текст на уровне символов → CharacterStyle
  • Затрагивает текст на уровне параграфа → ParagraphStyle
  • Затрагивает вид текста → UpdateAppearance
  • Затрагивает размер текста → UpdateLayout

Вот пример span’а для смены шрифта:

class CustomTypefaceSpan(private val font: Typeface?) : MetricAffectingSpan() {

  override fun updateMeasureState(textPaint: TextPaint) = update(textPaint)

  override fun updateDrawState(textPaint: TextPaint) = update(textPaint)

  fun update(textPaint: TextPaint) {
    textPaint.apply {
      val old = typeface
      val oldStyle = old?.style ?: 0
      val font = Typeface.create(font, oldStyle)

      typeface = font // Устанавливаем новый шрифт
    }
  }
}

Представим, что мы хотим сделать свой собственный span для выделения блоков кода, для этого отредактируем наш предыдущий span – добавим после установки шрифта еще и изменение цвета фона текста:

class CodeBlockSpan(private val font: Typeface?) : MetricAffectingSpan() {

             …

  fun update(textPaint: TextPaint) {
    textPaint.apply {
      // Устанавливаем новый шрифт
             …
      bgColor = lightGray // Устанавливаем цвет фона
    }
  }
}

Применим span к тексту:

// Устанавливаем один кастомный span
spannable.setSpan(CodeBlockSpan(typeface), ...)

Но точно такой же результат можно получить, скомбинировав два span’а: возьмем наш предыдущий CustomTypefaceSpan и BackgroundColorSpan из Android фреймворка:

// Устанавливаем цвет фона
spannable.setSpan(BackgroundColorSpan(lightGray), ...)

// Устанавливаем шрифт
spannable.setSpan(CustomTypefaceSpan(typeface), ...)

Эти два решения будут иметь отличие. Дело в том, что самописные span’ы не могут реализовывать интерфейс Parcelable, в отличие от системных.

При передаче стилизованной строки через Intent или буфер обмена в случае самописного span’а разметка не сохранится. При использовании span’ов из фреймворка разметка останется.

Использование span’ов в тексте

Для стилизованного текста во фреймворке есть два интерфейса: Spanned и Spannable (с неизменяемой и изменяемой разметкой соответственно) и три реализации: SpannedString (неизменяемый текст), SpannableString (неизменяемый текст) и SpannableStringBuilder (изменяемый текст).

SpannableStringBuilder, например, используется внутри EditText, которому требуется изменять текст.

Добавить новый span к строке можно при помощи метода:

setSpan(Object what, int start, int end, int flags)

Через первый параметр передается span, затем указывается диапазон индексов в тексте. И последним параметром можно управлять, какое будет поведение span’а при вставке нового текста: будет ли span распространяться на текст, вставленный в начальную или конечную точки (если в середину вставить новый текст, то span автоматически применится к нему вне зависимости от значений флага).

Перечисленные выше классы различаются не только семантически, но и тем, как они устроены внутри: SpannedString и SpannableString используют массивы для хранения span’ов, а SpannableStringBuilder использует дерево интервалов.

Если провести тесты на скорость отрисовки текста в зависимости от количества span’ов, то будут такие результаты: при использовании в строке до ~250 span’ов SpannableString и SpannableStringBuilder работают примерно с одинаковой скоростью, но если элементов разметки становится больше 250, то SpannableString начинает проигрывать. Таким образом, если стоит задача применить стиль к какому-то тексту, то при выборе класса надо руководствоваться семантическими требованиями: будут ли строка и стили изменяемыми. Но если для разметки требуется больше 250 span’ов, то предпочтение надо всегда отдавать SpannableStringBuilder.

Проверка на наличие span’а в тексте

Периодически возникает задача проверить, есть ли в spanned строке определенный span. И на Stackoverflow можно встретить такой код:

fun <T> hasSpan(spanned: Spanned, clazz: Class<T>): Boolean {
  val spans: Array<out T> = spanned.getSpans(0, spanned.length, clazz)
  return spans.isNotEmpty()
}

Такое решение будет работать, но оно неэффективно: придется пройти по всем span’ам, проверить, относится ли каждый из них к переданному типу, собрать результат в массив и в конце всего лишь проверить, что массив не пустой.

Более эффективным решением будет использование метода nextSpanTransition():

fun <T> hasSpan(spanned: Spanned, clazz: Class<T>): Boolean {
  val limit = spanned.length
  return spanned.nextSpanTransition(0, limit, clazz) < limit
}

Разметка текста в различных языковых ресурсах

Может возникнуть такая задача, когда требуется выделить при помощи разметки определенное слово в различных строковых ресурсах. Например, нам надо выделить слово “text” в английской версии и “texto” в испанской:

<!-- values-en/strings.xml -->
<string name="title">Best practices for text in Android</string>

<!-- values-es/strings.xml -->
<string name=”title”>Texto en Android: mejores prácticas</string>

Если требуется что-то простое, например, выделить слово жирным, то можно использовать обычные html-теги (<b>). В UI надо будет просто установить строковый ресурс в TextView:

textView.setText(R.string.title)

Но если требуется что-то более сложное, например, смена шрифта, то html уже не получится использовать. Решением будет использовать специальный тег <annotation>. Этот тег позволяет определить любую пару ключ-значение в xml-файле. Когда мы вытащим строку из ресурсов, эти теги автоматически сконвертируются в span’ы Annotation, расставленными по тексту с соответствующими ключами и значениями. После этого можно распарсить список аннотаций в тексте и применить нужные span’ы.

Предположим, нам надо поменять шрифт при помощи CustomTypefaceSpan.

Добавим тег и определим для него ключ “font” и значение – тип шрифта, который мы хотим использовать – “title_emphasis”:

<!-- values-en/strings.xml -->
<string name="title">Best practices for <annotation font=”title_emphasis”>text</annotation> in Android</string>

<!-- values-es/strings.xml -->
<string name=”title”><annotation font=”title_emphasis”>Texto</annotation> en Android: mejores prácticas</string>

Вытащим строку из ресурсов, найдем аннотации с ключом “font” и расставим span’ы:

// Вытаскиваем из ресурсов текст как SpannedString, чтобы найти в нем span’ы
val titleText = getText(R.string.title) as SpannedString

// Получаем все аннотации
val annotations = titleText.getSpans(0, titleText.length, Annotation::class.java)

// Делаем копию строки как SpannableString
// теперь можно менять разметку текста
val spannableString = SpannableString(titleText)

// проходим по всем аннотациям
for (annotation in annotations) {  

  // находим аннотации с ключом "font"
  if (annotation.key == "font") {
    val fontName = annotation.value

    // проверяем значение аннотации, чтобы установить нужный шрифт
    if (fontName == "title_emphasis") {
      val typeface = getFontCompat(R.font.permanent_marker)
      // устанавливаем span в тот же диапазон, что и аннотации
      spannableString.setSpan(
        CustomTypefaceSpan(typeface),
        titleText.getSpanStart(annotation),
        titleText.getSpanEnd(annotation),
        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
      )
    }
  }
}

styledText.text = spannableString

Выше упоминалось, что span’ы не из Android-фреймворка не могут имплементировать Parcelable и передаваться через Intent. Но это не относится к аннотациям, которые имплементируют Parcelable. Так что аннотированную строку можно передать через Intent и распарсить точно таким же образом, расставив свои span’ы.

Как текст располагается в TextView

TextView умеет отображать не только текст, но и картинки. Также можно задавать различные отступы перед текстом. Под капотом это работает так, что TextView создает дочерний класс Layout, ответственный непосредственно за отображение текста. Это абстрактный класс, который имеет три реализации, напрямую с ними обычно не приходится работать, если не писать свой элемент управления:

  • BoringLayout используется для простых текстов, не поддерживает переносы строк, RTL и другие вещи, но при этом является самым легковесным. TextView использует его, если текст удовлетворяет всем ограничениям.
  • StaticLayout используется в TextView для остальных случаев.
  • DynamicLayout используется для изменяемого текста в EditText.

У Layout есть много методов, которые позволяют узнать различные параметры отображаемого текста: координаты строк, baseline, координаты начала и конца текста в строке и т.д. (подробнее можно посмотреть в документации)

Такие методы могут быть очень полезны. Например, некоторые разработчики сталкиваются с задачей выделения части текста в прямоугольники с закругленными углами, и пытаются искать ее решение через span’ы, которые не применимы в решении этой проблемы.

Зато на помощь могут прийти методы класса Layout. Вот примерное решение:

При помощи аннотаций выделяем слова, которые должны быть обведены в прямоугольники.

Затем создаем 4 drawable ресурса для всех случаев переноса текста, который должен быть заключен в прямоугольники:

Далее находим нужные нам аннотации в тексте, как это описывалось выше. Теперь у нас есть индексы начала и конца такой аннотации. Через методы Layout можно узнать номер строки, на которой начинается проаннотированный текст, и на которой заканчивается:

val startLine = layout.getLineForOffset(spanStartIndex)
val endLine = layout.getLineForOffset(spanEndIndex)

Далее придется нарисовать один или несколько прямоугольников. Рассмотрим простой случай, когда проаннотированная часть текста оказалась на одной строке, тогда нам понадобится всего один прямоугольник с четырьмя закругленными углами. Определим его координаты и нарисуем:

...

if (startLine == endLine) {
  val lineTop = layout.getLineTop(startLine) // координаты верха строки
  val lineBottom = layout.getLineBottom(startLine) // координаты низа строки
  val startCoor = layout.getPrimaryHorizontal(spanStartIndex).toInt() // координаты начала прямоугольника
  val endCoor = layout.getPrimaryHorizontal(spanEndIndex).toInt() // координаты конца прямоугольника

  // Рисуем прямоугольник
  drawable.setBounds(startCoor, lineTop, endCoor, lineBottom)
  drawable.draw(canvas)

...

Как видно из этого примера, Layout хранит очень много полезной информации по отображаемому тексту, которая может помочь в реализации разных нестандартных задач.

Производительность TextView

TextView, как и любая View, при отображении проходит через три фазы: onMeasure(), onLayout() и onDraw(). При этом onMeasure() занимает больше всего времени, в отличие от двух других методов: в этот момент пересоздается класс Layout и производится расчет размеров текста. Так что изменение размера текста (например, смена шрифта) влечет за собой много работы. Изменение же цвета текста будет более легковесным, потому что потребует только вызова onDraw(). Как упоминалось выше, в системе есть глобальный кэш слов с рассчитанными размерами. Если слово уже есть в кэше, то повторный вызов onMeasure() для него займет 11-16% от времени, которое потребовалось бы для полного расчета.

Ускорение показа текста

В 2015 году разработчики Instagram ускорили показ комментариев к фотографиям, используя глобальный кэш. Идея была в том, чтобы виртуально рисовать текст до показа его на экране, таким образом “разогрев” системный кэш. Когда подходила очередь показа текста, пользователь видел его гораздо быстрее, так как текст уже был измерен и лежал в кэше.

Начиная с Android P (28) разработчики Google добавили в API возможность выполнить фазу измерения размера текста заранее в фоновом потоке – PrecomputedText (и бэкпорт для API начиная с Android I (14)PrecomputedTextCompat). С использованием нового API в фоновом потоке будет выполнено 90% работы.

Пример:

// UI thread

val params: PrecomputedText.Params = textView.getTextMetricsParams()
val ref = WeakReference(textView)

executor.execute {

  // background thread
  val text = PrecomputedText.create("Hello", params)
  val textView = ref.get()
   textView?.post {

    // UI thread
    val textView = ref.get()
    textView?.text = text
  }
}

Показ большого текста

Если надо показать большой текст, то не стоит его сразу передавать в TextView. Иначе приложение может перестать плавно работать или вовсе начать зависать, так как будет делать много работы на главном потоке, чтобы показать огромный текст, который пользователь, возможно, даже и не прокрутит до конца. Решением будет разбиение текста на части (например, параграфы) и показ отдельных частей в RecyclerView. Для еще большего ускорения можно заранее рассчитывать размер блоков текста, используя PrecomputedText.

Для облегчения встраивания PrecomputedText в RecyclerView разработчики Google сделали специальные методы PrecomputedTextCompat.getTextFuture() и AppCompatTextView.setTextFuture():

fun onBindViewHolder(vh: ViewHolder, position: Int) {

  val data = getData(position)
  vh.textView.setTextSize(...)
  vh.textView.setFontVariationSettings(...)

  // запускаем расчет заранее
  val future = PrecomputedTextCompat.getTextFuture(
    data.text, vh.textView.getTextMetricsParamsCompat(), myExecutor
  )

  // передадим future в TextView, который будет ждать результат до onMeasure()
  vh.textView.setTextFuture(future)
}

Так как RecyclerView во время скролла создает новые элементы, которые еще не видны пользователю, то такое решение будет иметь достаточно времени для выполнения работы в фоне до того, как элемент будет показан пользователю.

Следует помнить, что после вызова метода getTextFuture() нельзя менять стиль текста (например, поставить новый шрифт), в противном случае произойдет исключение, так как значения, с которыми вызывался getTextFuture(), не будут совпадать с теми, которые окажутся в TextView.

Что нужно знать, когда устанавливаешь текст в TextView

При вызове метода TextView.setText() на самом деле внутри создается копия строки:

if (type == SPANNABLE || movementMethod != null) {
  text = spannableFactory.newSpannable(spannable) // Копирование
} else {
  text = new SpannedString(spannable) // Копирование
}

То есть если установить текст со span’ами в TextView, а затем попытаться изменить переданный в setText() объект, то в отображении ничего не произойдет.

Как видно из кода, новый объект создается при помощи фабрики. В TextView имеется возможность заменить фабрику, используемую по-умолчанию, на свою реализацию. Это может быть полезно, чтобы не делать лишних копирований строк. Для этого пишем фабрику, возвращающую тот же объект, и устанавливаем ее в TextView через сеттер spannableFactory:

class MySpannableFactory : Spannable.Factory() {
  override fun newSpannable(source: CharSequence): Spannable {
    return source as? Spannable ?: super.newSpannable(source)
  }
}

textView.spannableFactory = MySpannableFactory()

При установке текста надо не забывать делать вызов textView.setText(spannable, BufferType.SPANNABLE), чтобы происходило обращение к нашей фабрике.

Разработчики Google советуют использовать это решение для отображения текста со span’ами в RecyclerView, чтобы уменьшить потребление ресурсов нашим приложением.

Если текст уже установлен в TextView, и надо добавить новый span, то совсем не нужно делать повторный вызов setText(). Надо просто взять текст из TextView и добавить в него новый span. TextView автоматически слушает spannable-строку на добавление новых span’ов, и перерисовывается:

val spannable = textView.getText() as Spannable
val span = CustomTypefaceSpan(span)

spannable.setSpan(span, ...)

Если же у нас есть span, который стоит в тексте у TextView, то можно обновить значения его параметров и заставить TextView перерисоваться. Если новое изменение не затрагивает размер текста, достаточно вызвать invalidate(), в противном случае – requestLayout():

val spannable = textView.getText() as Spannable
val span = CustomTypefaceSpan(span)

spannable.setSpan(span, ...)

span.setTypeface(anotherTypeface)

textView.requestLayout() // re-measure and re-draw

// or

textView.invalidate() // re-draw

Использование autoLink

В TextView есть возможность автоматического обнаружения ссылок. Для ее включения достаточно указать в разметке атрибут autoLink. При значении autoLink=”web” TextView во время установки нового текста найдет в нем все URL через регулярное выражение и установит на найденные диапазоны символов URLSpan. Вот примерный код, как это происходит в SDK при вызове setText():

spannable = new SpannableString(string);
Matcher m = pattern.matcher(text);

while (...) { // проходимся по всем найденным ссылкам
  String utl = …
  URLSpan span = new URLSpan(url);
  spannable.setSpan(span, ...);
}

Так как это происходит на UI потоке, то не стоит использовать autoLink=”web” внутри элементов RecyclerView. В таком случае лучше вынести определение ссылок в фоновый поток. И здесь на помощь нам приходит класс LinkifyCompat:

// Вытаскиваем ссылки, когда подготавливаем данные к показу на background thread

val spannable = SpannableString(string)
LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS)

// В адаптере RecyclerView
override fun onBindViewHolder(holder: ViewHolder, position: Int) {

  holder.textView.setText(spannable, BufferType.SPANNABLE)

  // ...
}

У autoLink еще есть возможность указать значение map – распознавание почтовых адресов (оно же будет включено при значении all). Эту возможность лучше вообще никогда не использовать. Проблема в том, что под капотом там будет создание экземпляра WebView, через который будет осуществляться поиск адреса! В исходном коде SDK в методе Linkify.gatherMapLinks() можно увидеть такую строку, этот код выполняется на главном потоке:

while ((address = WebView.findAddress(string)) != null) {
        ...
}

А внутри WebView стоит TODO от разработчиков SDK:

public static String findAddress(String addr) {

  // TODO: Rewrite this in Java so it is not needed to start up chromium
  // Could also be deprecated

  return getFactory().getStatics().findAddress(addr);
}

Но что же тогда использовать? Решением будет новая технология Smart Linkify, к сожалению доступная только начиная с Android P (28), которая работает на основе нейронных сетей и распознает различную информацию, в том числе и почтовые адреса. Вот небольшой пример использования:

// UI thread

val text: Spannable = …
val request = TextLinks.Request.Builder(text)
val ref = WeakReference(textView)

executor.execute {

  // background thread
  TextClassifier.generateLinks(request).apply(text)

  val textView = ref.get()
  textView?.post {
    // UI thread
    val textView = ref.get()
    textView?.text = text
  }
}

В отличие старого Linkify, распознанные адреса не будут простыми ссылками. Над адресами при нажатии будет отображаться контекстный toolbar с возможными действиями, например показ адреса на Google карте.

Технология Smart Linkify способна распознавать различные данные: номера телефонов, авиарейсы и многое другое.

Magnifier

Начиная с Android P (28), появился новый элемент управления – Magnifier, который показывает увеличенные символы при выделении текста. С его помощью пользователю гораздо проще установить курсор на нужную позицию.

По умолчанию он работает в TextView, EditText и WebView, но при желании его можно использовать при написании своих элементов пользовательского интерфейса: его API достаточно прост.

Заключение

В данной статье были опущены многие нововведения последних версий Android и смежные темы, заслуживающие отдельных статей, например:

  • работа со стилями и темами при отображении текста
  • работа со шрифтами
  • работа с классами, производными от TextView (например, EditText)

Если кому-то интересна одна из этих тем, рекомендую посмотреть презентацию с прошедшего Google I/O’19 “Best Practices for Using Text in Android”.

Полезные ссылки

Статьи

  • Florina Muntenescu. «Spantastic text styling with Spans»
  • Florina Muntenescu. «Underspanding spans»
  • Florina Muntenescu. «Styling internationalized text in Android»
  • Instagram Engineering. «Improving Comment Rendering on Android»
  • Daniel Lee. «Text rendering on Android»
  • Mariusz Dąbrowski. «What is new in Android P — PrecomputedText»
  • Chet Haase. «RecyclerView Prefetch»
  • Chris Craik. «Prefetch Text Layout in RecyclerView»

Доклады

  • Best practices for text on Android (Google I/O ’18)
  • Use Android Text Like a Pro (Android Dev Summit ’18)
  • Best Practices for Using Text in Android (Google I/O’19)

Общие сведения
Программная установка текста
Программная установка фона
Реагируем на событие onClick
Многострочный текст
Увеличиваем интервалы между строками
Бой с тенью
Создание ссылок автоматом
Совет: Используйте полупрозрачность с умом
Выделить текст для копирования
Стили

Компонент TextView предназначен для отображения текста без возможности редактирования его пользователем, что видно из его названия (Text — текст, view — просмотр).

Находится в разделе Texts.

TextView — один из самых используемых компонентов. С его помощью пользователю удобнее ориентироваться в программе. По сути, это как таблички: Руками не трогать, По газону не ходить, Вход с собаками воспрещен, Часы работы с 9.00 до 18.00 и т.д., и служит для представления пользователю описательного текста.

Для отображения текста в TextView в файле разметки используется атрибут android:text, например:


android:text="Погладь кота, ...!" 

Такой подход является нежелательным. Рекомендуется всегда использовать текстовые ресурсы. В будущем эта привычка позволит вам обеспечить многоязыковую поддержку:


android:text="@string/hello"

Программная установка текста

Программно текст можно задать методом setText():

 
// Инициализируем компонент 
TextView textView = findViewById(R.id.textView);
// задаём текст
textView.setText("Hello Kitty!");
// или с использованием текстовых ресурсов
textView.setText(R.string.hello);

Атрибуты

android:textsize
размер текста. При установке размера текста используется несколько единиц измерения: px (пиксели), dp, sp, in (дюймы), pt, mm. Для текстов рекомендуется использовать sp: android:textSize=»48sp», аналог — метод setTextSize()
android:textstyle
стиль текста. Используются константы: normal, bold, italic. Например, android:textStyle=»bold» выводит текст жирным
android:textcolor
цвет текста. Используются четыре формата в шестнадцатеричной кодировке: #RGB; #ARGB; #RRGGBB; #AARRGGBB, где R, G, B — соответствующий цвет, А — прозрачность (alpha-канал). Значение А, установленное в 0, означает прозрачность 100%.

Для всех вышеперечисленных атрибутов в классе TextView есть соответствующие методы для чтения или задания соответствующих свойств.

Программно установим размеры текста при помощи setTextSize() с различными единицами измерения.


// 20 DIP (Device Independent Pixels)
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);

// 0.5 inch
textView.setTextSize(TypedValue.COMPLEX_UNIT_IN, 0.5f);

// 10 millimeter
textView.setTextSize(TypedValue.COMPLEX_UNIT_MM, 10);

// 30 points
textView.setTextSize(TypedValue.COMPLEX_UNIT_PT, 30);

// 30 raw pixels
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 30);

// 30 scaled pixels
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 30);

TextView

По умолчанию у компонентов TextView отсутствует фоновый цвет. Чтобы задать цвет, укажите значение Drawable для атрибута android:background. В качестве значения Drawable может использоваться изображение или XML-представление фигуры, включающий ресурс Drawable (поместить в папку res/drawable).

Программная установка фона

В некоторых случаях программисты из-за невнимательности неправильно меняют фон элемента программным способом и удивляются, почему ничего не работает.

Предположим, у вас определён в ресурсах зелёный цвет:


<color name="tvBackground">#337700</color>

Следующий код будет ошибочным:


textview.setBackgroundColor(R.color.tvBackground); // не работает

Нужно так (два варианта):


textView.setBackgroundResource(R.color.tvBackground); // первый вариант
textView.setBackgroundColor(getResources().getColor(R.color.tvBackground)); // второй вариант

Реагируем на событие onClick

Если вы хотите, чтобы TextView обрабатывал нажатия (атрибут android:onClick), то не забывайте также использовать в связке атрибут android:clickable=»true». Иначе работать не будет!

Многострочный текст

Если вы хотите создать многострочный текст в TextView, то используйте символы n для переноса строк.

Например, в ресурсах:


<string name="about_text">
    У лукоморья дуб зелёный;n
    Златая цепь на дубе том:n
    И днём и ночью <b>кот учёный</b>n
    Всё ходит по цепи кругом;n
    Идёт <b>направо</b> - песнь заводит,n
    <b>Налево</b> - сказку говорит.</string>

Обратите внимание, что в тексте также применяется простое форматирование.

Также перенос на новую строку можно задать в коде:


textView.setText("Первая строка nВторая строка nТретья строка");

Увеличиваем интервалы между строками

Вы можете управлять интервалом между соседними строчками текста через атрибут android:lineSpacingMultiplier, который является множителем. Установите дробное значение меньше единицы, чтобы сократить интервал или больше единицы, чтобы увеличить интервал между строками.


android:lineSpacingMultiplier="0.8"

Бой с тенью

Чтобы оживить текст, можно дополнительно задействовать атрибуты для создания эффектов тени: shadowColor, shadowDx, shadowDy и shadowRadius. С их помощью вы можете установить цвет тени и ее смещение. Во время установки значений вы не увидите изменений, необходимо запустить пример в эмуляторе или на устройстве. В следующем примере я создал тень красного цвета со смещением в 2 пикселя по вертикали и горизонтали. Учтите, что для смещения используются единицы px (пиксели), единицы dp не поддерживаются.


<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="12dp"
    android:text="Бой с тенью"
    android:textSize="80sp"
    android:textStyle="bold"
    android:shadowColor="#ff0000"
    android:shadowDx="2"
    android:shadowDy="2"
    android:shadowRadius="5"/>

TextView с тенью

Программный эквивалент — метод public void setShadowLayer (float radius, float dx, float dy, int color):


TextView textShadow = (TextView)findViewById(R.id.hello);
textShadow.setShadowLayer(
    5f,   //float radius
    10f,  //float dx
    10f,  //float dy 
    0xFFFFFFFF //int color
);

Создание ссылок автоматом

У TextView есть ещё два интересных свойства Auto link (атрибут autoLink) и Links clickable (атрибут linksClickable), которые позволяют автоматически создавать ссылки из текста.

Выглядит это следующим образом. Предположим, мы присвоим элементу TextView текст Мой сайт: developer.alexanderklimov.ru и применим к нему указанные свойства.


<TextView
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:autoLink="web"
    android:linksClickable="true"
    android:text="Мой адрес: developer.alexanderklimov.ru" />

При этом уже на этапе разработки вы увидите, что строка адреса сайта после слов Мой адрес: стала ссылкой. Если вы запустите приложение и нажмете на ссылку, то откроется браузер с указанным адресом. Вам даже не придется писать дополнительный код. Аналогично, если указать номер телефона (параметр phone), то запустится звонилка.

У ссылки есть интересная особенность — при длительном нажатии на ссылку появляется диалоговое окно, позволяющее скопировать ссылку в буфер обмена.

Атрибут autoLink позволяет комбинировать различные виды ссылок для автоматического распознавания: веб-адрес, email, номер телефона.

Ссылка в TextView

Цвет ссылки можно поменять через свойство Text color link (XML-атрибут textColorLink), а программно через метод setTextLinkColor().

Программно можно установить ссылки на текст через класс Linkify:


TextView tvDisplay = (TextView)findViewById(R.id.tvDisplay);

String data = "" +
        "Пример использования Linkify для создания ссылок в тексте.n" +
        "n" +
        "URL: http://developer.alexanderklimov.ru/ n" +
        "Email: [email protected] n" +
        "Телефон: (495)-458-58-29 n" +
        "Адрес: 10110 ул.Котовского, г.Мышкин n" +
        "n" +
        "Классно получилось?";
 
        if(tvDisplay != null) {
            tvDisplay.setText(data);
            Linkify.addLinks(tvDisplay, Linkify.ALL);
        }

Ссылка в TextView

Кроме константы ALL, можно также использовать Linkify.EMAIL_ADDRESSES, Linkify.MAP_ADDRESSES, Linkify.PHONE_NUMBERS. К сожалению, русские адреса не распознаются. В моём случае индекс был распознан как телефонный номер, а город и улица не стали ссылкой.

В таких случаях придётся самостоятельно добавить ссылки в текстах. Например, определим ссылку в ресурсе:


<string name="my_site"><a href="http://developer.alexanderklimov.ru/android">Самый лучший сайт про android</a></string>

Присвоим созданный ресурс тексту в TextView и запустим пример. Сам текст будет выглядеть как ссылка, но реагировать не будет. Чтобы исправить данную проблему, добавим код:


TextView textView = (TextView) findViewById(R.id.textView);
textView.setMovementMethod(LinkMovementMethod.getInstance());

Ссылки в тексте выглядят не совсем удобными. Есть отдельная библиотека, которая улучшает функциональность. Описание проблем и ссылка на библиотеку есть в статье A better way to handle links in TextView — Saket Narayan.

Совет: Используйте полупрозрачность с умом

Если вам нужно установить текст полупрозрачным, то не используйте атрибут android:alpha:


<TextView 
    android:textColor="#fff"
    android:alpha="0.5" />

Дело в том, что такой подход затрачивает много ресурсов при перерисовке.

Атрибут textColor позволяет установить полупрозрачность без потери производительности:


<TextView 
    android:textColor="80ffffff" />

Выделить текст для копирования

По умолчанию, текст в TextView нельзя выделить для копирования. Но в API 11 появилась такая возможность, которая может пригодиться. Делается либо при помощи XML-атрибута android:textIsSelectable, либо через метод setTextIsSelectable().

Добавьте в разметку два компонента TextView и одно текстовое поле EditText для вставки скопированного текста. У первой текстовой метки установим возможность выделения текста декларативно.


<TextView
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Выдели слово Кот для проверки"
    android:textIsSelectable="true"
    android:textSize="26sp"/>

Для второго компонента возможность выделения создадим программно.


TextView secondTextView = (TextView) findViewById(R.id.textView2);
secondTextView.setTextIsSelectable(true);

Сделайте долгий тап на тексте в любом TextView. Увидите стандартные ползунки для выбора длины текста. Скопируйте текст, сделайте длинный тап в EditText и вставьте текст.

Стили

Выводим разделитель под текстом.


<TextView
    style="?android:listSeparatorTextViewStyle"
    ...
    android:text="Заголовок"/>

Дополнительное чтение

Используем собственные шрифты

Spannable

Продвинутые примеры с TextView

Автоподгонка текста по размеру TextView

Библиотеки

armcha/AutoLinkTextView: AutoLinkTextView is TextView that supports Hashtags (#), Mentions (@) , URLs (http://), Phone and Email automatically detecting and ability to handle clicks. — распознаёт ссылки, номера телефонов, хэштеги.

RomainPiel/Shimmer-android — сияющий текст.

Реклама

A textview gives you many options to display text in your Android app. This tutorial will give you all the information you need to create textviews in XML views or programmatically with Kotlin.

TextView Height and Width

Android provides several ways to specify the width and height of views. As a developer, you either specify the width/height in the XML resource files or you can set these values in your Kotlin files.

Basically, there are 4 options.

Explicit dimensions

You can set the exact dimensions of your width and height by indicating the number and unit. Android supports the following units:

  • px: Pixels – Corresponds to actual pixels on the screen
  • dp: Density-independent Pixels – An abstract unit that is based on the physical density of the screen.
  • sp: Scale-independent Pixels – This is like the dp unit, but it is also scaled by the user’s font size preference.
  • pt: Points – 1/72 of an inch based on the physical size of the screen, assuming a 72dpi density screen.
  • mm: Millimeters – Based on the physical size of the screen.
  • in: Inches – Based on the physical size of the screen.

XML

<TextView
    android:id="@+id/small"
    android:layout_height="10mm"
    android:layout_width="50mm"
    android:text="Small"
    ...
/>

<TextView
    android:id="@+id/big"
    android:layout_height="50mm"
    android:layout_width="50mm"
    android:text="Big"
    ...
/>

Setting these values in XML is very easy. Note that in the image on the right, we have set some constraints to align the upper to top-left and the bottom view to the bottom-right of the parent. This is not shown in the XML layout above.

Android Textview Width and Height

Code

Setting exact values for the width and the height of a view in Kotlin is not as easy as in XML. The reason is that you must control the layout params class. In there the width and height is given in pixels. So you must translate all units first to correct pixel values.

In our example, we have created an extension function, which calculates the conversion of millimeters to pixels.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val small = findViewById<TextView>(R.id.small)
        val big = findViewById<TextView>(R.id.big)

        val displayMetrics = resources.displayMetrics

        small.layoutParams.width = displayMetrics.mmToPixel(50)
        small.layoutParams.height = displayMetrics.mmToPixel(10)

        big.layoutParams.width = displayMetrics.mmToPixel(50)
        big.layoutParams.height = displayMetrics.mmToPixel(50)
    }
}

fun DisplayMetrics.mmToPixel(value : Int) : Int {
    return (this.xdpi * (1.0f/25.4f) * value).toInt()
}

Match Parent

The second option is to match the dimension of the parent of the view. This option is great if your view needs to be responsive to auto-scaling. It will automatically adapt to any changes of the parent.

You can set the width and height of the textview by indicating match_parent in the XML file or by indicating it programmatically in the constructor of the LayoutParam Class.

XML

<TextView
    android:id="@+id/match"
    android:layout_height="10mm"
    android:layout_width="match_parent"
    android:text="Match Parent Width"
    ...
/>

<TextView
    android:id="@+id/fixed"
    android:layout_height="50mm"
    android:layout_width="50mm"
    android:text="Fixed Size"
    ...
/>

Code

The MATCH_PARENT indicator is a constant integer (-1) that can be accessed by the static field in the ViewGroup.LayoutParams class.

val match = findViewById<TextView>(R.id.match)
val fixed = findViewById<TextView>(R.id.fixed)

match.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
Kotlin Android TextView Match Parent

Wrap Content

Similar to match parent the dimensions can be set by wrapping all the content.

XML

<TextView
    android:id="@+id/longText"
    android:layout_height="10mm"
    android:layout_width="wrap_content"
    android:text="Long Text"
    ...
/>

<TextView
    android:id="@+id/shortText"
    android:layout_height="10mm"
    android:layout_width="wrap_content"
    android:text="S"
    ...
/>
Kotlin Android TextView Wrap Content

By Constraints

Text Content

The main property of a text view is naturally the text content. You can bind the content within XML by setting the “android:text” field. You can either write directly your text or you can reference it within a lookup value from a different resource file. This would be the preferred way if the text must not be dynamically available. In Kotlin code you can directly set the text property as shown below.

XML

<TextView
    ...
    android:id="@+id/view"
    android:text="Hello World!"
/>

Code

val view = findViewById<TextView>(R.id.view)
view.text = "Changed Hello World"
Android TextView in Kotlin

Text Size

The textsize determines how small or big the text will be displayed on your android phone. Ideally, this value should be defined in a resource file. You can set the textSize directly in the XML or programmatically.

XML

<TextView
	android:id="@+id/viewSmall"
	android:text="Small"
	android:textSize="14sp"
	...
/>

<TextView
	android:id="@+id/viewBig"
	android:text="Big"
	android:textSize="60sp"
	...
/>

Code

val viewSmall= findViewById<TextView>(R.id.viewSmall)
val viewBig= findViewById<TextView>(R.id.viewBig)

viewSmall.textSize = 14F
viewBig.textSize = 65F
Android TextView with custom TextSize

Font Family

XML

<TextView
	android:id="@+id/viewSans"
	android:fontFamily="sans-serif"
	...
/>

<TextView
	android:id="@+id/viewCasual"
	android:fontFamily="casual"
	...
/>

Code

Text Color

The Android framework provides several ways to set the text color, either statically in XML or programmatically in code.

SetTextColor from RGB / HEX colors

The first option is to use hardcoded RGB, RGBA or HEX colors. You can write them directly in your XML file. Or you can specify the text color programmatically in your Android app. When using the HEX, RGB colors in your Kotlin code, the build in Color class provides some nice feature to parse it.

XML

<TextView
	android:id="@+id/viewBlue"
	android:textColor="#2196F3"
	...
/>

<TextView
	android:id="@+id/viewRed"
	android:textColor="#E91E63"
	...
/>

Code: From HEX Color Code

val viewBlue = findViewById<TextView>(R.id.viewBlue)
val viewRed = findViewById<TextView>(R.id.viewRed)

viewBlue.setTextColor(Color.parseColor("#2196F3"));
viewRed.setTextColor(Color.parseColor("#E91E63"));
Android TextView with custom TextColor

Code: From RGB / RGBA colors

viewBlue.setTextColor(Color.rgb(0, 0, 255));
viewRed.setTextColor(Color.rgb(255, 0, 0));

SetTextColor from predefined values

The second option is to use predefined (named) colors. You can either use internally defined colors (such as Color.Blue) or you can use your own colors, which you can specify in specific resource bundles.

Code: From internal predefined colors

The color class has some build in colors.

viewBlue.setTextColor(Color.BLUE);
viewRed.setTextColor(Color.RED);

Code / XML: From predefined colors

The colors in this example are defined in a resource bundle and can be accessed by the ContextCompat class.

viewBlue.setTextColor(ContextCompat.getColor(this, R.color.blue));
viewRed.setTextColor(ContextCompat.getColor(this, R.color.red));
<resources>
    ...
    <color name="red">#E91E63</color>
    <color name="blue">#2196F3</color>
</resources>

Text Style

With the textstyle you can control if your text is bold, italic or underlined. In XML it is quite straightforward. Programmatically it is not so easy to find, as you need to use the typeface interface.

XML

<TextView
	android:id="@+id/viewBold"
	android:textStyle="bold"
	...
/>

<TextView
	android:id="@+id/viewItalic"
	android:textStyle="italic"
	...
/>

Code

val viewBold = findViewById<TextView>(R.id.viewBold)
val viewItalic = findViewById<TextView>(R.id.viewItalic)

viewBold.setTypeface(null, Typeface.BOLD);
viewItalic.setTypeface(null, Typeface.ITALIC);
Android TextView with custom TextStyle

Text Alignment

Android provides several ways to align the text in you app. The text is aligned with respect to the textview width and height. This means you need to define width / height which is not “wrap_content”. The following option is the easiest way to do it. However, it is recommended to use the Gravity property as it gives you much more control about the text alignment.

XML

<TextView
	android:id="@+id/viewLeft"
	android:layout_width="0dp"
	android:textAlignment="textStart"
	app:layout_constraintEnd_toEndOf="parent"
	app:layout_constraintHorizontal_bias="0.5"
	app:layout_constraintStart_toStartOf="parent"
/>

<TextView
	android:id="@+id/viewRight"
	android:layout_width="0dp"
	android:textAlignment="textEnd"
	app:layout_constraintEnd_toEndOf="parent"
	app:layout_constraintHorizontal_bias="0.5"
	app:layout_constraintStart_toStartOf="parent"
/>

Code

val viewLeft= findViewById<TextView>(R.id.viewLeft)
val viewRight= findViewById<TextView>(R.id.viewRight)

viewLeft.textAlignment = TextView.TEXT_ALIGNMENT_TEXT_START;
viewRight.textAlignment = TextView.TEXT_ALIGNMENT_TEXT_END;

Background Color

XML

The background color can be easily given by using the “android:background” field. It accepts HEX color codes. You can also assign colors from other resources files.

<TextView
	android:id="@+id/viewRed"
	android:background="#E91E63"
	...
/>

<TextView
	android:id="@+id/viewBlue"
	android:background="#3F51B5"
	...
/>

Code

Similar to the text color there are several ways to set programmatically the background color of a textview in Kotlin. You can either use color codes such as RGB/RGBA or HEX. Or you can use predefined colors. With predefined colors you can either use Androids predefined colors or you can create your own color resources.

val viewRed = findViewById<TextView>(R.id.viewRed)
val viewBlue = findViewById<TextView>(R.id.viewBlue)

// RGB Colors
viewRed.setBackgroundColor(Color.rgb(255, 0, 0))
viewBlue.setBackgroundColor(Color.rgb(0, 0, 255))

// HEX Colors
viewRed.setBackgroundColor(Color.parseColor("#E91E63"))
viewBlue.setBackgroundColor(Color.parseColor("3F51B5"))

// Predefined internal Colors
viewRed.setBackgroundColor(Color.RED)
viewBlue.setBackgroundColor(Color.BLUE)

// Predefined XML Colors
viewRed.setTextColor(ContextCompat.getColor(this, R.color.red));
viewBlue.setTextColor(ContextCompat.getColor(this, R.color.blue));

Text alignment with Gravity

The most flexible way in aligning text within your app is to constrain the view and to give it; if necessary some width. Afterward, you can assign one or more gravity parameters to your view, which will affect the text position. You can set the gravity property in the Android XML file, or you can set the gravity parameter programmatically in your Kotlin code. Tip: Your view needs to have the correct width and height so that the gravity parameter works well (you should not use wrap_content).

Gravity Properties

The following options are available to align your text. They can be superimposed (as shown in the example) unless they are not contradictive to each other. For example, you can use Left and Right at the same time.

Bottom Top clip_horizontal
Left Right clip_vertical
center center_horizontal center_vertical
fill fill_horizontal fill_vertical
start end
Available Properties

XML

<TextView
    android:id="@+id/viewTopLeft"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="top|left"
    android:text="TopLeft"
    android:textSize="48sp"
    app:layout_constraintBottom_toTopOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<TextView
    android:id="@+id/viewBottomRight"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="bottom|right"
    android:text="BottomRight"
    android:textSize="48sp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="parent" 
/>

Code

val topLeft = findViewById<TextView>(R.id.viewTopLeft)
val bottomRight = findViewById<TextView>(R.id.viewBottomRight)

topLeft.gravity = Gravity.TOP + Gravity.LEFT
bottomRight.gravity = Gravity.BOTTOM + Gravity.RIGHT
Android Textview Gravity

Within the XML you can specify several gravity properties by using the “|” symbol. In code, you can add (binary mask) the static field values of the public gravity class (LINK).

Text Visibility

XML

The visibility of elements can be specified in the XML resource file with the “visibility” tag and the corresponding values.

<TextView
    android:id="@+id/isVisible"
    android:text="isVisible"
    android:visibility="visible"
/>

<TextView
    android:id="@+id/notVisible"
    android:text="notVisible"
    android:visibility="invisible"
/>

Code

In your Android application, you can programmatically set the visibility of the Kotlin text view by specifying the Enum values, coming from “View”.

val isVisible = findViewById<TextView>(R.id.isVisible)
val notVisible = findViewById<TextView>(R.id.notVisible)

isVisible.visibility = View.VISIBLE;
notVisible.visibility = View.INVISIBLE;
Android Textview Visibility

Text Visibility with Alpha Channel

Another option to control text visibility is to specify the alpha channel. With this option, you even have more control over the visual appearance than the previous option.

The alpha channel is a floating point number with a range of 0.0 – 1.0. If you indicate 0.0 the view will be completely invisible. If you indicate 1.0 the view will be 100 % visible. All numbers in between will make the view somewhat transparent. In our example, we have put the “Invisible” text view to 50 % transparency.

Once again you specify the alpha channel in the XML resource file of your Android app. This option is recommended for static views. The other way is to set the alpha value dynamically from your Kotlin project files.

XML

<TextView
    android:id="@+id/isVisible"
    android:text="isVisible"
    android:alpha="1.0"
/>

<TextView
    android:id="@+id/notVisible"
    android:alpha="0.5"
    android:text="notVisible"
/>

Code

val isVisible = findViewById<TextView>(R.id.isVisible)
val notVisible = findViewById<TextView>(R.id.notVisible)

isVisible.alpha = 1.0f;
notVisible.alpha = 0.5f;
Android textview alpha channel

Clickable Textview

By default a textview is clickable. You can register any OnClickListeners or OnTouchListeners to handle click and touch events.

XML

<TextView
    android:id="@+id/isClickable"
    android:text="isClickable"
    android:clickable="true"    
/>

<TextView
    android:id="@+id/isNotClickable"
    android:text="isNotClickable"
    android:clickable="false"
/>

Code

private var nClicks = 0

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val clickable = findViewById<TextView>(R.id.isVisible)
    val notClickable = findViewById<TextView>(R.id.notVisible)

    clickable.isClickable = true
    clickable.setOnClickListener {
        nClicks++;
        notClickable.text = "Clicked: $nClicks"
    }
}
Android Clickable Textview

Additional Information and References

Official reference guide from Google

  • https://developer.android.com/reference/android/widget/TextView

Other useful material

  • https://github.com/codepath/android_guides/wiki/Working-with-the-TextView

More Content

Android Button in Kotlin

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Android studio как изменить название приложения
  • Android studio как изменить название пакета
  • Android studio как изменить имя приложения
  • An unidentified error occurred ps5 турецкий аккаунт что делать
  • An unidentified error occurred playstation

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии