diff --git a/CalligraphySample/src/main/java/uk/co/chrisjenx/calligraphy/sample/CalligraphyApplication.java b/CalligraphySample/src/main/java/uk/co/chrisjenx/calligraphy/sample/CalligraphyApplication.java index 7af7454..eab1578 100644 --- a/CalligraphySample/src/main/java/uk/co/chrisjenx/calligraphy/sample/CalligraphyApplication.java +++ b/CalligraphySample/src/main/java/uk/co/chrisjenx/calligraphy/sample/CalligraphyApplication.java @@ -16,6 +16,7 @@ public void onCreate() { CalligraphyConfig.initDefault(new CalligraphyConfig.Builder() .setDefaultFontPath("fonts/Roboto-ThinItalic.ttf") .setFontAttrId(R.attr.fontPath) + .addCustomViewWithSetTypeface(CustomViewWithTypefaceSupport.class) .addCustomStyle(TextField.class, R.attr.textFieldStyle) .build() ); diff --git a/CalligraphySample/src/main/java/uk/co/chrisjenx/calligraphy/sample/CustomViewWithTypefaceSupport.java b/CalligraphySample/src/main/java/uk/co/chrisjenx/calligraphy/sample/CustomViewWithTypefaceSupport.java new file mode 100644 index 0000000..7b54681 --- /dev/null +++ b/CalligraphySample/src/main/java/uk/co/chrisjenx/calligraphy/sample/CustomViewWithTypefaceSupport.java @@ -0,0 +1,75 @@ +package uk.co.chrisjenx.calligraphy.sample; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; + +/** + * @author Dmitriy Tarasov + */ +public class CustomViewWithTypefaceSupport extends View { + + private Paint paint; + private Rect textBounds; + private int width; + private int height; + + public CustomViewWithTypefaceSupport(Context context) { + super(context); + init(); + } + + public CustomViewWithTypefaceSupport(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CustomViewWithTypefaceSupport(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CustomViewWithTypefaceSupport(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + paint = new Paint(); + paint.setTextSize(50); + textBounds = new Rect(); + } + + @Override + protected void onDraw(Canvas canvas) { + String text = "This is a custom view with setTypeface support"; + Paint.FontMetrics fm = paint.getFontMetrics(); + paint.getTextBounds(text, 0, text.length(), textBounds); + + width = textBounds.left + textBounds.right + getPaddingLeft() + getPaddingRight(); + height = (int) (Math.abs(fm.top) + fm.bottom); + + canvas.drawText(text, 0, -fm.top + getPaddingTop(), paint); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(width, height); + } + + /** + * Used by Calligraphy to change view's typeface + */ + @SuppressWarnings("unused") + public void setTypeface(Typeface tf) { + paint.setTypeface(tf); + invalidate(); + } +} diff --git a/CalligraphySample/src/main/res/layout/fragment_main.xml b/CalligraphySample/src/main/res/layout/fragment_main.xml index 4c7bc46..7dff524 100644 --- a/CalligraphySample/src/main/res/layout/fragment_main.xml +++ b/CalligraphySample/src/main/res/layout/fragment_main.xml @@ -72,6 +72,11 @@ android:layout_height="wrap_content" android:text="@string/defined_custom_view"/> + + , Integer> mClassStyleAttributeMap; + /** + * Collection of custom non-{@code TextView}'s registered for applying typeface during inflation + * @see uk.co.chrisjenx.calligraphy.CalligraphyConfig.Builder#addCustomViewWithSetTypeface(Class) + */ + private final Set> hasTypefaceViews; protected CalligraphyConfig(Builder builder) { mIsFontSet = builder.isFontSet; @@ -111,9 +123,11 @@ protected CalligraphyConfig(Builder builder) { mAttrId = builder.attrId; mReflection = builder.reflection; mCustomViewCreation = builder.customViewCreation; + mCustomViewTypefaceSupport = builder.customViewTypefaceSupport; final Map, Integer> tempMap = new HashMap<>(DEFAULT_STYLES); tempMap.putAll(builder.mStyleClassMap); mClassStyleAttributeMap = Collections.unmodifiableMap(tempMap); + hasTypefaceViews = Collections.unmodifiableSet(builder.mHasTypefaceClasses); } /** @@ -138,6 +152,14 @@ public boolean isCustomViewCreation() { return mCustomViewCreation; } + public boolean isCustomViewTypefaceSupport() { + return mCustomViewTypefaceSupport; + } + + public boolean isCustomViewHasTypeface(View view) { + return hasTypefaceViews.contains(view.getClass()); + } + /* default */ Map, Integer> getClassStyles() { return mClassStyleAttributeMap; } @@ -162,6 +184,10 @@ public static class Builder { * Use Reflection to intercept CustomView inflation with the correct Context. */ private boolean customViewCreation = true; + /** + * Use Reflection during view creation to try change typeface via setTypeface method if it exists + */ + private boolean customViewTypefaceSupport = false; /** * The fontAttrId to look up the font path from. */ @@ -179,6 +205,8 @@ public static class Builder { */ private Map, Integer> mStyleClassMap = new HashMap<>(); + private Set> mHasTypefaceClasses = new HashSet<>(); + /** * This defaults to R.attr.fontPath. So only override if you want to use your own attrId. * @@ -275,6 +303,15 @@ public Builder addCustomStyle(final Class styleClass, final return this; } + /** + * Register custom non-{@code TextView}'s which implement {@code setTypeface} so they can have the Typeface applied during inflation. + */ + public Builder addCustomViewWithSetTypeface(Class clazz) { + customViewTypefaceSupport = true; + mHasTypefaceClasses.add(clazz); + return this; + } + public CalligraphyConfig build() { this.isFontSet = !TextUtils.isEmpty(fontAssetPath); return new CalligraphyConfig(this); diff --git a/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/CalligraphyFactory.java b/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/CalligraphyFactory.java index ca45b62..dbff457 100644 --- a/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/CalligraphyFactory.java +++ b/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/CalligraphyFactory.java @@ -3,6 +3,7 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; +import android.graphics.Typeface; import android.os.Build; import android.text.TextUtils; import android.util.AttributeSet; @@ -11,6 +12,8 @@ import android.view.ViewTreeObserver; import android.widget.TextView; +import java.lang.reflect.Method; + class CalligraphyFactory { private static final String ACTION_BAR_TITLE = "action_bar_title"; @@ -124,18 +127,8 @@ void onViewCreatedInternal(View view, final Context context, AttributeSet attrs) // Try to get typeface attribute value // Since we're not using namespace it's a little bit tricky - // Try view xml attributes - String textViewFont = CalligraphyUtils.pullFontPathFromView(context, attrs, mAttributeId); - - // Try view style attributes - if (TextUtils.isEmpty(textViewFont)) { - textViewFont = CalligraphyUtils.pullFontPathFromStyle(context, attrs, mAttributeId); - } - - // Try View TextAppearance - if (TextUtils.isEmpty(textViewFont)) { - textViewFont = CalligraphyUtils.pullFontPathFromTextAppearance(context, attrs, mAttributeId); - } + // Check xml attrs, style attrs and text appearance for font path + String textViewFont = resolveFontPath(context, attrs); // Try theme attributes if (TextUtils.isEmpty(textViewFont)) { @@ -178,7 +171,50 @@ public void onGlobalLayout() { } }); } + + // Try to set typeface for custom views using interface method or via reflection if available + if (view instanceof HasTypeface) { + Typeface typeface = getDefaultTypeface(context, resolveFontPath(context, attrs)); + if (typeface != null) { + ((HasTypeface) view).setTypeface(typeface); + } + } else if (CalligraphyConfig.get().isCustomViewTypefaceSupport() && CalligraphyConfig.get().isCustomViewHasTypeface(view)) { + final Method setTypeface = ReflectionUtils.getMethod(view.getClass(), "setTypeface"); + String fontPath = resolveFontPath(context, attrs); + Typeface typeface = getDefaultTypeface(context, fontPath); + if (setTypeface != null && typeface != null) { + ReflectionUtils.invokeMethod(view, setTypeface, typeface); + } + } } + private Typeface getDefaultTypeface(Context context, String fontPath) { + if (TextUtils.isEmpty(fontPath)) { + fontPath = CalligraphyConfig.get().getFontPath(); + } + if (!TextUtils.isEmpty(fontPath)) { + return TypefaceUtils.load(context.getAssets(), fontPath); + } + return null; + } + + /** + * Resolving font path from xml attrs, style attrs or text appearance + */ + private String resolveFontPath(Context context, AttributeSet attrs) { + // Try view xml attributes + String textViewFont = CalligraphyUtils.pullFontPathFromView(context, attrs, mAttributeId); + // Try view style attributes + if (TextUtils.isEmpty(textViewFont)) { + textViewFont = CalligraphyUtils.pullFontPathFromStyle(context, attrs, mAttributeId); + } + + // Try View TextAppearance + if (TextUtils.isEmpty(textViewFont)) { + textViewFont = CalligraphyUtils.pullFontPathFromTextAppearance(context, attrs, mAttributeId); + } + + return textViewFont; + } } diff --git a/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/HasTypeface.java b/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/HasTypeface.java new file mode 100644 index 0000000..48950f9 --- /dev/null +++ b/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/HasTypeface.java @@ -0,0 +1,22 @@ +package uk.co.chrisjenx.calligraphy; + +import android.graphics.Typeface; + +/** + * There are two ways to set typeface for custom views: + *
    + *
  • Implementing this interface. You should only implements {@link #setTypeface(Typeface)} method.
  • + *
  • Or via reflection. If custom view already has setTypeface method you can + * register it during Calligraphy configuration + * {@link uk.co.chrisjenx.calligraphy.CalligraphyConfig.Builder#addCustomViewWithSetTypeface(Class)}
  • + *
+ * First way is faster but encourage more effort from the developer to implements interface. Second one + * requires less effort but works slowly cause reflection calls. + * + * @author Dmitriy Tarasov + */ +public interface HasTypeface { + + void setTypeface(Typeface typeface); + +} diff --git a/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/ReflectionUtils.java b/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/ReflectionUtils.java index 923a20d..99c0d86 100644 --- a/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/ReflectionUtils.java +++ b/calligraphy/src/main/java/uk/co/chrisjenx/calligraphy/ReflectionUtils.java @@ -1,5 +1,7 @@ package uk.co.chrisjenx.calligraphy; +import android.util.Log; + import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -10,6 +12,8 @@ */ class ReflectionUtils { + private static final String TAG = ReflectionUtils.class.getSimpleName(); + static Field getField(Class clazz, String fieldName) { try { final Field f = clazz.getDeclaredField(fieldName); @@ -50,8 +54,8 @@ static void invokeMethod(Object object, Method method, Object... args) { try { if (method == null) return; method.invoke(object, args); - } catch (IllegalAccessException | InvocationTargetException ignored) { - ignored.printStackTrace(); + } catch (IllegalAccessException | InvocationTargetException e) { + Log.d(TAG, "Can't invoke method using reflection", e); } } }