Live wallpapers are now part of the officially supported APIs in Android SDK 2.1, and today i’m going to talk about how to start writing your own live wallpaper. By the end of this tutorial you should be able to create your own live wallpaper, test it on a Nexus One device and get it ready for publishing on the Android Market.

Getting started

The plumbing

The SDK comes with a sample live wallpaper that can be found in the platforms/android-2.1/samples/CubeLiveWallpaper folder. As with any other Android application, you will need to write a few XML files that let the market / device know whether your application can be run on the specific device. In our case, live wallpapers are supported by the 2.1 version of the platform, and as such should not be visible when you are browsing the market on pre-2.1 devices.

We start with the AndroidManifest.xml that looks like this:




   

      
         
            
         
         
      

   

   

   

Important points:

  • The service tag lets the platform know that your service is a wallpaper
  • The uses-sdk tag lets the platform know that you are requiring the 2.1 version
  • The uses-feature tag lets the platform know that this is a live wallpaper

The android:resource attribute of the meta-data tag in the service description points to the bokeh.xml file that goes under the res/xml folder. Here are the contents of that file:



Here is how the live wallpaper picker looks like with our wallpaper at the top:

There are two strings and one image:

  • The title string is defined by the android:label attribute in the service tag of the application defined in the AndroidManifest.xml
  • The description string is defined by the android:description attribute in the wallpaper defined in the bokeh.xml
  • The thumbnail is defined by the android:thumbnail attribute in the wallpaper defined in the bokeh.xml

Here are the contents of res/values/strings.xml:




    
    
    
    Bokeh Wallpapers

    
    Bokeh Rainbow

    Translucent fading circles with rainbow colors

And the thumbnail image goes into the res/drawable folder.

How a live wallpaper works

A live wallpaper consists of three main layers:

  • Life cycle events – create, destroy, move, show, hide, touch
  • Animations
  • Drawing

The android:name attribute in your service tag should point to a class name that extends the android.service.wallpaper.WallpaperService class. This is the base class for all live wallpapers in the system. It is an abstract class and you must implement the onCreateEngine() method to return your own live wallpaper engine. The engine is responsible for handling all the functionality mentioned above – life cycle events, animations and drawing.

The android.service.wallpaper.WallpaperService.Engine internal class has a number of methods that you can override in order to respond to different life cycle events of the your live wallpaper. Let’s take a short look at the relevant methods:

  • onCreate() – called when the engine is initialized. At this point the drawing surface has not yet been created
  • onDestroy() – called when the engine is destroyed. After this method is called, the engine is no longer valid
  • onSurfaceCreated() – called when the drawing surface has been created
  • onSurfaceChanged() – called when structural changes (such as size or format) have been made to the drawing surface
  • onSurfaceDestroyed() – called when the drawing surface has been destroyed
  • onVisibilityChanged() – called when the wallpaper becomes visible or hidden. When the wallpaper is hidden, you should suspend your animation and not draw anything to save the CPU cycles
  • onOffsetsChanged() – called when the wallpaper offsets are changed after the user “swipes” the home screen to one of the sides. Can be used to create a “parallax” effect when the wallpaper is moving along with the home screen
  • onTouchEvent() – called when the user performs touch-screen interaction with the window that is showing the wallpaper
  • onCommand() – called to process a command

A live wallpaper is supposed to have dynamic content (hence the word “live”). The base classes (WallpaperService and WallpaperService.Engine) do not provide any special facilities to run the animation loop. It is your responsibility to start the animation loop, handle the life cycle events to suspend / resume the animations and handle each animation step. One way to handle the animation loop is with the android.os.Handler and java.lang.Runnable classes.

Let’s see a sample base implementation of an animation loop for a live wallpaper that handles the relevant events:

import android.os.Handler;
import android.service.wallpaper.WallpaperService;
import android.view.SurfaceHolder;

public abstract class AnimationWallpaper extends WallpaperService {

   protected abstract class AnimationEngine extends Engine {
      private Handler mHandler = new Handler();

      private Runnable mIteration = new Runnable() {
         public void run() {
            iteration();
            drawFrame();
         }
      };

      private boolean mVisible;

      @Override
      public void onDestroy() {
         super.onDestroy();
         // stop the animation
         mHandler.removeCallbacks(mIteration);
      }

      @Override
      public void onVisibilityChanged(boolean visible) {
         mVisible = visible;
         if (visible) {
            iteration();
            drawFrame();
         } else {
            // stop the animation
            mHandler.removeCallbacks(mIteration);
         }
      }

      @Override
      public void onSurfaceChanged(SurfaceHolder holder, int format,
            int width, int height) {
         iteration();
         drawFrame();
      }

      @Override
      public void onSurfaceDestroyed(SurfaceHolder holder) {
         super.onSurfaceDestroyed(holder);
         mVisible = false;
         // stop the animation
         mHandler.removeCallbacks(mIteration);
      }

      @Override
      public void onOffsetsChanged(float xOffset, float yOffset,
            float xOffsetStep, float yOffsetStep, int xPixelOffset,
            int yPixelOffset) {
         iteration();
         drawFrame();
      }

      protected abstract void drawFrame();

      protected void iteration() {
         // Reschedule the next redraw in 40ms
         mHandler.removeCallbacks(mIteration);
         if (mVisible) {
            mHandler.postDelayed(mIteration, 1000 / 25);
         }
      }
   }
}

Here, we have an abstract extension of the WallpaperService that handles the major life cycle events of our live wallpaper:

  • The mIteration runnable will be called at each iteration to draw one single frame and schedule the next iteration
  • The mHandler will handle the message queue associated with our main thread. We will use it to wait for the next iteration and run our mIteration
  • The mVisible stores the current visibility flag of our wallpaper
  • When the animation needs to stop – in onDestroy(), onSurfaceDestroyed() and onVisibilityChanged() when false is passed – we let our handler know that it should not run our callback. This effectively stops our animation loop, removing the animation / redraw requests.
  • When the wallpaper becomes visible – in onVisibilityChanged() when true is passed, we run a single iteration and draw a single frame. The call to iteration() effectively schedules the next iteration to run in 40 milliseconds.
  • Whenever there are changes to the surface – in onSurfaceChanged() and onOffsetsChanged() we respond immediately by redrawing the wallpaper and rescheduling the next iteration

Finally, it’s time to talk about the drawing. The drawFrame() method defined in our abstract class is called on every animation step. Here is how it looks like:

@Override
protected void drawFrame() {
   SurfaceHolder holder = getSurfaceHolder();

   Canvas c = null;
   try {
      c = holder.lockCanvas();
      if (c != null) {
         draw(c);
      }
   } finally {
      if (c != null)
         holder.unlockCanvasAndPost(c);
   }
}

Here, we obtain the Canvas object from the SurfaceHolder, perform the actual drawing and then unlock the canvas and let the platform know that it should draw the contents of the canvas back on the screen.

Handling the details

With our base abstract class handling the life cycle events and scheduling the animation iterations, we can focus on the specifics of our live wallpaper. This is how it is going to look like:

It is a bunch of fading translucent circles that are colored based on their vertical location on the screen. First, let’s see the model class that stores the information on one single circle and updates it on every animation tick:

public class BokehRainbowCircle {
   float origRadius, deltaRadius, radius;

   float origX, deltaX, x;

   float origY, deltaY, y;

   int color;

   int alpha;

   int steps;

   int currentStep;

   public BokehRainbowCircle(float xCenter, float yCenter, float radius,
         int color, int steps) {
      this.x = xCenter;
      this.origX = xCenter;
      this.deltaX = (float) (40.0 * Math.random() - 20.0);

      this.y = yCenter;
      this.origY = yCenter;
      this.deltaY = (float) (40.0 * Math.random() - 20.0);

      this.origRadius = radius;
      this.radius = radius;
      this.deltaRadius = 0.5f * radius;

      this.color = color;
      this.alpha = 0;

      this.steps = steps;
   }

   void tick() {
      this.currentStep++;

      float fraction = (float) this.currentStep / (float) this.steps;

      this.radius = this.origRadius + fraction * this.deltaRadius;
      this.x = this.origX + fraction * this.deltaX;
      this.y = this.origY + fraction * this.deltaY;

      this.alpha = (fraction <= 0.25f) ? (int) (128 * 4.0f * fraction) : (int) (-128 * (fraction - 1) / 0.75f);
   }

   boolean isDone() {
       return this.currentStep > this.steps;
   }
}

Here, the tick() method is called on every animation iteration. This method updates the circle alpha, radius and center position to create a fading, growing and moving circle – that still maintains its color.

Now let’s see the implementation of the wallpaper itself. We start by implementing the onCreateEngine to return our own engine (note how both the wallpaper and its engine extend our base classes):

public class BokehRainbowWallpaper extends AnimationWallpaper {

   @Override
   public Engine onCreateEngine() {
      return new BokehEngine();
   }

   class BokehEngine extends AnimationEngine {

As mentioned before, the home screen (of at least Nexus One) can be swiped to the side (left or right). The wallpaper can respond to these events and create a parallax effect – where it shifts with the home screen, but at a lesser extent. In this specific case, our live wallpaper is twice as wide as the visible width of the screen. Since Nexus One can be scrolled by two screens to both sides, this creates a nice effect whereby the wallpaper is scrolled slower than the home screen contents, making it appear farther away from the user.

The information about the current size and offsets of the wallpaper is stored in the following fields:

int offsetX;
int offsetY;
int height;
int width;
int visibleWidth;

And set in the following life cycle events of our engine:

@Override
public void onSurfaceChanged(SurfaceHolder holder, int format,
      int width, int height) {

   this.height = height;
   if (this.isPreview()) {
      this.width = width;
   } else {
      this.width = 2 * width;
   }
   this.visibleWidth = width;

   for (int i = 0; i < 20; i++) {
      this.createRandomCircle();
   }

   super.onSurfaceChanged(holder, format, width, height);
}

@Override
public void onOffsetsChanged(float xOffset, float yOffset,
      float xOffsetStep, float yOffsetStep, int xPixelOffset,
      int yPixelOffset) {
   // store the offsets
   this.offsetX = xPixelOffset;
   this.offsetY = yPixelOffset;

   super.onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep,
         xPixelOffset, yPixelOffset);
}

Note how in onSurfaceChanged we compute the overall width of the wallpaper based on its preview state (you cannot swipe in preview mode). The offsets stored in the onOffsetsChanged are used during the drawing - see the calls to drawCircle below:

void draw(Canvas c) {
   c.save();
   c.drawColor(0xff000000);

   synchronized (circles) {
      for (BokehRainbowCircle circle : circles) {
         if (circle.alpha == 0)
            continue;

         // intersects with the screen?
         float minX = circle.x - circle.radius;
         if (minX > (-this.offsetX + this.visibleWidth)) {
            continue;
         }
         float maxX = circle.x + circle.radius;
         if (maxX < -this.offsetX) {
            continue;
         }

         paint.setAntiAlias(true);

         // paint the fill
         paint.setColor(Color.argb(circle.alpha, Color
               .red(circle.color), Color.green(circle.color),
               Color.blue(circle.color)));
         paint.setStyle(Paint.Style.FILL_AND_STROKE);
         c.drawCircle(circle.x + this.offsetX, circle.y
               + this.offsetY, circle.radius, paint);

         // paint the contour
         paint.setColor(Color.argb(circle.alpha, 63 + 3 * Color
               .red(circle.color) / 4, 63 + 3 * Color
               .green(circle.color) / 4, 63 + 3 * Color
               .blue(circle.color) / 4));
         paint.setStyle(Paint.Style.STROKE);
         paint.setStrokeWidth(3.0f);
         c.drawCircle(circle.x + this.offsetX, circle.y
               + this.offsetY, circle.radius, paint);
      }
   }

   c.restore();
}

In addition to handling the wallpaper offsets, our drawing also checks whether the specific circle needs to be painted at all. Since our wallpaper is twice as wide as the visible width of the screen, we do not need to draw those circles that lie completely to the left and to the right of the visible portion. This way we can save CPU cycle and make the wallpaper animation smoother.

The circles are created in two places. First, we create 20 circles in the onSurfaceChanged method shown above. Also, we create a circle every time the user touches the screen. This is done in the onCommand method:

@Override
public Bundle onCommand(String action, int x, int y, int z,
      Bundle extras, boolean resultRequested) {
   if ("android.wallpaper.tap".equals(action)) {
      createCircle(x - this.offsetX, y - this.offsetY);
   }
   return super.onCommand(action, x, y, z, extras, resultRequested);
}

Note the name of the command action and the usage of the offsets to create the model circle at the correct absolute location (so that when it's drawn with the offsets, it will appear at the touch spot).

Now let's take a look what happens on every animation iteration. We override the iteration() method from our super class and:

  • Let each circle know that another iteration is done
  • Remove all circles that have completed their life (so that our model does not grow indefinitely)
  • Call the super implementation to schedule the next iteration
@Override
protected void iteration() {
   synchronized (circles) {
      for (Iterator it = circles.iterator(); it
            .hasNext();) {
         BokehRainbowCircle circle = it.next();
         circle.tick();
         if (circle.isDone())
            it.remove();
      }
      iterationCount++;
      if (isPreview() || iterationCount % 2 == 0)
         createRandomCircle();
   }

   super.iteration();
}

To complete the code, here is how we create a single model circle:

@Override
public Bundle onCommand(String action, int x, int y, int z,
      Bundle extras, boolean resultRequested) {
   if ("android.wallpaper.tap".equals(action)) {
      createCircle(x - this.offsetX, y - this.offsetY);
   }
   return super.onCommand(action, x, y, z, extras, resultRequested);
}
void createRandomCircle() {
   int x = (int) (width * Math.random());
   int y = (int) (height * Math.random());
   createCircle(x, y);
}

int getColor(float yFraction) {
   return Color.HSVToColor(new float[] { 360.0f * yFraction, 1.0f,
         1.0f });
}

void createCircle(int x, int y) {
   float radius = (float) (40 + 20 * Math.random());

   float yFraction = (float) y / (float) height;
   yFraction = yFraction + 0.05f - (float) (0.1f * (Math.random()));
   if (yFraction < 0.0f)
      yFraction += 1.0f;
   if (yFraction > 1.0f)
      yFraction -= 1.0f;
   int color = getColor(yFraction);

   int steps = 40 + (int) (20 * Math.random());
   BokehRainbowCircle circle = new BokehRainbowCircle(x, y, radius,
         color, steps);
   synchronized (this.circles) {
      this.circles.add(circle);
   }
}

The color of the circle is based on its vertical position (matching the hue to the Y fraction). The circle radius and time-to-live are computed randomly.

Packaging and testing

If you are developing your live wallpaper in Eclipse, the ADT plugin will create an .apk - Android Package file - for you. It will be under the bin folder of your project. At this point you can use the Android Debug Bridge - adb tool - to put your live wallpaper on the device and test it. Run the adb install or adb install -r (if you're replacing the existing one) command pointing to your APK file. Once your package has been installed, bring the live wallpaper picker and click on your wallpaper. Now you can test your wallpaper in both preview and live mode.

Once you are satisified with your wallpaper, it's time to sign and publish it on the Market. Congratulations! You are done.

Resources and conclusion

Complete sources and resources for this wallpaper are available on GitHub. This wallpaper is also available as a free download on the Android Market - search for "Bokeh Rainbow".

A few final words about the performance of live wallpapers. Remember that this is a mobile device, and it is not as powerful as your average laptop / desktop machine. This applies to CPU as well as memory. The more you want to put on the screen, the less fluid your wallpaper will feel. Right now the code above is using the Canvas APIs to paint the circles, and the performance is not as smooth as i would like it to be - even when i only paint the circles that intersect with the visible part of the screen. There are a few options to improve the performance.

One option is to use pre-computed bitmaps and some sort of home-grown double buffering with offscreen bitmaps. However, using a lot of bitmaps may lead to OutOfMemoryErrors - depending on how much memory is available to your process in specific, and to the system in general. You can also play with different Canvas APIs and see which ones are faster for your specific visuals. OpenGL is another option - if you feel adventurous enough.

The Developer Resources has a few articles related to performance, especially on avoiding memory leaks and tracking memory allocations. In particular, it is recommended to reuse as many instances as possible to avoid costly garbage collection of small objects - like is done with the Paint instance in the code above.

The core Substance look-and-feel library is in the feature freeze state, and it’s time to tie the loose ends. Over the next couple of weeks i will be updating documentation, fixing bugs and perhaps adding a few new skins. Along with that, i will polish the visuals of third-party components covered by the Substance plugins that i maintain, and today it’s time to talk about the date picker component from SwingX.

The ever-changing plans for Swing in JDK 7 (which went from grandiose to acceptable to almost non-existent) at one time included a new date picker component available in the core Swing library. While the fate of such a component has not been addressed in the five-hour press conference today, there are a number of open-source and commercial date picker components available for use in Swing applications. One of them comes from SwingX – a library that is supported by the matching Substance SwingX plugin.

The latest 6.0dev drop of Substance SwingX plugin aligns the visuals of the JXDatePicker component with those of editable comboboxes. The inset look, double border and flat button that responds to the mouse rollover – all of these add up to a consistent and polished appearance of your UI running under Substance 6.0. Let’s see a few screenshots.

Here is the date picker component under the Business skin in the latest 6.0dev drop:

https://substance.dev.java.net/release-info/6.0/date-pickers-business.png

Here, you can see four date pickers in different areas of the application – toolbar, sidebar, status bar and the general area. While under the Business sin there is not much visual difference, it becomes more evident under the Business Blue Steel skin:

https://substance.dev.java.net/release-info/6.0/date-pickers-business-bluesteel.png

Here, the toolbar has a darker shade of blue used on its controls, and sidebar / status bar use a lighter shade of blue. Notice how the component automatically picks up the colors for the background and the border based on the decoration area that hosts it.

Next up the Gemini skin:

https://substance.dev.java.net/release-info/6.0/date-pickers-gemini.png

Here, note how the date picker in the toolbar has picked up not only the lighter color for the border, but also the correct light color for the foreground and the arrow icon. The same can be seen under the Magellan skin:

https://substance.dev.java.net/release-info/6.0/date-pickers-magellan.png

with date pickers in different decoration areas picking the matching foreground / background colors. Finally, the date pickers under the Dust skin:

https://substance.dev.java.net/release-info/6.0/date-pickers-dust.png

You are more than welcome to take the latest 6.0dev drops of Substance and its plugins for a spin and report bugs / visual artifacts to the project mailing lists or forums:

Today i’m going to talk about the last big change that went into the next release of Substance look-and-feel – enhanced support for component states.

What is a component state? Let’s take a look at buttons – the most basic building blocks on any UI toolkit. Buttons usually have icons and texts so that the users know what will happen when they click them. In addition, modern UI toolkits provide rich texturing capabilities that allow skinning different parts of the button visuals – such as background, border and focus ring. These visuals usually depend on the current state of the button.

If a button does not respond to UI events (such as mouse click, for instance), it is said to be disabled – and usually has a lighter or partially translucent appearance to match its disabled state. A toggle button can be in a selected state, conveying that certain application parameter is toggled on. A selected button is usually painted with different hue to facilitate quick scanning of the application state. There are more button states – rollover when the mouse is moved over it, pressed when the user pressed but has not yet released the mouse and default button which is invoked when the user presses the Enter key.

Different controls have different states. For example, a progress bar can be determinate or indeterminate – depending on whether the application code can reliably assess the overall length of the work to be performed. A text component can be editable or uneditable. Input controls can be marked as required – to prevent the user from leaving the screen until he makes a selection on that control.

Component states in Substance are manager by the org.pushingpixels.substance.api.ComponentState class. Instances of this class correspond to states of Swing core and custom controls. This class provides a number of predefined static instances to cover most action-based controls such as buttons, check boxes and menu items. In addition, application code can define custom component states that create fine grained mapping between arbitrary states of controls and specific color scheme bundles in custom skins.

Each component state is defined by two arrays of component state facets (available in ComponentStateFacet class). The first array specifies the facets that are on, and the second array specifies the facets that are off. For example, when a selected toggle button is pressed, it transitions to PRESSED_SELECTED state. This state has ComponentStateFacet.ENABLE, ComponentStateFacet.SELECTION and ComponentStateFacet.PRESS as its on facets. If a selected toggle button is disabled, it has ComponentStateFacet.SELECTION in its on facets and ComponentStateFacet.ENABLE in its off facets.

The ComponentStateFacet class defines a number of core facets. The ComponentStateFacet.ENABLE facet is universal – it is relevant for all Swing controls. Other facets apply to a wider range of controls. For example, ComponentStateFacet.ROLLOVER facet applies to all controls that can show rollover effects – including buttons, menu items, comboboxes, sliders, scrollbars and many more. Some facets apply to a very narrow range of controls. For exaple, ComponentStateFacet.EDITABLE is only relevant for editable controls, such as text components, editable comboboxes or spinners.

The static instances of ComponentState defined in this class do not aim to cover all possible combinations of on and off facets. In addition to making this class to unwieldy, it is not possible to do since application code can define its own facets. Instead, Substance provides three ways to fine-tune the mapping between the component states and the color schemes used to paint the components.

  1. When the skin is queried for the color scheme that matches the specific component state – let’s say ComponentState.PRESSED_SELECTED – the skinning layer first looks for the exact state (as passed to SubstanceColorSchemeBundle.registerColorScheme(SubstanceColorScheme, ColorSchemeAssociationKind, ComponentState) or similar APIs). If the exact match is found, it is used. If there is no exact match, the skinning layer will look at all color schemes registered for the specific color scheme association kind in the matching color scheme bundle. The decision is made based on how “close” the registered component state is to the component state of the currently painted component. For example, ComponentState.PRESSED_SELECTED is a better match for ComponentState.PRESSED_UNSELECTED than ComponentState.ROLLOVER_SELECTED – since the ComponentStateFacet.PRESS has more weight than the ComponentStateFacet.ROLLOVER in the decision process. The skinning layer will choose the “closest” registered component state that is sufficiently close. For example, ComponentState.DISABLED_SELECTED will never be chosen for ComponentState.SELECTED, even if there are no other registered component states. This way the application code can register a few color schemes in the specific bundle, and have all other states “fall back” to the smaller subset of states.
  2. Facets such as ComponentStateFacet.DETERMINATE or ComponentStateFacet.EDITABLE are relevant only for a small subset of controls. In order to simplify the API signature of ComponentState, these facets are not part of any of the predefined static states in this class. Instead, they are used internally in the matching UI delegates (such as for progress bar or text components) to find the best match among all the registered states of the current skin. The specific skin can define its own ComponentState instances that use these facets. For example, NebulaSkin defines a number of component states that use the ComponentStateFacet.DETERMINATE facet, and maps the matching color schemes. At runtime, the procedure described in the previous item will match the state of the specific progress bar to the states defined in this skin, and use the matching color schemes.
  3. Custom application components may have facets that do not directly map to the core facets defined in the ComponentStateFacet class. In this case, the application code can create its own facet instances, and its own component states that use those facets in the on and off lists. Part of the custom code will be in the UI delegates that compute the current state of the custom component using the new facets. Other part of the custom code will be in the skin definition that maps the component states defined with the new facets to the specific color schemes.

Note that you do not have to create explicit dependency between custom component states used in the skin definition and custom component states used in the painting routines (in the UI delegates). In fact, the custom component states defined in the Substance UI delegate for progress bar are not accessible to the application code. The recommended way to separate the skin definition from the model lookups in the painting is:

The skin definition defines a sufficiently broad set of custom component states that use the new facets. Note that you do not have to create a custom state for every possible permutation of new facets (along with the relevant core facets). A well defined set of component states will provide a good fallback state for every relevant permutation of facets, keeping the skin definition small and manageable.
The UI delegate that queries the component model will use accurate component states that account for all the relevant on and off facets – including the core facets defined in the ComponentStateFacet class. When this (perhaps elaborate) state is passed to SubstanceColorSchemeBundle.getColorScheme(ColorSchemeAssociationKind, ComponentState) API, the the procedure described above will match the this state to one of the “base” states defined in your skin, and use the matching color scheme.
Note that the matching algorithm only looks at the facets in the on and off lists, and ignores the component state name. This allows you to create a broad component state in your skin, and a number of narrow component states during the painting – and have the Substance skinning layer find the best match.

When the matching algorithm cannot find a sufficiently close match, the skinning layer will fall back on one of the three base color schemes passed to the SubstanceColorSchemeBundle.SubstanceColorSchemeBundle(SubstanceColorScheme, SubstanceColorScheme, SubstanceColorScheme) constructor. States with ComponentStateFacet.ENABLE in their off list will fall back to the disabled color scheme. The ComponentState.ENABLED will fall back to the enabled color scheme. The rest of the states will fall back to the active color scheme. To change the fallback behavior pass a non-null fallback color scheme to the ComponentState.ComponentState(String, ComponentState, ComponentStateFacet[], ComponentStateFacet[]) constructor as the second parameter.

Let’s see two examples. As mentioned above, the Nebula skin defines custom color schemes for progress bars using the ComponentStateFacet.DETERMINATE:

ComponentState determinateState = new ComponentState("determinate",
	new ComponentStateFacet[] { ComponentStateFacet.ENABLE,
			ComponentStateFacet.DETERMINATE }, null);
ComponentState indeterminateState = new ComponentState("indeterminate",
	new ComponentStateFacet[] { ComponentStateFacet.ENABLE },
	new ComponentStateFacet[] { ComponentStateFacet.DETERMINATE });
SubstanceColorScheme determinateScheme = schemes
	.get("Nebula Determinate");
SubstanceColorScheme determinateBorderScheme = schemes
	.get("Nebula Determinate Border");
defaultSchemeBundle.registerColorScheme(determinateScheme,
	determinateState, indeterminateState);
defaultSchemeBundle.registerColorScheme(determinateBorderScheme,
	ColorSchemeAssociationKind.BORDER, determinateState,
	indeterminateState);

ComponentState determinateDisabledState = new ComponentState(
	"determinate disabled",
	new ComponentStateFacet[] { ComponentStateFacet.DETERMINATE },
	new ComponentStateFacet[] { ComponentStateFacet.ENABLE });
ComponentState indeterminateDisabledState = new ComponentState(
	"indeterminate disabled", null, new ComponentStateFacet[] {
			ComponentStateFacet.ENABLE,
			ComponentStateFacet.DETERMINATE });
SubstanceColorScheme determinateDisabledScheme = schemes
	.get("Nebula Determinate Disabled");
SubstanceColorScheme determinateDisabledBorderScheme = schemes
	.get("Nebula Determinate Disabled Border");
defaultSchemeBundle.registerColorScheme(determinateDisabledScheme,
	determinateDisabledState, indeterminateDisabledState);
defaultSchemeBundle.registerColorScheme(
	determinateDisabledBorderScheme,
	ColorSchemeAssociationKind.BORDER, determinateDisabledState,
	indeterminateDisabledState);

And the resulting visuals – note that the progress bars use brown color scheme, while all the other controls use gray colors:

https://substance.dev.java.net/release-info/6.0/nebula-progressbars.png

The second example is from the Magellan skin that registers a specific color scheme to be used for painting uneditable text components:

ComponentState uneditable = new ComponentState("uneditable",
	new ComponentStateFacet[] { ComponentStateFacet.ENABLE },
	new ComponentStateFacet[] { ComponentStateFacet.EDITABLE });
SubstanceColorScheme uneditableControls = colorSchemes
	.get("Magellan Uneditable Controls");
defaultColorSchemeBundle.registerColorScheme(uneditableControls,
	ColorSchemeAssociationKind.FILL, uneditable);

Note that if your custom components use the DETERMINATE or EDITABLE facets in the computation of their states, they will get the matching visuals from the corresponding core Substance skins.

Animations – footnotes

January 12th, 2010

The animation series that was published on this blog last week has been largely the product of reworking the animation layer in Substance look-and-feel and replacing it with the Trident animation library. This work has some implications for the users of both library, and today i’m going to talk about those.

If you’re using Substance look-and-feel library in your applications, you will need to add the matching Trident jar to your classpath – starting from release 6.0 of Substance. The matching Trident version is 1.2 and it will be officially released at the same time with Substance 6.0. While this is not a major Trident release, it does remove deprecated APIs and as such will break applications that are using those APIs. All the removed APIs have direct replacements, and the final release notes will provide additional information (if you cannot find it in the code). The final Trident 1.2 / Substance 6.0 releases are scheduled for March-April 2010 timeframe.

Substance 6.0 breaks API signatures of most published painter interfaces. The painter interfaces that received two color schemes and the cycle position now only receive a single color scheme. If you are just using Substance as the application look-and-feel, you should not need to worry. If you are using Substance painter APIs to create consistent visuals for your custom / 3rd party components, you will need to change the code. If you have passed the same color scheme to a painter API call, changing your code is simple. If you passed two different color schemes, you will need to call the Substance API twice, changing the graphics composite to match the value of the cycle position.

Breaking the painter APIs is a necessary step to enable multi-state color transitions discussed at length in this series. The old APIs assumed that animating a control always involves two states – previous and current. This assumption is not correct. Suppose your buttons are painted with light blue color. When the mouse is over a button, the button is painted with light yellow color, and when the button is pressed, it is painted with saturated orange. Substance animates the button colors based on the state transitions. Suppose it takes 500ms to complete a single animation. The user moves the mouse over a button, and Substance starts animating the color from light blue to light yellow. Halfway through the animation (250ms), the user presses the button. Now, there are three states participating in the animation: default with light blue, rollover with light yellow and pressed with saturated orange. All the states contribute to the overall appearance of the button as long as the combined animation is in progress.

Tracking state transitions is done internally in Substance – in a layer built on top of Trident base timeline APIs. The tracking layer is not going to be part of Trident 1.2, since it is closely tied not only to Swing classes, but to Substance skinning model. Applications interested in adopting multi-state transitions should not use the internal state tracker layer of Substance. It is subject to change at any point in time. The base rules for multi-state transitions are derived from the examples in this series that have shown different movement paths of a physical object between three points.

The current implementation of the multi-state transitions in Substance 6.0dev does not model most of the physical laws discussed in this series. Handling momentum / inertia, direction change involving smooth turns, and the matching velocity models are not implemented. The work on this will continue throughout 2010, and some of it might find its way to the Trident itself. I am not aware of any other animation library (Java based or otherwise) that provides out-of-the-box support even for simple animations based on the physical rules discussed in this series.

Substance animations deal exclusively with colors. The current straight-line / straight-turn / no-momentum movement paths in the RGB color space result in visually consistent and smooth animations, for any reasonable durations. However, the long term goal for both Trident and Substance is to create mathematically correct animation model which is based on the rules of the physical world – where applicable. At the present moment the mathematics behind the graphs in this series is left as an exercise to the readers.