package org.solovyev.android.views.dragbutton; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Typeface; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.support.v4.graphics.ColorUtils; import android.text.TextPaint; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.TextView; import java.util.EnumMap; import java.util.Map; import static android.graphics.Color.BLACK; import static android.util.TypedValue.COMPLEX_UNIT_DIP; import static android.util.TypedValue.applyDimension; public class DirectionTextView { public static final float DEF_ALPHA = 0.4f; public static final float DEF_SCALE = 0.4f; public static final float SHADOW_RADIUS_DPS = 2; @NonNull private final Map<DragDirection, Text> texts = new EnumMap<>(DragDirection.class); public DirectionTextView() { } public void init(@NonNull TextView view, @Nullable AttributeSet attrs) { init(view, attrs, view.getPaint()); } public void setBaseTextPaint(@NonNull TextPaint baseTextPaint) { for (Text text : texts.values()) { text.initPaint(baseTextPaint); } } public void init(@NonNull View view, @Nullable AttributeSet attrs, @NonNull TextPaint baseTextPaint) { final Context context = view.getContext(); final int defColor = baseTextPaint.getColor(); final int defPadding = context.getResources().getDimensionPixelSize(R.dimen.drag_direction_text_default_padding); final float minTextSize = context.getResources().getDimensionPixelSize(R.dimen.drag_direction_text_min_size); if (attrs == null) { for (DragDirection direction : DragDirection.values()) { final Text text = new Text(direction, view, minTextSize); text.init(baseTextPaint, null, DEF_SCALE, defColor, DEF_ALPHA, defPadding); texts.put(direction, text); } return; } final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DirectionText); final float scale = array.getFloat(R.styleable.DirectionText_directionTextScale, DEF_SCALE); final float alpha = array.getFloat(R.styleable.DirectionText_directionTextAlpha, DEF_ALPHA); final int color = array.getColor(R.styleable.DirectionText_directionTextColor, defColor); final int padding = array.getDimensionPixelSize(R.styleable.DirectionText_directionTextPadding, defPadding); for (DragDirection direction : DragDirection.values()) { final Text text = new Text(direction, view, minTextSize); text.init(baseTextPaint, array, scale, color, alpha, padding); texts.put(direction, text); } array.recycle(); } public void draw(@NonNull Canvas canvas) { for (Text text : texts.values()) { text.draw(canvas); } } @NonNull public Text getText(@NonNull DragDirection direction) { return texts.get(direction); } public void setHighContrast(boolean highContrast) { for (Text text : texts.values()) { text.setHighContrast(highContrast); } } public static class Text { public final Rect bounds = new Rect(); @NonNull private final TextPaint paint = new TextPaint(); @NonNull private final DragDirection direction; @NonNull private final View view; private final float minTextSize; @NonNull private final PointF offset = new PointF(0, 0); private float fixedTextHeight = 0; @NonNull private String value = ""; private float scale; private int color; private int contrastColor; private float alpha; private boolean visible = true; private boolean highContrast; private int padding; public Text(@NonNull DragDirection direction, @NonNull View view, float minTextSize) { this.direction = direction; this.view = view; this.minTextSize = minTextSize; } public void init(@NonNull TextPaint base, @Nullable TypedArray array, float defScale, int defColor, float defAlpha, int defPadding) { if (array != null) { if (array.hasValue(direction.textAttr)) { value = nullToEmpty(array.getString(direction.textAttr)); } padding = array.getDimensionPixelSize(direction.paddingAttr, defPadding); scale = array.getFloat(direction.scaleAttr, defScale); } else { value = ""; scale = defScale; padding = defPadding; } alpha = defAlpha; color = defColor; contrastColor = makeContrastColor(color); initPaint(base); } @NonNull private String nullToEmpty(@Nullable String s) { return s == null ? "" : s; } private int makeContrastColor(int color) { final int colorRes = isLightColor(color) ? R.color.drag_button_text : R.color.drag_text_inverse; return ContextCompat.getColor(view.getContext(), colorRes); } public void initPaint(@NonNull TextPaint base) { paint.set(base); paint.setColor(color); paint.setAlpha(intAlpha()); final Typeface typeface = base.getTypeface(); if (typeface != null && typeface.getStyle() != Typeface.NORMAL) { paint.setTypeface(Typeface.create(typeface, Typeface.NORMAL)); } // pre-calculate fixed height paint.setTextSize(Math.max(base.getTextSize() * DEF_SCALE, minTextSize)); paint.getTextBounds("|", 0, 1, bounds); fixedTextHeight = bounds.height(); // set real text size value paint.setTextSize(Math.max(base.getTextSize() * scale, minTextSize)); initPaintShadow(); invalidate(true); } private void initPaintShadow() { if (highContrast && needsShadow(color)) { paint.setShadowLayer(applyDimension(COMPLEX_UNIT_DIP, SHADOW_RADIUS_DPS, view.getResources().getDisplayMetrics()), 0, 0, BLACK); } else { paint.setShadowLayer(0, 0, 0, BLACK); } } private int intAlpha() { return (int) (255 * alpha); } public void setVisible(boolean visible) { if (this.visible == visible) { return; } this.visible = visible; invalidate(false); } public void setColor(int color) { setColor(color, alpha); } public void setAlpha(float alpha) { setColor(color, alpha); } public void setHighContrast(boolean highContrast) { if (this.highContrast == highContrast) { return; } this.highContrast = highContrast; initPaintShadow(); invalidate(false); } public void setColor(int color, float alpha) { if (this.color == color && this.alpha == alpha) { return; } this.color = color; this.contrastColor = makeContrastColor(color); this.alpha = alpha; paint.setColor(color); paint.setAlpha(intAlpha()); initPaintShadow(); invalidate(false); } private void invalidate(boolean remeasure) { view.invalidate(); if (remeasure) { offset.set(0, 0); } } public void draw(@NonNull Canvas canvas) { if (!hasValue()) { return; } if (offset.x == 0 && offset.y == 0) { calculatePosition(); } if (highContrast) { paint.setColor(contrastColor); paint.setAlpha(255); } final int width = view.getWidth(); final int height = view.getHeight(); switch (direction) { case up: canvas.drawText(value, width + offset.x, offset.y, paint); break; case down: canvas.drawText(value, width + offset.x, height + offset.y, paint); break; case left: canvas.drawText(value, offset.x, height / 2 + offset.y, paint); break; case right: canvas.drawText(value, width + offset.x, height / 2 + offset.y, paint); break; } if (highContrast) { paint.setColor(color); paint.setAlpha(intAlpha()); } } private void calculatePosition() { paint.getTextBounds(value, 0, value.length(), bounds); final int paddingLeft = padding; final int paddingRight = padding; final int paddingTop = padding; final int paddingBottom = padding; switch (direction) { case up: case down: offset.x = -paddingLeft - bounds.width() - bounds.left; if (direction == DragDirection.up) { offset.y = paddingTop + fixedTextHeight; } else { offset.y = -paddingBottom; } break; case left: case right: if (direction == DragDirection.left) { offset.x = paddingLeft; } else { offset.x = -paddingRight - bounds.width(); } offset.y = (paddingTop - paddingBottom) / 2 + fixedTextHeight / 2; break; } } @NonNull public String getValue() { return visible ? value : ""; } public void setValue(@NonNull String value) { if (TextUtils.equals(this.value, value)) { return; } this.value = value; invalidate(true); } public boolean hasValue() { return visible && !TextUtils.isEmpty(value); } } private static boolean isLightColor(@ColorInt int color) { return ColorUtils.calculateLuminance(color) > 0.5f; } public static boolean needsShadow(@ColorInt int color) { return isLightColor(color); } }