From 2c8e16936345d7dd50df18ad4968c48c361e81e0 Mon Sep 17 00:00:00 2001 From: Sergey Solovyev Date: Wed, 7 Jun 2017 21:06:15 +0200 Subject: [PATCH] Cache Paints in drag buttons --- .../android/calculator/BaseActivity.java | 2 +- .../calculator/keyboard/BaseKeyboardUi.java | 16 +- .../org/solovyev/android/views/Adjuster.java | 6 +- .../views/dragbutton/DirectionDragButton.java | 44 +-- .../dragbutton/DirectionDragImageButton.java | 24 +- .../views/dragbutton/DirectionDragView.java | 2 +- .../views/dragbutton/DirectionText.java | 200 ++++++++++++ .../views/dragbutton/DirectionTextView.java | 306 +++--------------- .../android/views/dragbutton/PaintCache.java | 215 ++++++++++++ 9 files changed, 495 insertions(+), 320 deletions(-) create mode 100644 dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionText.java create mode 100644 dragbutton/src/main/java/org/solovyev/android/views/dragbutton/PaintCache.java diff --git a/app/src/main/java/org/solovyev/android/calculator/BaseActivity.java b/app/src/main/java/org/solovyev/android/calculator/BaseActivity.java index 6dfc3e0f..f353aa70 100644 --- a/app/src/main/java/org/solovyev/android/calculator/BaseActivity.java +++ b/app/src/main/java/org/solovyev/android/calculator/BaseActivity.java @@ -96,7 +96,7 @@ public abstract class BaseActivity extends AppCompatActivity implements SharedPr if (view instanceof TextView) { final TextView textView = (TextView) view; final Typeface oldTypeface = textView.getTypeface(); - if (oldTypeface == newTypeface) { + if (oldTypeface != null && oldTypeface.equals(newTypeface)) { return; } final int style = oldTypeface != null ? oldTypeface.getStyle() : Typeface.NORMAL; diff --git a/app/src/main/java/org/solovyev/android/calculator/keyboard/BaseKeyboardUi.java b/app/src/main/java/org/solovyev/android/calculator/keyboard/BaseKeyboardUi.java index 98799cf4..8801cfcb 100644 --- a/app/src/main/java/org/solovyev/android/calculator/keyboard/BaseKeyboardUi.java +++ b/app/src/main/java/org/solovyev/android/calculator/keyboard/BaseKeyboardUi.java @@ -1,5 +1,12 @@ package org.solovyev.android.calculator.keyboard; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING; +import static android.view.HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING; +import static android.view.HapticFeedbackConstants.KEYBOARD_TAP; +import static org.solovyev.android.calculator.App.cast; +import static org.solovyev.android.calculator.Preferences.Gui.Mode.simple; + import android.app.Activity; import android.app.Application; import android.content.Context; @@ -40,13 +47,6 @@ import javax.inject.Inject; import dagger.Lazy; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING; -import static android.view.HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING; -import static android.view.HapticFeedbackConstants.KEYBOARD_TAP; -import static org.solovyev.android.calculator.App.cast; -import static org.solovyev.android.calculator.Preferences.Gui.Mode.simple; - public abstract class BaseKeyboardUi implements SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener { public static float getTextScale(@NonNull Context context) { @@ -212,7 +212,7 @@ public abstract class BaseKeyboardUi implements SharedPreferences.OnSharedPrefer private static class AdjusterHelper implements Adjuster.Helper { - public static AdjusterHelper instance = new AdjusterHelper(); + public static final AdjusterHelper instance = new AdjusterHelper(); @Override public void apply(@NonNull DirectionDragImageButton view, float textSize) { diff --git a/app/src/main/java/org/solovyev/android/views/Adjuster.java b/app/src/main/java/org/solovyev/android/views/Adjuster.java index 4b015c6c..badc339f 100644 --- a/app/src/main/java/org/solovyev/android/views/Adjuster.java +++ b/app/src/main/java/org/solovyev/android/views/Adjuster.java @@ -1,5 +1,7 @@ package org.solovyev.android.views; +import static android.graphics.Matrix.MSCALE_Y; + import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -11,13 +13,11 @@ import android.view.ViewTreeObserver; import android.widget.ImageView; import android.widget.TextView; -import static android.graphics.Matrix.MSCALE_Y; - public class Adjuster { private static final float[] MATRIX = new float[9]; @NonNull - private static Helper textViewHelper = new Helper() { + private static final Helper textViewHelper = new Helper() { @Override public void apply(@NonNull TextView view, float textSize) { view.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); diff --git a/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragButton.java b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragButton.java index a4341270..a51b2bbe 100644 --- a/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragButton.java +++ b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragButton.java @@ -2,22 +2,14 @@ package org.solovyev.android.views.dragbutton; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Typeface; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.text.TextPaint; import android.util.AttributeSet; -import static android.graphics.Color.BLACK; -import static android.util.TypedValue.COMPLEX_UNIT_DIP; -import static android.util.TypedValue.applyDimension; -import static org.solovyev.android.views.dragbutton.DirectionTextView.SHADOW_RADIUS_DPS; - public class DirectionDragButton extends DragButton implements DirectionDragView { private final DirectionTextView textView = new DirectionTextView(); - @NonNull - private final TextPaint baseTextPaint = new TextPaint(); - private boolean highContrast; public DirectionDragButton(Context context) { super(context); @@ -41,20 +33,11 @@ public class DirectionDragButton extends DragButton implements DirectionDragView private void init(@Nullable AttributeSet attrs) { textView.init(this, attrs); - baseTextPaint.set(getPaint()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - final TextPaint paint = getPaint(); - if (baseTextPaint.getTextSize() != paint.getTextSize() || - baseTextPaint.getTypeface() != paint.getTypeface() || - baseTextPaint.getColor() != paint.getColor() || - baseTextPaint.getAlpha() != paint.getAlpha()) { - baseTextPaint.set(paint); - textView.setBaseTextPaint(paint); - } textView.draw(canvas); } @@ -69,9 +52,21 @@ public class DirectionDragButton extends DragButton implements DirectionDragView return this; } + @Override + public void setTypeface(Typeface tf, int style) { + super.setTypeface(tf, style); + textView.setTypeface(getPaint().getTypeface()); + } + + @Override + public void setTextSize(int unit, float size) { + super.setTextSize(unit, size); + textView.setTextSize(getPaint().getTextSize()); + } + @Override @NonNull - public DirectionTextView.Text getText(@NonNull DragDirection direction) { + public DirectionText getText(@NonNull DragDirection direction) { return textView.getText(direction); } @@ -93,15 +88,6 @@ public class DirectionDragButton extends DragButton implements DirectionDragView @Override public void setHighContrast(boolean highContrast) { - if(this.highContrast == highContrast) { - return; - } - this.highContrast = highContrast; - this.textView.setHighContrast(highContrast); - if (highContrast && DirectionTextView.needsShadow(getCurrentTextColor())) { - setShadowLayer(applyDimension(COMPLEX_UNIT_DIP, SHADOW_RADIUS_DPS, getResources().getDisplayMetrics()), 0, 0, BLACK); - } else { - setShadowLayer(0, 0, 0, BLACK); - } + textView.setHighContrast(highContrast); } } diff --git a/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragImageButton.java b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragImageButton.java index e18cb5d3..39694780 100644 --- a/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragImageButton.java +++ b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragImageButton.java @@ -5,13 +5,11 @@ import android.graphics.Canvas; import android.graphics.Typeface; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.text.TextPaint; import android.util.AttributeSet; import android.widget.TextView; public class DirectionDragImageButton extends DragImageButton implements DirectionDragView { private final DirectionTextView textView = new DirectionTextView(); - private final TextPaint baseTextPaint = new TextPaint(); public DirectionDragImageButton(Context context) { super(context); @@ -35,8 +33,7 @@ public class DirectionDragImageButton extends DragImageButton implements Directi private void init(@Nullable AttributeSet attrs) { final TextView view = new TextView(getContext(), attrs); - baseTextPaint.set(view.getPaint()); - textView.init(this, attrs, baseTextPaint); + textView.init(this, attrs, view.getPaint()); } @Override @@ -46,29 +43,24 @@ public class DirectionDragImageButton extends DragImageButton implements Directi } @NonNull - public DirectionTextView.Text getText(@NonNull DragDirection direction) { + public DirectionText getText(@NonNull DragDirection direction) { return textView.getText(direction); } - public void setTypeface(@NonNull Typeface newTypeface) { - final Typeface oldTypeface = baseTextPaint.getTypeface(); - if (oldTypeface == newTypeface) { - return; - } - baseTextPaint.setTypeface(newTypeface); - textView.setBaseTextPaint(baseTextPaint); + public void setTypeface(@NonNull Typeface typeface) { + textView.setTypeface(typeface); } - public void setTextSize(float textSizePxs) { - baseTextPaint.setTextSize(textSizePxs); - textView.setBaseTextPaint(baseTextPaint); + public void setTextSize(float textSize) { + textView.setTextSize(textSize); } public float getTextSize() { - return baseTextPaint.getTextSize(); + return textView.getTextSize(); } @Override public void setHighContrast(boolean highContrast) { + textView.setHighContrast(highContrast); } } diff --git a/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragView.java b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragView.java index 9b90edbf..467b7bc9 100644 --- a/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragView.java +++ b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionDragView.java @@ -4,5 +4,5 @@ import android.support.annotation.NonNull; public interface DirectionDragView extends DragView { @NonNull - DirectionTextView.Text getText(@NonNull DragDirection direction); + DirectionText getText(@NonNull DragDirection direction); } diff --git a/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionText.java b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionText.java new file mode 100644 index 00000000..a2cfe66c --- /dev/null +++ b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionText.java @@ -0,0 +1,200 @@ +package org.solovyev.android.views.dragbutton; + +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.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.view.View; + +public class DirectionText { + + static final float DEF_SCALE = 0.4f; + private static final Rect TMP = new Rect(); + @NonNull + private final DragDirection direction; + @NonNull + private final View view; + private final float minTextSize; + @NonNull + private final PointF offset = new PointF(Integer.MIN_VALUE, Integer.MIN_VALUE); + @NonNull + private final PaintCache paintCache; + @NonNull + private PaintCache.Entry entry; + @NonNull + private String value = ""; + private boolean visible = true; + private int padding; + private float scale; + private float baseTextSize; + + public DirectionText(@NonNull DragDirection direction, @NonNull View view, + float minTextSize) { + this.direction = direction; + this.view = view; + this.minTextSize = minTextSize; + this.paintCache = PaintCache.get(); + } + + public void init(@Nullable TypedArray array, float defScale, + int defColor, float defAlpha, int defPadding, @NonNull Typeface defTypeface, + float textSize) { + baseTextSize = textSize; + 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; + } + final PaintCache.Spec spec = new PaintCache.Spec(defColor, defAlpha, + defTypeface, scaledTextSize(textSize, scale), false); + entry = paintCache.get(view.getContext(), spec); + } + + @NonNull + private String nullToEmpty(@Nullable String s) { + return s == null ? "" : s; + } + + private float scaledTextSize(float textSize, float scale) { + return Math.max(textSize * scale, minTextSize); + } + + public void setVisible(boolean visible) { + if (this.visible == visible) { + return; + } + this.visible = visible; + invalidate(false); + } + + private void invalidate(boolean remeasure) { + view.invalidate(); + if (remeasure) { + offset.set(Integer.MIN_VALUE, Integer.MIN_VALUE); + } + } + + public void setColor(int color) { + setColor(color, entry.spec.alpha); + } + + private void setColor(int color, float alpha) { + if (entry.spec.color == color && entry.spec.alpha == alpha) { + return; + } + entry = paintCache.get(view.getContext(), entry.spec.color(color, alpha)); + invalidate(false); + } + + public void setAlpha(float alpha) { + setColor(entry.spec.color, alpha); + } + + void setHighContrast(boolean highContrast) { + if (entry.spec.highContrast == highContrast) { + return; + } + entry = paintCache.get(view.getContext(), entry.spec.highContrast(highContrast)); + invalidate(false); + } + + public void setTypeface(@NonNull Typeface typeface) { + if (entry.spec.typeface.equals(typeface)) { + return; + } + entry = paintCache.get(view.getContext(), entry.spec.typeface(typeface)); + invalidate(true); + } + + void draw(@NonNull Canvas canvas) { + if (!hasValue()) { + return; + } + if (offset.x == Integer.MIN_VALUE || offset.y == Integer.MIN_VALUE) { + calculatePosition(); + } + final int width = view.getWidth(); + final int height = view.getHeight(); + switch (direction) { + case up: + canvas.drawText(value, width + offset.x, offset.y, entry.paint); + break; + case down: + canvas.drawText(value, width + offset.x, height + offset.y, entry.paint); + break; + case left: + canvas.drawText(value, offset.x, height / 2 + offset.y, entry.paint); + break; + case right: + canvas.drawText(value, width + offset.x, height / 2 + offset.y, entry.paint); + break; + } + } + + boolean hasValue() { + return visible && !TextUtils.isEmpty(value); + } + + private void calculatePosition() { + TMP.setEmpty(); + entry.paint.getTextBounds(value, 0, value.length(), TMP); + + 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 - TMP.width() - TMP.left; + if (direction == DragDirection.up) { + offset.y = paddingTop + entry.getFixedTextHeight(scaledTextSize(baseTextSize, DEF_SCALE)); + } else { + offset.y = -paddingBottom; + } + break; + case left: + case right: + if (direction == DragDirection.left) { + offset.x = paddingLeft; + } else { + offset.x = -paddingRight - TMP.width(); + } + offset.y = (paddingTop - paddingBottom) / 2 + entry.getFixedTextHeight(scaledTextSize(baseTextSize, DEF_SCALE)) / 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 void setBaseTextSize(float baseTextSize) { + if (this.baseTextSize == baseTextSize) { + return; + } + this.baseTextSize = baseTextSize; + entry = paintCache.get(view.getContext(), entry.spec.textSize(scaledTextSize(baseTextSize, scale))); + invalidate(true); + } +} diff --git a/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionTextView.java b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionTextView.java index 389f85c3..1ed1a418 100644 --- a/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionTextView.java +++ b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/DirectionTextView.java @@ -1,18 +1,13 @@ package org.solovyev.android.views.dragbutton; import android.content.Context; +import android.content.res.Resources; 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; @@ -20,297 +15,84 @@ 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; +class DirectionTextView { -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; + static final float SHADOW_RADIUS_DPS = 2; + private static final float DEF_ALPHA = 0.4f; @NonNull - private final Map texts = new EnumMap<>(DragDirection.class); + private final Map texts = new EnumMap<>(DragDirection.class); + private float textSize; + private Typeface typeface; - public DirectionTextView() { + 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 base) { + textSize = base.getTextSize(); + typeface = base.getTypeface() == null ? Typeface.DEFAULT : base.getTypeface(); - 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); + final Resources res = context.getResources(); - - 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); + final float minTextSize = + res.getDimensionPixelSize(R.dimen.drag_direction_text_min_size); + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DirectionText); + final float scale = + a.getFloat(R.styleable.DirectionText_directionTextScale, DirectionText.DEF_SCALE); + final float alpha = a.getFloat(R.styleable.DirectionText_directionTextAlpha, DEF_ALPHA); + final int color = a.getColor(R.styleable.DirectionText_directionTextColor, base.getColor()); + final int padding = a.getDimensionPixelSize(R.styleable.DirectionText_directionTextPadding, + res.getDimensionPixelSize(R.dimen.drag_direction_text_default_padding)); for (DragDirection direction : DragDirection.values()) { - final Text text = new Text(direction, view, minTextSize); - text.init(baseTextPaint, array, scale, color, alpha, padding); + final DirectionText text = new DirectionText(direction, view, minTextSize); + text.init(a, scale, color, alpha, padding, typeface, textSize); texts.put(direction, text); } - array.recycle(); + a.recycle(); } - public void draw(@NonNull Canvas canvas) { - for (Text text : texts.values()) { + void draw(@NonNull Canvas canvas) { + for (DirectionText text : texts.values()) { text.draw(canvas); } } @NonNull - public Text getText(@NonNull DragDirection direction) { + public DirectionText getText(@NonNull DragDirection direction) { return texts.get(direction); } - public void setHighContrast(boolean highContrast) { - for (Text text : texts.values()) { + void setHighContrast(boolean highContrast) { + for (DirectionText 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 setTypeface(@NonNull Typeface typeface) { + if(this.typeface == typeface) { + return; } - - 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); + for (DirectionText text : texts.values()) { + text.setTypeface(typeface); } } - private static boolean isLightColor(@ColorInt int color) { - return ColorUtils.calculateLuminance(color) > 0.5f; + public float getTextSize() { + return textSize; } - public static boolean needsShadow(@ColorInt int color) { - return isLightColor(color); + public void setTextSize(float textSize) { + if (this.textSize == textSize) { + return; + } + this.textSize = textSize; + for (DirectionText text : texts.values()) { + text.setBaseTextSize(textSize); + } } } diff --git a/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/PaintCache.java b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/PaintCache.java new file mode 100644 index 00000000..f0b229be --- /dev/null +++ b/dragbutton/src/main/java/org/solovyev/android/views/dragbutton/PaintCache.java @@ -0,0 +1,215 @@ +package org.solovyev.android.views.dragbutton; + +import static android.graphics.Color.BLACK; +import static android.util.TypedValue.COMPLEX_UNIT_DIP; +import static android.util.TypedValue.applyDimension; +import static org.solovyev.android.views.dragbutton.DirectionTextView.SHADOW_RADIUS_DPS; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.support.v4.graphics.ColorUtils; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; + +class PaintCache { + + @NonNull + private static final Rect TMP = new Rect(); + private static final String TAG = "PaintCache"; + @NonNull + private static final PaintCache INSTANCE = new PaintCache(); + + static class Entry { + @NonNull + public final Spec spec; + @NonNull + public final Paint paint; + private float lastTextSize; + private float fixedTextHeight; + + Entry(@NonNull Spec spec, @NonNull Paint paint) { + this.spec = spec; + this.paint = paint; + } + + float getFixedTextHeight(float textSize) { + if (lastTextSize == textSize) { + return fixedTextHeight; + } + if (lastTextSize != 0) { + Log.d(TAG, "Remeasuring text for size: " + textSize); + } + final float oldTextSize = paint.getTextSize(); + paint.setTextSize(textSize); + TMP.setEmpty(); + paint.getTextBounds("|", 0, 1, TMP); + paint.setTextSize(oldTextSize); + lastTextSize = textSize; + fixedTextHeight = TMP.height(); + return fixedTextHeight; + } + } + + static class Spec { + @ColorInt + public final int color; + public final float alpha; + @NonNull + public final Typeface typeface; + public final float textSize; + public final boolean highContrast; + + Spec(int color, float alpha, @NonNull Typeface typeface, + float textSize, + boolean highContrast) { + this.color = color; + this.alpha = alpha; + this.typeface = typeface; + this.textSize = textSize; + this.highContrast = highContrast; + } + + private int contrastColor(@NonNull Context context) { + final int colorRes = + isLightColor(color) ? R.color.drag_button_text : R.color.drag_text_inverse; + return ContextCompat.getColor(context, colorRes); + } + + private static boolean isLightColor(@ColorInt int color) { + return ColorUtils.calculateLuminance(color) > 0.5f; + } + + private int intAlpha() { + return (int) (255 * alpha); + } + + private boolean needsShadow() { + return needsShadow(color); + } + + public static boolean needsShadow(@ColorInt int color) { + return isLightColor(color); + } + + @Override + public int hashCode() { + int result = color; + result = 31 * result + (alpha != +0.0f ? Float.floatToIntBits(alpha) : 0); + result = 31 * result + typeface.hashCode(); + result = 31 * result + (textSize != +0.0f ? Float.floatToIntBits(textSize) : 0); + result = 31 * result + (highContrast ? 1 : 0); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final Spec spec = (Spec) o; + + if (color != spec.color) return false; + if (Float.compare(spec.alpha, alpha) != 0) return false; + if (Float.compare(spec.textSize, textSize) != 0) return false; + if (highContrast != spec.highContrast) return false; + return typeface.equals(spec.typeface); + + } + + @NonNull + public Spec highContrast(boolean highContrast) { + return new Spec(color, alpha, typeface, textSize, highContrast); + } + + @NonNull + public Spec color(int color, float alpha) { + return new Spec(color, alpha, typeface, textSize, highContrast); + } + + @NonNull + public Spec typeface(@NonNull Typeface typeface) { + return new Spec(color, alpha, typeface, textSize, highContrast); + } + + @NonNull + public Spec textSize(float textSize) { + return new Spec(color, alpha, typeface, textSize, highContrast); + } + + @Override + public String toString() { + return "Spec{" + + "color=" + color + + ", alpha=" + alpha + + ", typeface=" + System.identityHashCode(typeface) + + ", textSize=" + textSize + + ", highContrast=" + highContrast + + '}'; + } + } + + private float shadowRadius; + @NonNull + private final Map map = new HashMap<>(); + + private void lazyLoad(@NonNull Context context) { + if (shadowRadius != 0) { + return; + } + final Resources res = context.getResources(); + shadowRadius = applyDimension(COMPLEX_UNIT_DIP, SHADOW_RADIUS_DPS, + res.getDisplayMetrics()); + } + + @NonNull + public static PaintCache get() { + return INSTANCE; + } + + @NonNull + public Entry get(@NonNull Context context, @NonNull Spec spec) { + lazyLoad(context); + Entry entry = map.get(spec); + if (entry == null) { + entry = new Entry(spec, makePaint(context, spec)); + map.put(spec, entry); + } else { + Log.d(TAG, "Reusing paint for spec: " + spec); + } + return entry; + } + + @NonNull + private Paint makePaint(@NonNull Context context, @NonNull Spec spec) { + Log.d(TAG, "Creating new paint for spec: " + spec); + final Paint paint = new Paint(); + paint.setAntiAlias(true); + if (spec.highContrast) { + paint.setColor(spec.contrastColor(context)); + paint.setAlpha(255); + } else { + paint.setColor(spec.color); + paint.setAlpha(spec.intAlpha()); + } + if (spec.typeface.getStyle() != Typeface.NORMAL) { + paint.setTypeface(Typeface.create(spec.typeface, Typeface.NORMAL)); + } else { + paint.setTypeface(spec.typeface); + } + paint.setTextSize(spec.textSize); + if (spec.highContrast && spec.needsShadow()) { + paint.setShadowLayer(shadowRadius, 0, 0, BLACK); + } else { + paint.setShadowLayer(0, 0, 0, BLACK); + } + return paint; + } +}