/* * Copyright 2016 Gleb Godonoga. * * 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.andrada.sitracker.ui.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.text.Layout; import android.text.StaticLayout; import android.text.TextUtils; import android.util.AttributeSet; import android.widget.TextView; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; /** * A simple {@link android.widget.TextView} subclass that uses {@link android.text.TextUtils#ellipsize(CharSequence, * android.text.TextPaint, float, android.text.TextUtils.TruncateAt, boolean, * android.text.TextUtils.EllipsizeCallback)} to truncate the displayed text. This is used in * {@link com.andrada.sitracker.ui.components.PublicationItemView} when displaying publication description * which is converted from HTML to a {@link android.text.SpannableString} and sometimes causes * issues for the built-in TextView ellipsize function. */ public class EllipsizedTextView extends TextView { private static final String ELLIPSIS = "..."; private final List<EllipsizeListener> ellipsizeListeners = new ArrayList<EllipsizeListener>(); private boolean isEllipsized; private boolean isStale; private boolean programmaticChange; private String fullText; private int maxLines = -1; private float lineSpacingMultiplier = 1.0f; private float lineAdditionalVerticalPadding = 0.0f; public EllipsizedTextView(@NotNull Context context) { this(context, null, 0); } public EllipsizedTextView(@NotNull Context context, @NotNull AttributeSet attrs) { this(context, attrs, 0); } public EllipsizedTextView(@NotNull Context context, @NotNull AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // Attribute initialization final TypedArray a = context.obtainStyledAttributes(attrs, new int[]{ android.R.attr.maxLines }, defStyle, 0); maxLines = a.getInteger(0, 2); a.recycle(); } 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; } private void resetText() { int maxLines = getMaxLines(); String workingText = fullText; boolean ellipsized = false; if (maxLines != -1) { Layout layout = createWorkingLayout(workingText); if (layout.getLineCount() > maxLines) { workingText = fullText.substring(0, layout.getLineEnd(maxLines - 1)).trim(); while (createWorkingLayout(workingText + ELLIPSIS).getLineCount() > maxLines) { int lastSpace = workingText.lastIndexOf(' '); if (lastSpace == -1) { break; } workingText = workingText.substring(0, lastSpace); } workingText = workingText + ELLIPSIS; ellipsized = true; } } if (!workingText.equals(getText())) { programmaticChange = true; try { setText(workingText); } finally { programmaticChange = false; } } isStale = false; if (ellipsized != isEllipsized) { isEllipsized = ellipsized; for (EllipsizeListener listener : ellipsizeListeners) { listener.ellipsizeStateChanged(ellipsized); } } } @Override protected void onDraw(Canvas canvas) { if (isStale) { super.setEllipsize(null); resetText(); } super.onDraw(canvas); } private Layout createWorkingLayout(String workingText) { return new StaticLayout(workingText, getPaint(), getWidth() - getPaddingLeft() - getPaddingRight(), Layout.Alignment.ALIGN_NORMAL, lineSpacingMultiplier, lineAdditionalVerticalPadding, false); } public interface EllipsizeListener { void ellipsizeStateChanged(boolean ellipsized); } @Override public void setMaxLines(int maxLines) { super.setMaxLines(maxLines); this.maxLines = maxLines; isStale = true; } public int getMaxLines() { return maxLines; } @Override public void setEllipsize(TextUtils.TruncateAt where) { // Ellipsize settings are not respected } @Override public void setLineSpacing(float add, float mult) { this.lineAdditionalVerticalPadding = add; this.lineSpacingMultiplier = mult; super.setLineSpacing(add, mult); } @Override protected void onTextChanged(CharSequence text, int start, int before, int after) { super.onTextChanged(text, start, before, after); if (!programmaticChange) { fullText = text.toString(); isStale = true; } } }