0

I have a bunch of text that may be way too long to fit on the user's screen. So instead of wasting space I want to only show a short excerpt of it and allow the user to expand it. The design for this functionality calls for a short piece of coloured text at the end of this excerpt ("Show More...").

I am wondering how I can:

  • Colour that bit of text a different colour than the rest of the text.
  • Change the colour of that bit of text depending on the state of the TextTiew. Blue if the user isn't interacting with it and red while the user is pressing it.

Is this possible and how would I go about doing it?

Note: I will be hooking up the whole TextView as a button in a sense.

JPvdMerwe
  • 3,328
  • 3
  • 27
  • 32

3 Answers3

2

You can extend TextView with overriding ellipsize behavior like I've done (based on code from this question: android ellipsize multiline textview)

import android.content.Context;
import android.graphics.Canvas;
import android.text.Layout;
import android.text.Layout.Alignment;
import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;


public class EllipsizingTextView extends TextView {
    private static final char ELLIPSIS = '…';

    public interface EllipsizeListener {
        void ellipsizeStateChanged(boolean ellipsized);
    }

    private final List<EllipsizeListener> ellipsizeListeners = new ArrayList<EllipsizeListener>();
    private volatile boolean isEllipsized;
    private volatile boolean isStale;
    private volatile boolean programmaticChange;
    private CharSequence fullCharSequence;
    private int maxLines = -1;
    private float lineSpacingMultiplier = 1.0f;
    private float lineAdditionalVerticalPadding = 0.0f;

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

    public EllipsizingTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAttributes(context, attrs);
    }

    public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initAttributes(context, attrs);
    }

    private void initAttributes(Context context, AttributeSet attrs) {
        maxLines = attrs.getAttributeIntValue("http://schemas.android.com/apk/res/android", "maxLines", -1);
        if (maxLines != -1) {
            isStale = true;
        }
    }

    public void addEllipsizeListener(EllipsizeListener listener) {
        if (listener == null) {
            throw new NullPointerException();
        }
        ellipsizeListeners.add(listener);
    }

    public void removeEllipsizeListener(EllipsizeListener listener) {
        ellipsizeListeners.remove(listener);
    }

    public boolean isEllipsized() {
        return isEllipsized;
    }

    public void setMaxLines(int maxLines) {
        if (getMaxLines() != maxLines) {
            super.setMaxLines(maxLines);
            this.maxLines = maxLines;
            isStale = true;
        }
    }

    public int getMaxLines() {
        return maxLines;
    }

    public void setLineSpacing(float add, float mult) {
        this.lineAdditionalVerticalPadding = add;
        this.lineSpacingMultiplier = mult;
        super.setLineSpacing(add, mult);
    }

    protected void onTextChanged(CharSequence text, int start, int before, int after) {
        super.onTextChanged(text, start, before, after);
        if (!programmaticChange) {
            fullCharSequence = text;
            isStale = true;
        }
    }

    protected void onDraw(Canvas canvas) {
        if (isStale) {
            super.setEllipsize(null);
            resetText();
        }
        super.onDraw(canvas);
    }

    private void resetText() {
        int maxLines = getMaxLines();
        boolean ellipsized = false;
        SpannableStringBuilder builder = SpannableStringBuilder.valueOf(fullCharSequence);

        if (maxLines != -1) {
            Layout layout = createWorkingLayout(builder);
            if (layout.getLineCount() > maxLines) {
                int end = layout.getLineEnd(maxLines - 1);
                int nextSpace = builder.toString().indexOf(' ', end);
                if (nextSpace != -1) {
                    builder.delete(end, builder.length());
                }
                builder.append(ELLIPSIS);
                while (createWorkingLayout(builder).getLineCount() > maxLines) {
                    int pos = builder.toString().lastIndexOf(' ');
                    builder.delete(pos, builder.length());
                    builder.append(ELLIPSIS);
                }
                ellipsized = true;
            }
        }

        if (!TextUtils.equals(builder, getText())) {
            programmaticChange = true;
            try {
                setText(builder);
            } finally {
                programmaticChange = false;
            }
        }
        isStale = false;
        if (ellipsized != isEllipsized) {
            isEllipsized = ellipsized;
            for (EllipsizeListener listener : ellipsizeListeners) {
                listener.ellipsizeStateChanged(ellipsized);
            }
        }
    }

    private Layout createWorkingLayout(CharSequence workingText) {
        return new StaticLayout(workingText, getPaint(), getWidth() - getPaddingLeft() - getPaddingRight(),
                Alignment.ALIGN_NORMAL, lineSpacingMultiplier, lineAdditionalVerticalPadding, false);
    }

    public void setEllipsize(TruncateAt where) {
        // Ellipsize settings are not respected
    }

    public int getLineCount() {
        return Math.min(super.getLineCount(), getMaxLines());
    }

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (changed) {
            isStale = true;
        }
        super.onLayout(changed, left, top, right, bottom);
    }
}

You can replace ELLIPSIS with your string (like "Show more") and add ForegroundColorSpan or BackgroundColorSpan for this part of text. Be careful with spaces in this case (it can lead you to infinite loop).

Community
  • 1
  • 1
OleGG
  • 8,589
  • 1
  • 28
  • 34
  • Works perfect. Thanks. But it ignores all spannables because of using builder.toString(). I had to remove toString() and use indexOf from CharSequence (I'm using Kotlin) to make use of spannables. – muthuraj Nov 17 '17 at 10:18
2

You're looking for SpannableString

specifically ForegroundColorSpan

Community
  • 1
  • 1
josephus
  • 8,284
  • 1
  • 37
  • 57
0

different colored text in one textview can be achieved by fromHTML method. and user feedback for touch state can be achieved by onTouchListener setting up to textview. but in onTouchListener you will need to code that make changes text color.

lulumeya
  • 1,619
  • 11
  • 14