package uk.co.chrisjenx.calligraphy; import android.content.Context; import android.content.res.AssetManager; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Paint; import android.graphics.Typeface; import android.text.Editable; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.TypedValue; import android.widget.TextView; /** * Created by chris on 20/12/2013 * Project: Calligraphy */ public final class CalligraphyUtils { public static final int[] ANDROID_ATTR_TEXT_APPEARANCE = new int[]{android.R.attr.textAppearance}; /** * Applies a custom typeface span to the text. * * @param s text to apply it too. * @param typeface typeface to apply. * @return Either the passed in Object or new Spannable with the typeface span applied. */ public static CharSequence applyTypefaceSpan(CharSequence s, Typeface typeface) { if (s != null && s.length() > 0) { if (!(s instanceof Spannable)) { s = new SpannableString(s); } ((Spannable) s).setSpan(TypefaceUtils.getSpan(typeface), 0, s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } return s; } /** * Applies a Typeface to a TextView. * Defaults to false for deferring, if you are having issues with the textview keeping * the custom Typeface, use * {@link #applyFontToTextView(android.widget.TextView, android.graphics.Typeface, boolean)} * * @param textView Not null, TextView or child of. * @param typeface Not null, Typeface to apply to the TextView. * @return true if applied otherwise false. * @see #applyFontToTextView(android.widget.TextView, android.graphics.Typeface, boolean) */ public static boolean applyFontToTextView(final TextView textView, final Typeface typeface) { return applyFontToTextView(textView, typeface, false); } /** * Applies a Typeface to a TextView, if deferred,its recommend you don't call this multiple * times, as this adds a TextWatcher. * * Deferring should really only be used on tricky views which get Typeface set by the system at * weird times. * * @param textView Not null, TextView or child of. * @param typeface Not null, Typeface to apply to the TextView. * @param deferred If true we use Typefaces and TextChange listener to make sure font is always * applied, but this sometimes conflicts with other * {@link android.text.Spannable}'s. * @return true if applied otherwise false. * @see #applyFontToTextView(android.widget.TextView, android.graphics.Typeface) */ public static boolean applyFontToTextView(final TextView textView, final Typeface typeface, boolean deferred) { if (textView == null || typeface == null) return false; textView.setPaintFlags(textView.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG); textView.setTypeface(typeface); if (deferred) { textView.setText(applyTypefaceSpan(textView.getText(), typeface), TextView.BufferType.SPANNABLE); textView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { applyTypefaceSpan(s, typeface); } }); } return true; } /** * Useful for manually fonts to a TextView. Will not default back to font * set in {@link uk.co.chrisjenx.calligraphy.CalligraphyConfig} * * @param context Context * @param textView Not null, TextView to apply to. * @param filePath if null/empty will do nothing. * @return true if fonts been applied */ public static boolean applyFontToTextView(final Context context, final TextView textView, final String filePath) { return applyFontToTextView(context, textView, filePath, false); } static boolean applyFontToTextView(final Context context, final TextView textView, final String filePath, boolean deferred) { if (textView == null || context == null) return false; final AssetManager assetManager = context.getAssets(); final Typeface typeface = TypefaceUtils.load(assetManager, filePath); return applyFontToTextView(textView, typeface, deferred); } static void applyFontToTextView(final Context context, final TextView textView, final CalligraphyConfig config) { applyFontToTextView(context, textView, config, false); } static void applyFontToTextView(final Context context, final TextView textView, final CalligraphyConfig config, boolean deferred) { if (context == null || textView == null || config == null) return; if (!config.isFontSet()) return; applyFontToTextView(context, textView, config.getFontPath(), deferred); } /** * Applies font to TextView. Will fall back to the default one if not set. * * @param context context * @param textView textView to apply to. * @param config Default Config * @param textViewFont nullable, will use Default Config if null or fails to find the * defined font. */ public static void applyFontToTextView(final Context context, final TextView textView, final CalligraphyConfig config, final String textViewFont) { applyFontToTextView(context, textView, config, textViewFont, false); } static void applyFontToTextView(final Context context, final TextView textView, final CalligraphyConfig config, final String textViewFont, boolean deferred) { if (context == null || textView == null || config == null) return; if (!TextUtils.isEmpty(textViewFont) && applyFontToTextView(context, textView, textViewFont, deferred)) { return; } applyFontToTextView(context, textView, config, deferred); } /** * Tries to pull the Custom Attribute directly from the TextView. * * @param context Activity Context * @param attrs View Attributes * @param attributeId if -1 returns null. * @return null if attribute is not defined or added to View */ static String pullFontPathFromView(Context context, AttributeSet attrs, int[] attributeId) { if (attributeId == null || attrs == null) return null; final String attributeName; try { attributeName = context.getResources().getResourceEntryName(attributeId[0]); } catch (Resources.NotFoundException e) { // invalid attribute ID return null; } final int stringResourceId = attrs.getAttributeResourceValue(null, attributeName, -1); return stringResourceId > 0 ? context.getString(stringResourceId) : attrs.getAttributeValue(null, attributeName); } /** * Tries to pull the Font Path from the View Style as this is the next decendent after being * defined in the View's xml. * * @param context Activity Activity Context * @param attrs View Attributes * @param attributeId if -1 returns null. * @return null if attribute is not defined or found in the Style */ static String pullFontPathFromStyle(Context context, AttributeSet attrs, int[] attributeId) { if (attributeId == null || attrs == null) return null; final TypedArray typedArray = context.obtainStyledAttributes(attrs, attributeId); if (typedArray != null) { try { // First defined attribute String fontFromAttribute = typedArray.getString(0); if (!TextUtils.isEmpty(fontFromAttribute)) { return fontFromAttribute; } } catch (Exception ignore) { // Failed for some reason. } finally { typedArray.recycle(); } } return null; } /** * Tries to pull the Font Path from the Text Appearance. * * @param context Activity Context * @param attrs View Attributes * @param attributeId if -1 returns null. * @return returns null if attribute is not defined or if no TextAppearance is found. */ static String pullFontPathFromTextAppearance(final Context context, AttributeSet attrs, int[] attributeId) { if (attributeId == null || attrs == null) { return null; } int textAppearanceId = -1; final TypedArray typedArrayAttr = context.obtainStyledAttributes(attrs, ANDROID_ATTR_TEXT_APPEARANCE); if (typedArrayAttr != null) { try { textAppearanceId = typedArrayAttr.getResourceId(0, -1); } catch (Exception ignored) { // Failed for some reason return null; } finally { typedArrayAttr.recycle(); } } final TypedArray textAppearanceAttrs = context.obtainStyledAttributes(textAppearanceId, attributeId); if (textAppearanceAttrs != null) { try { return textAppearanceAttrs.getString(0); } catch (Exception ignore) { // Failed for some reason. return null; } finally { textAppearanceAttrs.recycle(); } } return null; } /** * Last but not least, try to pull the Font Path from the Theme, which is defined. * * @param context Activity Context * @param styleAttrId Theme style id * @param attributeId if -1 returns null. * @return null if no theme or attribute defined. */ static String pullFontPathFromTheme(Context context, int styleAttrId, int[] attributeId) { if (styleAttrId == -1 || attributeId == null) return null; final Resources.Theme theme = context.getTheme(); final TypedValue value = new TypedValue(); theme.resolveAttribute(styleAttrId, value, true); final TypedArray typedArray = theme.obtainStyledAttributes(value.resourceId, attributeId); try { String font = typedArray.getString(0); return font; } catch (Exception ignore) { // Failed for some reason. return null; } finally { typedArray.recycle(); } } /** * Last but not least, try to pull the Font Path from the Theme, which is defined. * * @param context Activity Context * @param styleAttrId Theme style id * @param subStyleAttrId the sub style from the theme to look up after the first style * @param attributeId if -1 returns null. * @return null if no theme or attribute defined. */ static String pullFontPathFromTheme(Context context, int styleAttrId, int subStyleAttrId, int[] attributeId) { if (styleAttrId == -1 || attributeId == null) return null; final Resources.Theme theme = context.getTheme(); final TypedValue value = new TypedValue(); theme.resolveAttribute(styleAttrId, value, true); int subStyleResId = -1; final TypedArray parentTypedArray = theme.obtainStyledAttributes(value.resourceId, new int[]{subStyleAttrId}); try { subStyleResId = parentTypedArray.getResourceId(0, -1); } catch (Exception ignore) { // Failed for some reason. return null; } finally { parentTypedArray.recycle(); } if (subStyleResId == -1) return null; final TypedArray subTypedArray = context.obtainStyledAttributes(subStyleResId, attributeId); if (subTypedArray != null) { try { return subTypedArray.getString(0); } catch (Exception ignore) { // Failed for some reason. return null; } finally { subTypedArray.recycle(); } } return null; } private static Boolean sToolbarCheck = null; private static Boolean sAppCompatViewCheck = null; /** * See if the user has added appcompat-v7, this is done at runtime, so we only check once. * * @return true if the v7.Toolbar is on the classpath */ static boolean canCheckForV7Toolbar() { if (sToolbarCheck == null) { try { Class.forName("android.support.v7.widget.Toolbar"); sToolbarCheck = Boolean.TRUE; } catch (ClassNotFoundException e) { sToolbarCheck = Boolean.FALSE; } } return sToolbarCheck; } /** * See if the user has added appcompat-v7 with AppCompatViews * * @return true if AppcompatTextView is on the classpath */ static boolean canAddV7AppCompatViews() { if (sAppCompatViewCheck == null) { try { Class.forName("android.support.v7.widget.AppCompatTextView"); sAppCompatViewCheck = Boolean.TRUE; } catch (ClassNotFoundException e) { sAppCompatViewCheck = Boolean.FALSE; } } return sAppCompatViewCheck; } private CalligraphyUtils() { } }