ClipDrawable not working when used as layout background for widget

android, android-linearlayout, android-widget, java

Solution

You are correct in that you need to set the level on a Drawable instance, but unfortunately you can't get at the Drawable because it's in a remote view.

However, ImageView does have a method called `setImageLevel`, which you can call remotely. My suggestion would be to put the Drawable in an ImageView instead of as the background of the container, and then use `setInt(R.id.image_view, "setImageLevel", level)`.

Here's what the layout should look like:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widgetLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="@dimen/widget_padding" >

    <ImageView
        android:id="@+id/battery"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/battery_clip_layer" />

    <TextView
        android:id="@+id/batteryPercentageWidgetTextView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/battery_percentage_widget_default"
        android:gravity="center" />

</FrameLayout>

And the `updateWidget()` method:

private void updateWidget() {
    final int percentage = batteryPercentage.get();
    log.i(LOG, "Updated... " + percentage);

    RemoteViews remoteViews = new RemoteViews(getPackageName(),  R.layout.battery_widget_layout);
    remoteViews.setTextViewText(R.id.batteryPercentageWidgetTextView, percentage + "%");

    remoteViews.setInt(R.id.battery, "setImageLevel", percentage * 100);

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
    appWidgetManager.updateAppWidget(new ComponentName(this, BatteryAppWidgetProvider.class), remoteViews);
}

Problem

I'm attempting to make a simple widget that displays the battery percentage both textually and graphically in a widget. The textual part works without problem, but I'm having great difficulty getting the widget to graphically update. Graphically, I have a battery image that I clip according to battery percentage. I'm attempting to use `ClipDrawable` for this. battery_widget_layout.xml ``` <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/widgetLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" android:padding="@dimen/widget_padding" android:background="@drawable/battery_clip_layer" > <TextView android:id="@+id/batteryPercentageWidgetTextView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="@string/battery_percentage_widget_default" android:gravity="center" /> </LinearLayout> ``` battery_clip_layer.xml (ie a ClipDrawable) ``` <clip xmlns:android="http://schemas.android.com/apk/res/android" android:clipOrientation="vertical" android:gravity="bottom" android:drawable="@drawable/battery_shape" /> ``` BatteryService.java - a service that receives battery events and updates the widget via a RemoteViews ``` public class BatteryService extends Service { private static final String LOG = BatteryService.class.getName(); private final AtomicInteger batteryPercentage = new AtomicInteger(100); private final BroadcastReceiver batteryUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int level = intent.getIntExtra("level", 0); batteryPercentage.set(level); updateWidget(); } }; @Override public void onCreate() { super.onCreate(); registerReceiver(batteryUpdateReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); Log.i(LOG, "Created..."); } @Override public void onDestroy() { super.onDestroy(); unregisterReceiver(batteryUpdateReceiver); Log.i(LOG, "Destroyed..."); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(LOG, "Started..."); updateWidget(); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent arg0) { return null; } private void updateWidget() { final int percentage = batteryPercentage.get(); log.i(LOG, "Updated... " + percentage); RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.battery_widget_layout); remoteViews.setTextViewText(R.id.batteryPercentageWidgetTextView, percentage + "%"); //ATTEMPT 1 - no cigar remoteViews.setInt(R.drawable.battery_clip_layer, "setLevel", 5000); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); appWidgetManager.updateAppWidget(new ComponentName(this, BatteryAppWidgetProvider.class), remoteViews); } private void updateWidgetAttempt2() { final int percentage = batteryPercentage.get(); Log.i(LOG, "Updated... " + percentage); //ATTEMPT 2 - still no cigar Drawable clipLayer = getApplicationContext().getResources().getDrawable(R.drawable.battery_clip_layer); if (clipLayer instanceof ClipDrawable) { ClipDrawable clipDrawable = (ClipDrawable) clipLayer; int level = clipDrawable.getLevel(); if (clipDrawable.setLevel(10000)) { clipDrawable.invalidateSelf(); } Log.i(LOG, "Updated clip amount..." + level + " -> " + ((ClipDrawable) clipLayer).getLevel()); } RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.battery_widget_layout); remoteViews.setTextViewText(R.id.batteryPercentageWidgetTextView, percentage + "%"); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); appWidgetManager.updateAppWidget(new ComponentName(this, BatteryAppWidgetProvider.class), remoteViews); } } ``` In `BatteryService` please have a look at the two different attempts to update the widget (`updateWidget()` and `updateWidgetAttempt2()`). Neither attempt is successful. I feel that I'm doing something fundamentally wrong. I'm most grateful for any help/advice! :)

Original source