Java tutorial
/* * Copyright (C) 2016 Borja Bravo ?lvarez * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.borjabravo.readmoretextview; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build; import android.support.v4.content.ContextCompat; import android.support.v7.widget.AppCompatTextView; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.util.AttributeSet; import android.view.View; import android.view.ViewTreeObserver; /** * Created by borja on 17/4/16. */ public class ReadMoreTextView extends AppCompatTextView { private static final int TRIM_MODE_LINES = 0; private static final int TRIM_MODE_LENGTH = 1; private static final int DEFAULT_TRIM_LENGTH = 240; private static final int DEFAULT_TRIM_LINES = 2; private static final int INVALID_END_INDEX = -1; private static final boolean DEFAULT_SHOW_TRIM_EXPANDED_TEXT = true; private static final String ELLIPSIZE = "... "; private CharSequence text; private BufferType bufferType; private boolean readMore = true; private int trimLength; private CharSequence trimCollapsedText; private CharSequence trimExpandedText; private ReadMoreClickableSpan viewMoreSpan; private int colorClickableText; private boolean showTrimExpandedText; private int trimMode; private int lineEndIndex; private int trimLines; public ReadMoreTextView(Context context) { this(context, null); } public ReadMoreTextView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ReadMoreTextView); this.trimLength = typedArray.getInt(R.styleable.ReadMoreTextView_trimLength, DEFAULT_TRIM_LENGTH); int resourceIdTrimCollapsedText = typedArray.getResourceId(R.styleable.ReadMoreTextView_trimCollapsedText, R.string.read_more); int resourceIdTrimExpandedText = typedArray.getResourceId(R.styleable.ReadMoreTextView_trimExpandedText, R.string.read_less); this.trimCollapsedText = getResources().getString(resourceIdTrimCollapsedText); this.trimExpandedText = getResources().getString(resourceIdTrimExpandedText); this.trimLines = typedArray.getInt(R.styleable.ReadMoreTextView_trimLines, DEFAULT_TRIM_LINES); this.colorClickableText = typedArray.getColor(R.styleable.ReadMoreTextView_colorClickableText, ContextCompat.getColor(context, R.color.accent)); this.showTrimExpandedText = typedArray.getBoolean(R.styleable.ReadMoreTextView_showTrimExpandedText, DEFAULT_SHOW_TRIM_EXPANDED_TEXT); this.trimMode = typedArray.getInt(R.styleable.ReadMoreTextView_trimMode, TRIM_MODE_LINES); typedArray.recycle(); viewMoreSpan = new ReadMoreClickableSpan(); onGlobalLayoutLineEndIndex(); setText(); } public void setText() { super.setText(getDisplayableText(), bufferType); setMovementMethod(LinkMovementMethod.getInstance()); //setHighlightColor(Color.TRANSPARENT); } private CharSequence getDisplayableText() { return getTrimmedText(text); } @Override public void setText(CharSequence text, BufferType type) { this.text = text; bufferType = type; setText(); } private CharSequence getTrimmedText(CharSequence text) { if (trimMode == TRIM_MODE_LENGTH) { if (text != null && text.length() > trimLength) { if (readMore) { return updateCollapsedText(); } else { return updateExpandedText(); } } } if (trimMode == TRIM_MODE_LINES) { if (lineEndIndex < 0) refreshLineEndIndex(); if (text != null && lineEndIndex > 0) { if (readMore) { return updateCollapsedText(); } else { return updateExpandedText(); } } } return text; } private CharSequence updateCollapsedText() { int trimEndIndex = text.length(); switch (trimMode) { case TRIM_MODE_LINES: trimEndIndex = lineEndIndex - (ELLIPSIZE.length() + trimCollapsedText.length() + 1); break; case TRIM_MODE_LENGTH: trimEndIndex = trimLength + 1; break; } SpannableStringBuilder s; if (trimEndIndex < 0) { s = new SpannableStringBuilder(text, 0, text.length() - 1); } else { s = new SpannableStringBuilder(text, 0, trimEndIndex).append(ELLIPSIZE).append(trimCollapsedText); } return addClickableSpan(s, trimCollapsedText); } private CharSequence updateExpandedText() { if (showTrimExpandedText) { SpannableStringBuilder s = new SpannableStringBuilder(text, 0, text.length()).append(trimExpandedText); return addClickableSpan(s, trimExpandedText); } return text; } private CharSequence addClickableSpan(SpannableStringBuilder s, CharSequence trimText) { s.setSpan(viewMoreSpan, s.length() - trimText.length(), s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return s; } public void setTrimLength(int trimLength) { this.trimLength = trimLength; setText(); } public void setColorClickableText(int colorClickableText) { this.colorClickableText = colorClickableText; } public void setTrimCollapsedText(CharSequence trimCollapsedText) { this.trimCollapsedText = trimCollapsedText; } public void setTrimExpandedText(CharSequence trimExpandedText) { this.trimExpandedText = trimExpandedText; } public void setTrimMode(int trimMode) { this.trimMode = trimMode; } public void setTrimLines(int trimLines) { this.trimLines = trimLines; } private class ReadMoreClickableSpan extends ClickableSpan { @Override public void onClick(View widget) { readMore = !readMore; setText(); } @Override public void updateDrawState(TextPaint ds) { ds.setColor(colorClickableText); } } private void onGlobalLayoutLineEndIndex() { if (trimMode == TRIM_MODE_LINES) { getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { ViewTreeObserver obs = getViewTreeObserver(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { obs.removeOnGlobalLayoutListener(this); } else { obs.removeGlobalOnLayoutListener(this); } refreshLineEndIndex(); setText(); } }); } } private void refreshLineEndIndex() { try { if (trimLines == 0) { lineEndIndex = getLayout().getLineEnd(0); } else if (trimLines > 0 && getLineCount() >= trimLines) { lineEndIndex = getLayout().getLineEnd(trimLines - 1); } else { lineEndIndex = INVALID_END_INDEX; } } catch (Exception e) { e.printStackTrace(); } } }