package com.tylersuehr.esr; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.GravityCompat; import android.support.v4.view.ViewCompat; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.util.DisplayMetrics; import android.view.Gravity; /** * Copyright © 2017 Tyler Suehr * * Subclass of {@link AbstractStateDisplay} that contains properties for two texts (one above * the other one), a title and a subtitle perhaps, and affords an API to draw and manipulate * them. * * @author Tyler Suehr * @version 1.0 */ public class TextStateDisplay extends AbstractStateDisplay { private boolean textLayoutsConfigured = false; /* Properties for the title text */ private final TextPaint titlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); private StaticLayout titleLayout; private String title; /* Properties for the subtitle text */ private final TextPaint subtitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); private StaticLayout subtitleLayout; private String subtitle; /* Stores the gravity for the text */ private int textGravity = Gravity.CENTER; /* Space between the title and subtitle texts */ private int titleSpacing; public TextStateDisplay(Context c) { this(c, "", ""); } public TextStateDisplay(Context c, @NonNull String title, @Nullable String subtitle) { DisplayMetrics dm = c.getResources().getDisplayMetrics(); // Setup default sizes final int large = (int)(16f * dm.density); setPadding(large, large, large, large); this.titleSpacing = (int)(4f * dm.scaledDensity); // Setup title defaults this.titlePaint.setTextAlign(Paint.Align.CENTER); this.titlePaint.setTextSize(18f * dm.scaledDensity); this.titlePaint.setColor(Color.BLACK); this.title = title; // Default to a single line of text this.titleLayout = new StaticLayout(title, titlePaint, (int)titlePaint.measureText(title), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0, false); // Setup subtitle defaults this.subtitlePaint.setTextSize(14f * dm.scaledDensity); this.subtitlePaint.setTextAlign(Paint.Align.CENTER); this.subtitlePaint.setColor(Color.GRAY); this.subtitle = subtitle; // Default to a single line of text this.subtitleLayout = new StaticLayout(subtitle, subtitlePaint, (int)subtitlePaint.measureText(subtitle), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0, false); } @Override public void onDrawState(EmptyStateRecyclerView rv, Canvas canvas) { final int width = rv.getMeasuredWidth(); final int height = rv.getMeasuredHeight(); configureTextLayouts(width); // Account for vertical text gravity final int verticalGravity = textGravity&Gravity.VERTICAL_GRAVITY_MASK; float dy; switch (verticalGravity) { case Gravity.CENTER_VERTICAL: dy = (height >> 1) - ((int)getFullTextHeight() >> 1); break; case Gravity.BOTTOM: dy = height - getFullTextHeight(); break; default: case Gravity.TOP: dy = 0; break; } dy += getPaddingTop(); final int horizontalGravity = Gravity.getAbsoluteGravity(textGravity, ViewCompat.getLayoutDirection(rv))&Gravity.HORIZONTAL_GRAVITY_MASK; // Draw the title text canvas.save(); canvas.translate( getDx(width, horizontalGravity, titlePaint, titleLayout), dy); this.titleLayout.draw(canvas); canvas.restore(); // Add spacing for under the text with the title spacing dy += titleLayout.getHeight() + titleSpacing; // Draw the subtitle text under the title text canvas.save(); canvas.translate( getDx(width, horizontalGravity, subtitlePaint, subtitleLayout), dy); this.subtitleLayout.draw(canvas); canvas.restore(); } @Override public void setPadding(int left, int top, int right, int bottom) { super.setPadding(left, top, right, bottom); invalidateText(); } /** * Sets the overall alignment for the text (supports all gravity variations * except for RIGHT and END). * @param gravity Text gravity */ public void setTextGravity(int gravity) { // Let's adjust paint alignment to fit horizontal gravity (END not supported) final int horizontalGravity = Gravity.getAbsoluteGravity(gravity, ViewCompat.LAYOUT_DIRECTION_LTR)&Gravity.HORIZONTAL_GRAVITY_MASK; switch (horizontalGravity) { case GravityCompat.START: this.titlePaint.setTextAlign(Paint.Align.LEFT); this.subtitlePaint.setTextAlign(Paint.Align.LEFT); break; case Gravity.CENTER_HORIZONTAL: this.titlePaint.setTextAlign(Paint.Align.CENTER); this.subtitlePaint.setTextAlign(Paint.Align.CENTER); break; } this.textGravity = gravity; // No invalidation needed } /** * Sets the spacing between the title and subtitle texts. * @param spacing Spacing */ public void setTitleSpacing(int spacing) { this.titleSpacing = spacing; // No invalidation needed } /** * Sets the text color of the title text. * @param color Title text color */ public void setTitleTextColor(@ColorInt int color) { this.titlePaint.setColor(color); invalidateText(); } /** * Sets the text size of the title text. * @param textSize Title text size */ public void setTitleTextSize(float textSize) { this.titlePaint.setTextSize(textSize); invalidateText(); } /** * Sets the paint alignment that draws the title. * @param align {@link android.graphics.Paint.Align} */ public void setTitleTextAlign(Paint.Align align) { this.titlePaint.setTextAlign(align); invalidateText(); } /** * Sets the title text to be drawn. * @param title Title */ public void setTitle(String title) { this.title = title; invalidateText(); } /** * Sets the text color of the subtitle text. * @param color Subtitle text color */ public void setSubtitleTextColor(@ColorInt int color) { this.subtitlePaint.setColor(color); invalidateText(); } /** * Sets the text size of the subtitle text. * @param textSize Subtitle text size */ public void setSubtitleTextSize(float textSize) { this.subtitlePaint.setTextSize(textSize); invalidateText(); } /** * Sets the paint alignment that draws the subtitle. * @param align {@link android.graphics.Paint.Align} */ public void setSubtitleTextAlign(Paint.Align align) { this.subtitlePaint.setTextAlign(align); invalidateText(); } /** * Sets the subtitle to be drawn. * @param subtitle Subtitle */ public void setSubtitle(String subtitle) { this.subtitle = subtitle; invalidateText(); } /** * Sets the typeface used to draw the title and subtitle texts. * @param typeface {@link Typeface} */ public void setTypeface(Typeface typeface) { this.titlePaint.setTypeface(typeface); this.subtitlePaint.setTypeface(typeface); invalidateText(); } private void invalidateText() { this.textLayoutsConfigured = false; } private float getFullTextHeight() { return titleLayout.getHeight() // Height of all title lines (could be more than 1) + subtitleLayout.getHeight() // Height of all subtitle lines (could be more than 1) + titleSpacing + getPaddingTop() + getPaddingBottom(); // Spacing with top & bottom padding } private float getDx(final int width, final int horizontalGravity, final Paint paint, final StaticLayout layout) { final boolean centered = paint.getTextAlign() == Paint.Align.CENTER; final float dx; switch (horizontalGravity) { // No support for GravityCompat.END case Gravity.CENTER_HORIZONTAL: dx = (width >> 1) - (centered ? 0 : (layout.getWidth() >> 1) - getPaddingLeft()); break; default: case GravityCompat.START: dx = getPaddingLeft(); break; } return dx; } private void configureTextLayouts(final int availableWidth) { if (!textLayoutsConfigured) { final int totalNeededPadding = getPaddingLeft() + getPaddingRight(); // Create new static layout only if needed! if ((titleLayout.getWidth() + totalNeededPadding) > availableWidth) { this.titleLayout = new StaticLayout(title, titlePaint, availableWidth - totalNeededPadding, Layout.Alignment.ALIGN_NORMAL, 1.15f, 0, false); } // Create new static layout only if needed! if ((subtitleLayout.getWidth() + totalNeededPadding) > availableWidth) { this.subtitleLayout = new StaticLayout(subtitle, subtitlePaint, availableWidth - totalNeededPadding, Layout.Alignment.ALIGN_NORMAL, 1.15f, 0, false); } textLayoutsConfigured = true; } } }