JPvdMerwe
JPvdMerwe

Reputation: 3363

Change the colour of a section of text in a TextView depending on it's state

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:

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.

Upvotes: 0

Views: 604

Answers (3)

josephus
josephus

Reputation: 8304

You're looking for SpannableString

specifically ForegroundColorSpan

Upvotes: 2

OleGG
OleGG

Reputation: 8657

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).

Upvotes: 2

lulumeya
lulumeya

Reputation: 1628

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.

Upvotes: 0

Related Questions