Question

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();
            }
        };
    }
}
Was it helpful?

Solution

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.

OTHER TIPS

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;
}

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;
}

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.

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.

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

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!"); 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top