package com.alexvasilkov.android.commons.texts;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.text.style.MetricAffectingSpan;
import android.util.TypedValue;
import android.view.View;
import com.alexvasilkov.android.commons.utils.AppContext;
/**
* SpannableStringBuilder wrapper that allows applying various text styles to single TextView.
* <p/>
* Usage example:<br/>
* <pre>
* CharSequence text = new SpannableBuilder(this)
* .createStyle().setFont("fonts/blessed-day.otf").setColor(Color.RED).setSize(25).apply()
* .append("Part1 ")
* .currentStyle().setColor(Color.GREEN).setUnderline(true).apply()
* .append("Part2 ")
* .createStyle().setColor(Color.BLUE).apply()
* .append("Part3").build();
*
* textView.setText(text);
* </pre>
* <p/>
* In this example: Part1 will use custom font, red color and 25sp font size;
* Part2 will use custom font, green color, 25sp font size and underline;
* Part3 will use blue color only.
*/
public class SpannableBuilder {
private final SpannableStringBuilder mBuilder = new SpannableStringBuilder();
private final Context mAppContext;
private final OnSpanClickListener mClickListener;
private Style mCurrentStyle;
public SpannableBuilder() {
this(AppContext.get());
}
public SpannableBuilder(Context appContext) {
this(appContext, null);
}
public SpannableBuilder(Context appContext, OnSpanClickListener clickListener) {
mAppContext = appContext.getApplicationContext();
mClickListener = clickListener;
}
public Style currentStyle() {
return mCurrentStyle;
}
public Style createStyle() {
return new Style(mAppContext, this);
}
public SpannableBuilder clearStyle() {
mCurrentStyle = null;
return this;
}
public SpannableBuilder append(int stringId) {
return append(stringId, null);
}
public SpannableBuilder append(int stringId, Object clickObject) {
return append(mAppContext.getString(stringId), clickObject);
}
public SpannableBuilder append(CharSequence text) {
return append(text, null);
}
public SpannableBuilder append(CharSequence str, final Object clickObject) {
if (str == null || str.length() == 0) return this;
int length = mBuilder.length();
mBuilder.append(str);
if (clickObject != null && mClickListener != null) {
ClickableSpan clickSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
mClickListener.onSpanClicked(clickObject);
}
};
mBuilder.setSpan(clickSpan, length, length + str.length(), SpannableStringBuilder.SPAN_INCLUSIVE_EXCLUSIVE);
}
if (mCurrentStyle != null) {
Span span = new Span(mCurrentStyle);
mBuilder.setSpan(span, length, length + str.length(), SpannableStringBuilder.SPAN_INCLUSIVE_EXCLUSIVE);
}
return this;
}
public CharSequence build() {
return mBuilder;
}
private static class Span extends MetricAffectingSpan {
private final Style mStyle;
public Span(Style style) {
mStyle = style.clone();
}
@Override
public void updateDrawState(TextPaint ds) {
apply(ds);
}
@Override
public void updateMeasureState(TextPaint paint) {
apply(paint);
}
private void apply(Paint paint) {
if (mStyle.typeface != null) paint.setTypeface(mStyle.typeface);
if (mStyle.color != Style.NO_COLOR) paint.setColor(mStyle.color);
if (mStyle.size != Style.NO_SIZE) paint.setTextSize(mStyle.size);
paint.setUnderlineText(mStyle.underline);
}
}
public static class Style implements Cloneable {
private static final int NO_COLOR = Integer.MIN_VALUE;
private static final float NO_SIZE = Float.MIN_VALUE;
// Note: will be null for cloned object
private final Context context;
private final SpannableBuilder parent;
private Typeface typeface;
private int color = NO_COLOR;
private float size = NO_SIZE;
private boolean underline;
private Style(Context context, SpannableBuilder builer) {
this.context = context;
this.parent = builer;
}
public Style setFont(Typeface typeface) {
this.typeface = typeface;
return this;
}
/**
* For more details see {@link com.alexvasilkov.android.commons.texts.Fonts}
*/
public Style setFont(String fontPath) {
return setFont(Fonts.getTypeface(fontPath, context.getAssets()));
}
/**
* For more details see {@link com.alexvasilkov.android.commons.texts.Fonts}
*/
public Style setFont(int fontStringId) {
return setFont(context.getString(fontStringId));
}
public Style setColor(int color) {
this.color = color;
return this;
}
public Style setColorResId(int colorResId) {
return setColor(context.getResources().getColor(colorResId));
}
public Style setSize(int unit, float value) {
size = TypedValue.applyDimension(unit, value, context.getResources().getDisplayMetrics());
return this;
}
/**
* Setting size as scaled pixels (SP)
*/
public Style setSize(float value) {
return setSize(TypedValue.COMPLEX_UNIT_SP, value);
}
public Style setUnderline(boolean underline) {
this.underline = underline;
return this;
}
public SpannableBuilder apply() {
parent.mCurrentStyle = this;
return parent;
}
@Override
protected Style clone() {
Style style = new Style(null, null);
style.typeface = this.typeface;
style.color = this.color;
style.size = this.size;
style.underline = this.underline;
return style;
}
}
public interface OnSpanClickListener {
void onSpanClicked(Object clickObject);
}
}