41
votes

I have a digital clock widget. How can I use a custom font from assets/fonts as the default font in the textview showing the clock?

This is my code:

    package android.tristan.widget.digiclock;

import java.util.Calendar;

import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.os.Vibrator;
import android.text.format.DateFormat;
import android.widget.RemoteViews;


public class DigiClock extends AppWidgetProvider {

    @Override
    public void onDisabled(Context context) {
        super.onDisabled(context);
        context.stopService(new Intent(context, UpdateService.class));
    }
    public void onReceive(Context context, Intent intent)
    {
        super.onReceive(context, intent);

        if(intent.getAction().equals("android.tristan.widget.digiclock.CLICK"))
        {
          Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
          vibrator.vibrate(50);           
            final Intent alarmClockIntent = new Intent(Intent.ACTION_MAIN, null);
            alarmClockIntent.addCategory(Intent.CATEGORY_LAUNCHER);
            final ComponentName cn = new ComponentName("com.android.deskclock", "com.android.deskclock.AlarmClock");
            alarmClockIntent.setComponent(cn);
            alarmClockIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(alarmClockIntent);
        }
        if(intent.getAction().equals("android.tristan.widget.digiclock.CLICK_2"))
        {
          Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
          vibrator.vibrate(50);
           final Intent calendarIntent = new Intent(Intent.ACTION_MAIN, null);
           calendarIntent.addCategory(Intent.CATEGORY_LAUNCHER);
            final ComponentName cn = new ComponentName("com.android.calendar", "com.android.calendar.LaunchActivity");
            calendarIntent.setComponent(cn);
            calendarIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(calendarIntent);       
        }
    }

    @Override
    public void onEnabled(Context context) {
        super.onEnabled(context);
        context.startService(new Intent(UpdateService.ACTION_UPDATE));
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        context.startService(new Intent(UpdateService.ACTION_UPDATE));
        final int Top = appWidgetIds.length;
        final int Bottom = appWidgetIds.length;
        for (int i=0; i<Top; i++)
        {
            int[] appWidgetId = appWidgetIds;
            RemoteViews top=new RemoteViews(context.getPackageName(), R.layout.main);
            Intent clickintent=new Intent("android.tristan.widget.digiclock.CLICK");
            PendingIntent pendingIntentClick=PendingIntent.getBroadcast(context, 0, clickintent, 0);
            top.setOnClickPendingIntent(R.id.TopRow, pendingIntentClick);
            appWidgetManager.updateAppWidget(appWidgetId, top);
        }
        for (int i=0; i<Bottom; i++)
        {
            int[] appWidgetId = appWidgetIds;
            RemoteViews bottom=new RemoteViews(context.getPackageName(), R.layout.main);
            Intent clickintent=new Intent("android.tristan.widget.digiclock.CLICK_2");
            PendingIntent pendingIntentClick=PendingIntent.getBroadcast(context, 0, clickintent, 0);
            bottom.setOnClickPendingIntent(R.id.BottomRow, pendingIntentClick);
            appWidgetManager.updateAppWidget(appWidgetId, bottom);
        }
}

    public static final class UpdateService extends Service {

       static final String ACTION_UPDATE = "android.tristan.widget.digiclock.action.UPDATE";

        private final static IntentFilter sIntentFilter;

        private final static String FORMAT_12_HOURS = "h:mm";
        private final static String FORMAT_24_HOURS = "kk:mm";

        private String mTimeFormat;
        private String mDateFormat;
        private String mDayFormat;
        private Calendar mCalendar;

        static {
            sIntentFilter = new IntentFilter();
            sIntentFilter.addAction(Intent.ACTION_TIME_TICK);
            sIntentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
            sIntentFilter.addAction(Intent.ACTION_TIME_CHANGED);
        }

        @Override
        public void onCreate() {
            super.onCreate();
            reinit();
            registerReceiver(mTimeChangedReceiver, sIntentFilter);
         }

        @Override
        public void onDestroy() {
            super.onDestroy();
            unregisterReceiver(mTimeChangedReceiver);
        }

        @Override
        public void onStart(Intent intent, int startId) {
            super.onStart(intent, startId);

            if (ACTION_UPDATE.equals(intent.getAction())) {
                update();
            }
        }

        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }


        private void update() {
            mCalendar.setTimeInMillis(System.currentTimeMillis());
            final CharSequence time = DateFormat.format(mTimeFormat, mCalendar);
            final CharSequence date = DateFormat.format(mDateFormat, mCalendar);
            final CharSequence day = DateFormat.format(mDayFormat, mCalendar);

            RemoteViews views = new RemoteViews(getPackageName(), R.layout.main);
            views.setTextViewText(R.id.Time, time);
            views.setTextViewText(R.id.Day, day);
            views.setTextViewText(R.id.Date, date);

            ComponentName widget = new ComponentName(this, DigiClock.class);
            AppWidgetManager manager = AppWidgetManager.getInstance(this);
            manager.updateAppWidget(widget, views);
        }

        private void reinit() {
            mDayFormat = getString(R.string.day_format);
            mDateFormat = getString(R.string.date_format);
            mTimeFormat = is24HourMode(this) ? FORMAT_24_HOURS : FORMAT_12_HOURS;
            mCalendar = Calendar.getInstance();
        }

        private static boolean is24HourMode(final Context context) {
            return android.text.format.DateFormat.is24HourFormat(context);
        }

        private final BroadcastReceiver mTimeChangedReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                final String action = intent.getAction();

                if (action.equals(Intent.ACTION_TIME_CHANGED) ||
                    action.equals(Intent.ACTION_TIMEZONE_CHANGED))
                {
                    reinit();
                }

                update();
            }
        };
    }
}
9
Google really needs to add the ability to change the ttf in the xml layout, until then I think you are stuck using image views.Nathan Schwermann
I have read about rendering the font on the fly, and use the bitmaps instead of characters, but how would I code that? I would really like my clock widget to use the Clockopia font from the aosp roms.tristan202
This example will not work for a widget. Widgets are inherited from AppWidgetProvider which does not provide getAssets or findViewById.Ian Mackinnon

9 Answers

64
votes

What is needed is to render the font onto a canvas, and then pass it on to a bitmap and assign that to an ImageView. Like so:

public Bitmap buildUpdate(String time) 
{
    Bitmap myBitmap = Bitmap.createBitmap(160, 84, Bitmap.Config.ARGB_4444);
    Canvas myCanvas = new Canvas(myBitmap);
    Paint paint = new Paint();
    Typeface clock = Typeface.createFromAsset(this.getAssets(),"Clockopia.ttf");
    paint.setAntiAlias(true);
    paint.setSubpixelText(true);
    paint.setTypeface(clock);
    paint.setStyle(Paint.Style.FILL);
    paint.setColor(Color.WHITE);
    paint.setTextSize(65);
    paint.setTextAlign(Align.CENTER);
    myCanvas.drawText(time, 80, 60, paint);
    return myBitmap;
}

That's the part doing the font to image thingie, and this is how to use it:

String time = (String) DateFormat.format(mTimeFormat, mCalendar);
RemoteViews views = new RemoteViews(getPackageName(), R.layout.main);
views.setImageViewBitmap(R.id.TimeView, buildUpdate(time));

As you might notice, this code just shows the current time in the imageview, but it can easily be adjusted to whatever needs.

Edit:

ARGB_4444 is deprecated for ARGB_8888 as stated in the documentation

This field was deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead.

16
votes

I changed a little about measure size, so the bitmap will support different fontsize. It just support single line text.

public static Bitmap getFontBitmap(Context context, String text, int color, float fontSizeSP) {
    int fontSizePX = convertDiptoPix(context, fontSizeSP);
    int pad = (fontSizePX / 9);
    Paint paint = new Paint();
    Typeface typeface = Typeface.createFromAsset(context.getAssets(), "Fonts/Roboto-Regular.ttf");
    paint.setAntiAlias(true);
    paint.setTypeface(typeface);
    paint.setColor(color);
    paint.setTextSize(fontSizePX);

    int textWidth = (int) (paint.measureText(text) + pad * 2);
    int height = (int) (fontSizePX / 0.75);
    Bitmap bitmap = Bitmap.createBitmap(textWidth, height, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    float xOriginal = pad;
    canvas.drawText(text, xOriginal, fontSizePX, paint);
    return bitmap;
}

public static int convertDiptoPix(Context context, float dip) {
    int value = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, context.getResources().getDisplayMetrics());
    return value;
}
8
votes

This renders the font to a bitmap, and then assigns the bitmap to an ImageView.

public static RemoteViews buildUpdate(Context context)
{
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main);
    Bitmap myBitmap = Bitmap.createBitmap(100, 50, Bitmap.Config.ARGB_4444);
    Canvas myCanvas = new Canvas(myBitmap);
    Paint paint = new Paint();
    Typeface clock = Typeface.createFromAsset(context.getAssets(),"Clockopia.ttf");
    paint.setAntiAlias(true);
    paint.setSubpixelText(true);
    paint.setTypeface(clock);
    paint.setStyle(Paint.Style.FILL);
    paint.setColor(Color.BLACK);
    paint.setTextSize(15);
    myCanvas.drawText("Test", 0, 20, paint);

    views.setImageViewBitmap(R.id.TimeView, myBitmap);

    return views;
}
5
votes

This solution will create a bitmap that is the exact size required to fit the text.

/**
 * Creates and returns a new bitmap containing the given text.
 */
public static Bitmap createTextBitmap(final String text, final Typeface typeface, final float textSizePixels, final int textColour)
{
    final TextPaint textPaint = new TextPaint();
    textPaint.setTypeface(typeface);
    textPaint.setTextSize(textSizePixels);
    textPaint.setAntiAlias(true);
    textPaint.setSubpixelText(true);
    textPaint.setColor(textColour);
    textPaint.setTextAlign(Paint.Align.LEFT);
    Bitmap myBitmap = Bitmap.createBitmap((int) textPaint.measureText(text), (int) textSizePixels, Bitmap.Config.ARGB_8888);
    Canvas myCanvas = new Canvas(myBitmap);
    myCanvas.drawText(text, 0, myBitmap.getHeight(), textPaint);
    return myBitmap;
}

As mentioned elsewhere, the bitmap can then be assigned to a widget's ImageView.

final Bitmap textBitmap = createTextBitmap(text,
        FontManager.get().getTypeface("slab-serif", 0),
        context.getResources().getDimension(R.dimen.widget_font_size_large),
        context.getResources().getColor(R.color.widget_text)
);
views.setImageViewBitmap(R.id.widget_cardTextImage, textBitmap);

This has the advantage of producing a bitmap that will never undershoot or overshoot the text it should contain, which is important for widgets as their dimensions vary between devices and versions.

1
votes

There is no way to set your custom font to remote views because you don't have direct access to them(they are not your application's view).

But you can create a textView will all of the attributes that you want programmatically, Then convert it to a Bitmap and set it to an ImageView instead of your TextView.

This is a sample code that I used before:

private fun createTextBitmap(text: String, typeface: Typeface, textSize: Float, textColour: Int): Bitmap? {

        val textView = TextView(mService)
        textView.isDrawingCacheEnabled = true
        textView.text = text
        textView.typeface = typeface
        textView.setTextColor(textColour)
        textView.textSize = textSize
        textView.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)

        textView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
        textView.layout(0, 0, textView.measuredWidth, textView.measuredHeight)

        return textView.getDrawingCache(true)
    }
0
votes

I can't find it now, but I asked the same question and got a response from Google that this isn't possible. You are really restricted in what you can do with AppWidgets, for example you can only use certain UI widgets and you can't use custom fonts.

0
votes

I was facing the same problem, but I didn't wanted to use bitmap and canvas-like everyone is saying. Because I don't know how they work and how to handle them.

so this is what I did. I created multiple text views inside my widget layout file and set each text view a different font style using android:fontFamily and then set the visibility of all text views to "gone".

And at run time in the onUpdate method of appWidgetProvider class. I set the visibility of text view to Visible (of the text view which I wanted or which font I wanted to display to the user.

And to set the visibility dynmatically I used remoteViews.setViewVisibility(R.id.hadees_tv2, View.VISIBLE)

-4
votes
remoteviews.setTextViewText(textview_id, new SpannableStringBuilder(Html.fromHtml("<b>"+some_text_vaeiable"+"</b>")) );
-17
votes

This is how I do it in my applications, should apply to widgets as well.

Typeface customfont = Typeface.createFromAsset(getAssets(), "fonts/somefont.ttf");
TextView textview = (TextView) findViewById(R.id.textview);
textview.setTypeface(customfont);
textview.setText("Hey custom font!");