Android: application-wide font-size preference

25,355

Solution 1

Here it's how I made it for my app. In a few words - in Activity.onCreate() you get resource id of style with specific set of font sizes and apply this style to theme of activity. Then with preferences activity you can switch between these sets.

First of all in values/attrs.xml declare attributes for set of font sizes:

<declare-styleable name="FontStyle">
    <attr name="font_small" format="dimension" />
    <attr name="font_medium" format="dimension" />
    <attr name="font_large" format="dimension" />
    <attr name="font_xlarge" format="dimension" />
</declare-styleable>

Then in values/styles.xml declare few sets of font sizes:

<style name="FontStyle">
</style>

<style name="FontStyle.Small">
    <item name="font_small">14sp</item>
    <item name="font_medium">16sp</item>
    <item name="font_large">18sp</item>
    <item name="font_xlarge">20sp</item>
</style>

<style name="FontStyle.Medium">
    <item name="font_small">18sp</item>
    <item name="font_medium">20sp</item>
    <item name="font_large">22sp</item>
    <item name="font_xlarge">24sp</item>
</style>

<style name="FontStyle.Large">
    <item name="font_small">26sp</item>
    <item name="font_medium">28sp</item>
    <item name="font_large">30sp</item>
    <item name="font_xlarge">32sp</item>
</style>

Then in onCreate() method of every activity add:

getTheme().applyStyle(new Preferences(this).getFontStyle().getResId(), true);

where Preferences is a facade to SharedPreferences object:

public class Preferences {
    private final static String FONT_STYLE = "FONT_STYLE";

    private final Context context;

    public Preferences(Context context) {
        this.context = context;
    }

    protected SharedPreferences open() {
        return context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
    }

    protected Editor edit() {
        return open().edit();
    }

    public FontStyle getFontStyle() {
        return FontStyle.valueOf(open().getString(FONT_STYLE,
            FontStyle.Medium.name()));
    }

    public void setFontStyle(FontStyle style) {
        edit().putString(FONT_STYLE, style.name()).commit();
    }
}

and FontStyle is:

public enum FontStyle {
    Small(R.style.FontStyle_Small, "Small"), 
    Medium(R.style.FontStyle_Medium, "Medium"), 
    Large(R.style.FontStyle_Large, "Large");

    private int resId;
    private String title;

    public int getResId() {
        return resId;
    }

    public String getTitle() {
        return title;
    }

    FontStyle(int resId, String title) {
        this.resId = resId;
        this.title = title;
    }
}

And FontStyle.values() is used as items for Spinner in your PreferencesActivity. That's how mine looks like:

public class PreferencesActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    getTheme().applyStyle(new Preferences(this).getFontStyle().getResId(), true);
    super.onCreate(savedInstanceState);

    setContentView(R.layout.preferences);

    Preferences prefs = new Preferences(this);

    Spinner fontStylesView = (Spinner) findViewById(R.id.font_styles);
    FontStylesAdapter adapter = new FontStylesAdapter(this,
            R.layout.font_styles_row, FontStyle.values());
    fontStylesView.setAdapter(adapter);

    fontStylesView.setSelection(prefs.getFontStyle().ordinal());
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getSupportMenuInflater();
    inflater.inflate(R.menu.preferences, menu);
    return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case R.id.menu_done:
        onMenuDone();
        finish();
        return true;
    case R.id.menu_cancel:
        finish();
        return true;
    default:
        return false;
    }
}

private void onMenuDone() {
    Preferences prefs = new Preferences(this);

    Spinner fontStylesView = (Spinner) findViewById(R.id.font_styles);
    prefs.setFontStyle((FontStyle) fontStylesView.getSelectedItem());
}
}

And finally you can use your font size preferences:

<TextView android:textSize="?attr/font_large" />

Or I prefer using styles, in values/styles.xml add:

<style name="Label" parent="@android:style/Widget.TextView">
    <item name="android:textSize">?attr/font_medium</item>
    <item name="android:layout_width">wrap_content</item>
    <item name="android:layout_height">wrap_content</item>
</style>

<style name="Label.XLarge">
    <item name="android:textSize">?attr/font_xlarge</item>
</style>

And you can use it in this way:

<TextView style="@style/Label.XLarge" />

I hope my answer will help you.

Solution 2

Yes, it's possible. To do that you need to:

  1. Declare your own class extending TextView
  2. Use in all your dialogs/activities only it

Like:

public class SimpleTextView extends TextView
{
    private static final float DEFAULT_TEXT_SIZE=12.0;
    private static float textSize=DEFAULT_TEXT_SIZE;

    public SimpleTextView(Context context)
    {
        super(context);
        this.setTextSize(textSize);
    }

    public SimpleTextView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        this.setTextSize(textSize);
    }

    public SimpleTextView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        this.setTextSize(textSize);
    }

    public static void setGlobalSize(float size)
    {
        textSize=size;
    }

    public static float getGlobalSize()
    {
        return textSize;
    }
}

And now whereever you're you can globally change all text sizes to 20 in all textviews just calling:

SimpleTextView.setGlobalTextSize(20);

Solution 3

For those who have problems with inflating views with custom attributes from @mixels answer.

If your views are in fragment, you need to apply FontStyle also in fragment's onCreateView() or set values of these attributes in application's theme.

For more detailes see my aswer here.

Solution 4

Shooting from the hip here's idea to consider (no custom TextView implementation required)

  1. Declare property something like UNIVERSAL_FONT_SIZE with the idea that it can be changed from settings but will be retained between app invocations
  2. In onCreate method of each of your Activities get value of that property and save as a field
  3. Make your code use that for each text-resizable component
  4. Nothing will actually stop you from creating several properties such as BUTTONS_TXT_SIZE, TEXT_SIZE, LIST_TXT_SIZE, etc. and then have logic that takes for example percent of text increase and calculated proper sizes for each type of control (since you may have different sizes for different controls)

Along the same lines, say you want to make this to work dynamically? Create a simple class (say TextSetter) that holds internal list and have 3 methods: add, remove and setSize

  1. In Activity#onCreate identify each control you want to adjust and use TextSetter#set to add it to the list
  2. When user wants to increase/decrease font size maybe from the menu, when you handle that just execute TextSetter#setSize in which you will loop through the list of controls, detect which type it is and adjust text size accordingly
Share:
25,355

Related videos on Youtube

ge0rg
Author by

ge0rg

IT Consulting with focus on Smartphones and Security. Do not hesitate to ask if you have an interesting problem to solve.

Updated on July 09, 2022

Comments

  • ge0rg
    ge0rg almost 2 years

    Is it possible to make an application-wide setting for the font-size to be used by all views displaying text? I would like to provide a Preference to the user which should allow scaling all text in the app.

    Android explicitly allows using the "sp" dimension unit for scalable text, however there is no actual way to set the "user's font size preference" in a global way.

    Iterating through all views on Activity instantiation is not really an option ;-)

  • Cheryl Simon
    Cheryl Simon over 13 years
    That works for text that appears in TextViews, but what about all of the TextView subclasses like EditText, Button, CheckedTextView etc.? You would basically need to create subclasses of every Widget type that you use..
  • Barmaley
    Barmaley over 13 years
    @Mayra: that right - I was forced to do so... I mean it's not great fun, but result is fascinating :)
  • ge0rg
    ge0rg over 13 years
    This solution requires to derive from any View class with text display, and some more work needs to be done to add a scaling factor (so I can have a 10sp and a 20sp text scale while keeping the 2x size relation), but I see where it is heading, thanks.
  • ge0rg
    ge0rg about 13 years
    This is going into the right direction. I already tried to override the system wide font scale, however the ActivityManagerNative seems to be only usable from inside Android itself. The SpareParts example app, which provides the linked code, allows to set the font in an app-global way. I do not want to employ dirty tricks to get access to that API, but it seems there is no really elegant solution to that.
  • Krishna Shrestha
    Krishna Shrestha over 11 years
    @barmaley i followed your solution but i am not able scale all the textview in application.Does it works?
  • Sudarshan Bhat
    Sudarshan Bhat over 11 years
    Suppose I have some theme already set to my activity. Now I want to set FontStyle.Medium as default to all the activities. If I define textSize="?attr/font_medium</item>", it crashes as it is unknown to the activity. So this works only if I call getTheme().applyStyle() is it?
  • mixel
    mixel over 11 years
    Yes. With declare-styleable you declare some attributes. In styles you define values for these attributes. And then you need to apply one of these styles to activity theme. Which one is defined by saved preferences.
  • Robi Kumar Tomar
    Robi Kumar Tomar over 10 years
    It is not working for me. I had already fix setGlobalTextSize to setGlobalSize but no effect in my application. Have you tested it.
  • wangqi060934
    wangqi060934 about 9 years
    Is "applyStyle" take effect immediately?Or do I miss something?
  • tread
    tread almost 9 years
    @mixel This is not working in an ArrayAdapter. Error is: Binary XML file line #53: Error inflating class android.widget.TextView
  • mixel
    mixel almost 9 years
    @surfer190 Something wrong with your layout. Please attach a link to your project (on GitHub, for example).
  • tread
    tread almost 9 years
    @mixel I sorted it out by calling getTheme().applyStyle() in the constructor of the ArrayAdapter
  • mixel
    mixel almost 9 years
    @surfer190 You should call getTheme().applyStyle() in Activity.onCreate()
  • tread
    tread almost 9 years
    @mixel I do call it onCreate() but then there is a fragment opened with a navigationDrawer that shows a ListView with ArrayAdapter and it fails if it isn't in the Constrcutor of the ArrayAdapter
  • mixel
    mixel almost 9 years
    @surfer190 If you solved your issue then ok. If not, you can create test project with minimal amount of code required to reproduce your issue, place it on GitHub and I'll take a look.
  • azurh
    azurh almost 9 years
    This code works perfectly. But how do I restart the application instead of finishing it?
  • mixel
    mixel almost 9 years
    @azurth You need to restart Activity: Intent intent = getIntent(); finish(); startActivity(intent);
  • Robin Dijkhof
    Robin Dijkhof over 7 years
    Is it possible to make changes take effect at runtime? So I don't have to restart the app?
  • mixel
    mixel over 7 years
    @RobinDijkhof No, you have to restart activity.
  • Aspiring Dev
    Aspiring Dev over 7 years
    @mixel have used this in the past and it works very well but I'm wondering: is this still the best way to do this on newest android? I would think there would be a less 'hacky' way of doing this by now as long as you don't have to support older devices. Is there?
  • mixel
    mixel over 7 years
    @rpgmaker Yes, it's the best way. Android style styleable attributes give you flexibility in implementing this and there is no 'hacking' - It's just simple Android SDK features.
  • Sandeep Yohans
    Sandeep Yohans almost 6 years
    @barmaley This is not working... can you please update the answer?