对早期设备使用Roboto字体

时间:2012-03-21 02:35:30

标签: android android-layout android-fonts

我想在我的Android应用程序中使用Roboto字体,并确保它适用于没有安装字体的早期版本的Android。我知道我可以通过使用Typeface.createFromAsset()然后手动设置每个TextViews / Buttons / Other-Objects的字体来完成此操作。对于我在屏幕上显示的每个对象来说,这似乎是一件很大的痛苦。

我的问题是,有更好的方法吗?一些帮助类或在.xml主题文件中设置自定义字体的方法?任何自动化都比手动列出每个屏幕上的每个对象并更改字体更好。

谢谢!

6 个答案:

答案 0 :(得分:30)

以上接受的答案是正确的,但我只是想在这里提供我的实施

我的实用程序类:

package com.example.utils;

import android.content.Context;
import android.graphics.Typeface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class AndroidUtils
{
    private static Typeface robotoTypeFace;

    public static void setRobotoFont (Context context, View view)
    {
        if (robotoTypeFace == null)
        {
            robotoTypeFace = Typeface.createFromAsset(context.getAssets(), "fonts/Roboto/Roboto-Regular.ttf");
        }
        setFont(view, robotoTypeFace);
    }

    private static void setFont (View view, Typeface robotoTypeFace)
    {
        if (view instanceof ViewGroup)
        {
            for (int i = 0; i < ((ViewGroup)view).getChildCount(); i++)
            {
                setFont(((ViewGroup)view).getChildAt(i), robotoTypeFace);
            }
        }
        else if (view instanceof TextView)
        {
            ((TextView) view).setTypeface(robotoTypeFace);
        }
    }
}

如何使用它,假设this是一个活动:

AndroidUtils.setRobotoFont(this, view);

要为所有TextView设置相同的字体,您可以使用活动的decorView:

ViewGroup godfatherView = (ViewGroup)this.getWindow().getDecorView();
AndroidUtils.setRobotoFont(this, godfatherView);

如果你有适配器或片段,也不要忘记设置它们的字体。

另见here

答案 1 :(得分:16)

检索活动中的所有视图,检查其类型并应用适当的操作。

Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/Roboto/Roboto-Regular.ttf");
for (View view : allViews)
{
 if (view instanceof TextView) 
 {
    TextView textView = (TextView) view;
    textView.setTypeface(typeface);
  }
}

答案 2 :(得分:7)

感谢@Jitsu,@ Arnaud和@Pawan M,我提出了我的解决方案,比单独使用它们更好:

/**
 * Adapted from http://stackoverflow.com/a/12387343/450148
 *
 * @author Anton Averin
 * @author Felipe Micaroni Lalli
 */

package net.alouw.alouwCheckin.util;

import android.content.Context;
import android.graphics.Typeface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.EnumMap;
import java.util.Map;

public final class FontUtils {
    private FontUtils() {
    }

    private enum FontType {
        BOLD("fonts/Roboto/Roboto-BoldCondensed.ttf"),
        BOLD_ITALIC("fonts/Roboto/Roboto-BoldCondensedItalic.ttf"),
        NORMAL("fonts/Roboto/Roboto-Condensed.ttf"),
        ITALIC("fonts/Roboto/Roboto-CondensedItalic.ttf");

        private final String path;

        FontType(String path) {
            this.path = path;
        }

        public String getPath() {
            return path;
        }
    }

    /* cache for loaded Roboto typefaces*/
    private static Map<FontType, Typeface> typefaceCache = new EnumMap<FontType, Typeface>(FontType.class);

    /**
     * Creates Roboto typeface and puts it into cache
     */
    private static Typeface getRobotoTypeface(Context context, FontType fontType) {
        String fontPath = fontType.getPath();

        if (!typefaceCache.containsKey(fontType)) {
            typefaceCache.put(fontType, Typeface.createFromAsset(context.getAssets(), fontPath));
        }

        return typefaceCache.get(fontType);
    }

    /**
     * Gets roboto typeface according to passed typeface style settings.
     * <p/>
     * Will get Roboto-Bold for Typeface.BOLD etc
     */
    private static Typeface getRobotoTypeface(Context context, Typeface originalTypeface) {
        FontType robotoFontType = null;

        if (originalTypeface == null) {
            robotoFontType = FontType.NORMAL;
        } else {
            int style = originalTypeface.getStyle();

            switch (style) {
                case Typeface.BOLD:
                    robotoFontType = FontType.BOLD;
                    break;

                case Typeface.BOLD_ITALIC:
                    robotoFontType = FontType.BOLD_ITALIC;
                    break;

                case Typeface.ITALIC:
                    robotoFontType = FontType.ITALIC;
                    break;

                case Typeface.NORMAL:
                    robotoFontType = FontType.NORMAL;
                    break;
            }
        }

        return (robotoFontType == null) ? originalTypeface : getRobotoTypeface(context, robotoFontType);
    }

    /**
     * Walks ViewGroups, finds TextViews and applies Typefaces taking styling in consideration
     *
     * @param context - to reach assets
     * @param view    - root view to apply typeface to
     */
    public static void setRobotoFont(Context context, View view) {
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                setRobotoFont(context, ((ViewGroup) view).getChildAt(i));
            }
        } else if (view instanceof TextView) {
            Typeface currentTypeface = ((TextView) view).getTypeface();
            ((TextView) view).setTypeface(getRobotoTypeface(context, currentTypeface));
        }
    }
}

你onCreate主要活动的最后一件事:

if (Build.VERSION.SDK_INT < 11) {
    ViewGroup godfatherView = (ViewGroup) this.getWindow().getDecorView();
    FontUtils.setRobotoFont(this, godfatherView);
}

在我的自定义视图列表中,上面的代码不起作用,所以我必须这样做:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // (...)

    View view = // build your custom view here

    if (Build.VERSION.SDK_INT < 11) {
        FontUtils.setRobotoFont(activity, view);
    }

    return view;
}

答案 3 :(得分:3)

以下是此解决方案的改进版本。 它可以缓存字体并考虑TextView.textStyle参数设置。所以它可以设置粗体文本。 http://anton.averin.pro/2012/09/12/how-to-use-android-roboto-font-in-honeycomb-and-earlier-versions/

答案 4 :(得分:3)

我使用其他解决方案。我将自定义LayoutInflater.Factory设置为活动。所以我创建后可以完全访问视图。我可以为每个TextView安装自定义字体,而无需在视图层次结构上进行迭代。在您的所有应用程序中使用自定义字体应该做的一件事是在基本活动中调用new Font(...).install()。见下面的exaple。

以下是我的解决方案,其中包含使用示例:

import java.util.Map;

import com.google.common.collect.Maps;

import static com.google.common.base.Preconditions.checkNotNull;

import android.R;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.support.v4.app.FragmentActivity;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

/**
 * Provides an ability to apply custom font to all {@link TextView} and subclasses.
 *
 * To install custom font use method {@link #install(Activity)} in {@link Activity#onCreate(android.os.Bundle)}
 * <b>before</b> calling super.onCreate(Bundle).
 *
 * <p/>Example of usage:
 * <pre>
 * {@code
 * public class BaseActivity extends SherlockFragmentActivity {
 *
 *      protected void onCreate(Bundle state) {
 *          applyCustomFontForPreICS();
 *          super.onCreate(state);
 *      }
 *
 *      private void applyCustomFontForPreICS() {
 *          boolean isPreICS = Build.VERSION.SDK_INT < BUILD_VERSION_CODE_ICE_CREAM_SANDWICH
 *          if (isPreICS) {
 *              new Font(
 *                  "font/roboto_regular.ttf",
 *                  "font/roboto_bold.ttf",
 *                  "font/roboto_italic.ttf",
 *                  "font/roboto_bold_italic.ttf"
 *              ).install(this);
 *          }
 *      }
 * }
 * }
 * </pre>
 * 
 * @author Alexey Danilov (danikula@gmail.com)
 */
public class Font {

    private static final Map<String, Typeface> FONTS = Maps.newHashMap();

    private String regularFontPath;
    private String boldFontPath;
    private String italicFontPath;
    private String boldItalicFontPath;

    /**
     * Creates instance to be used for setting particular font.
     *
     * @param regularPath regular font assets path, must be not {@code null}
     * @param boldPath bold font assets path, must be not {@code null}
     * @param italicPath italic font assets path, must be not {@code null}
     * @param boldItalicPath bold and italic font assets path, must be not {@code null}
     */
    public Font(String regularPath, String boldPath, String italicPath, String boldItalicPath) {
        this.regularFontPath = checkNotNull(regularPath);
        this.boldFontPath = checkNotNull(boldPath);
        this.italicFontPath = checkNotNull(italicPath);
        this.boldItalicFontPath = checkNotNull(boldItalicPath);
    }

    /**
     * Installs custom font to activity.
     *
     * @param activity an activity custom font will be installed to, must be not {@code null}.
     */
    public void install(Activity activity) {
        checkNotNull(activity, "Activity must be not null!");

        LayoutInflater layoutInflater = activity.getLayoutInflater();
        boolean factoryIsEmpty = layoutInflater.getFactory() == null;
        if (!factoryIsEmpty) {
            throw new IllegalStateException("Impossible to use this method for this activity: layout factory is set!");
        }
        layoutInflater.setFactory(new FontLayoutInflaterFactory());
    }

    private Typeface getFont(int type, Context context) {
        switch (type) {
            case Typeface.NORMAL:
                return getFont(context, regularFontPath);
            case Typeface.BOLD:
                return getFont(context, boldFontPath);
            case Typeface.ITALIC:
                return getFont(context, italicFontPath);
            case Typeface.BOLD_ITALIC:
                return getFont(context, boldItalicFontPath);
            default: {
                throw new IllegalArgumentException("Undefined font type " + type);
            }
        }
    }

    private Typeface getFont(Context context, String path) {
        if (FONTS.containsKey(path)) {
            return FONTS.get(path);
        } else {
            Typeface typeface = makeTypeface(context, path);
            FONTS.put(path, typeface);
            return typeface;
        }
    }

    private Typeface makeTypeface(Context context, String path) {
        try {
            return Typeface.createFromAsset(context.getAssets(), path);
        } catch (Exception e) {
            // add user-friendly error message
            throw new IllegalArgumentException(String.format("Error creating font from assets path '%s'", path), e);
        }
    }

    private void applyFontToTextView(Context context, TextView textView, AttributeSet attrs) {
        int[] fontStyleAttributes = {R.attr.textStyle};
        TypedArray typedArray = context.obtainStyledAttributes(attrs, fontStyleAttributes);
        boolean isStyleSpecified = typedArray.getIndexCount() != 0;
        int type = isStyleSpecified ? typedArray.getInt(0, Typeface.NORMAL) : Typeface.NORMAL;
        Typeface font = getFont(type, context);
        textView.setTypeface(font, type);
    }

    private final class FontLayoutInflaterFactory implements LayoutInflater.Factory {

        // to improve perfomance the package with the most usable components should be the first.
        private final String[] ANDROID_UI_COMPONENT_PACKAGES = {
                "android.widget.",
                "android.webkit.",
                "android.view."
        };

        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            try {
                // we install custom LayoutInflater.Factory, so FragmentActivity have no chance set own factory and
                // inflate tag <fragment> in method onCreateView. So  call it explicitly.
                if ("fragment".equals(name) && context instanceof FragmentActivity) {
                    FragmentActivity fragmentActivity = (FragmentActivity) context;
                    return fragmentActivity.onCreateView(name, context, attrs);
                }

                View view = createView(name, attrs, LayoutInflater.from(context));
                if (view == null) {
                    // It's strange! The view is not ours neither android's. May be the package of this view
                    // is not listed in ANDROID_UI_COMPONENT_PACKAGES. Return null for the default behavior.
                    Log.d(LOG_TAG, "Cannot create view with name: " + name);
                    return null;
                }

                if (view instanceof TextView) {
                    TextView textView = (TextView) view;
                    applyFontToTextView(context, textView, attrs);
                }
                return view;
            } catch (InflateException e) {
                Log.e(LOG_TAG, "Error inflating view", e);
                return null;
            } catch (ClassNotFoundException e) {
                Log.e(LOG_TAG, "Error inflating view", e);
                return null;
            }
        }

        private View createView(String name, AttributeSet attrs, LayoutInflater layoutInflater) throws ClassNotFoundException {
            View view = null;
            boolean isAndroidComponent = name.indexOf('.') == -1;
            if (isAndroidComponent) {
                // We don't know package name of the view with the given simple name. Try android ui packages listed in
                // ANDROID_UI_COMPONENT_PACKAGES

                // The same implementation is in the class PhoneLayoutInflater from internal API
                for (String androidPackage : ANDROID_UI_COMPONENT_PACKAGES) {
                    try {
                        view = layoutInflater.createView(name, androidPackage, attrs);
                        if (view != null) {
                            break;
                        }
                    } catch (ClassNotFoundException e) {
                        // Do nothing, we will try another package
                    }
                }
            } else {
                view = layoutInflater.createView(name, null, attrs);
            }
            return view;
        }
    }
}

请注意,它依赖于guava,但您可以自己实现此方法。

答案 5 :(得分:3)

有史以来最好的解决方案是Calligraphy