アイコンをその場で合成しながらアニメーション

SWTを使ったアプリケーションでアニメーションをさせるためのコード片を公開しておきます。(ほとんど自分のためにですが。)肝はThread#sleep()でフレーム秒間を調整したらだめで、Display#timerExec(int,Runnable)を使って再帰的に画像合成と描画を行うようにしておくことです。この考え方はDraw2dを使ったアプリケーションでももちろん有効です。

  private static final class AnimationListener implements Listener {

    // WindowsとLinuxで画像の位置が変なので、調整するための定数を宣言
    private static final int PLATFORM_DELTA_X;
    private static final int PLATFORM_DELTA_Y;
    static {
      if("gtk".equals(SWT.getPlatform())){
        PLATFORM_DELTA_X = 1;
        PLATFORM_DELTA_Y = 3;
      }else{
        PLATFORM_DELTA_X = 0;
        PLATFORM_DELTA_Y = 0;      
      }
    }
    
    private int animationIndex = 0;
    private ImageData[] animationImages;
    protected int per10msec;
    private Control control;

    private AnimationListener(Control control) {
      this.control = control;
      // アニメーションの画像をロードする
      ImageLoader loader = new ImageLoader();
      InputStream stream = this.getClass().getResourceAsStream("loading.gif");
      animationImages = loader.load(stream);
    }

    public void handleEvent(final Event event) {
      if (event.item instanceof TreeItem) {
        TreeItem item = (TreeItem) event.item;
        Rectangle rect = item.getImageBounds(0);
        // elementはアニメーションの終了条件を判断する。
        // SWTでは各Controlはdata領域にモデルを格納できる。SWTプラットフォームからはdata領域は触らない。
        Object element = item.getData();
        if (element instanceof INotificationService) {
          INotificationService service = (INotificationService) element;
          Image image = item.getImage();
          doAnimation(control, item, rect, image, service);
          // 終了後の画像描画
          if(image != null && !event.gc.isDisposed()) {
            event.gc.drawImage(image, rect.x + PLATFORM_DELTA_X,rect.y + PLATFORM_DELTA_Y);
          }
        }
      }
    }

    private void doAnimation(final Control tree,
        final TreeItem item, final Rectangle rect, final Image image,final INotificationService service) {
      // アニメーション用のRunnableインスタンス
      Runnable animation = new Runnable() {
        public void run() {
          if(item.isDisposed()) return;
          GC imageGc = new GC(tree);
          final Image animationImage = getAnimationImage(image.getImageData());
          if(animationImage != null) {
            imageGc.drawImage(animationImage, rect.x + PLATFORM_DELTA_X,rect.y + PLATFORM_DELTA_Y);
            animationImage.dispose();
          }            
          if(service.isLoading()){
            // ここが肝。再帰的にRunnableを呼び出す 
            tree.getDisplay().timerExec(per10msec,this);
          }else{
            imageGc.drawImage(image,rect.x + PLATFORM_DELTA_X,rect.y + PLATFORM_DELTA_Y);
          }
          imageGc.dispose();
        }
      };
      if(service.isLoading()){
        tree.getDisplay().asyncExec(animation);
      }else{
        GC imageGc = new GC(tree);
        imageGc.drawImage(image,rect.x + PLATFORM_DELTA_X,rect.y + PLATFORM_DELTA_Y);
        imageGc.dispose();
      }
    }

    private Image getAnimationImage(final ImageData baseImage){
      //画像合成のためのクラス。めんどいので匿名に。
      CompositeImageDescriptor icon = new CompositeImageDescriptor(){
        @Override
        protected void drawCompositeImage(int width, int height) {
          // 合成するための元の画像を書き出す
          drawImage(baseImage,0,0);
          // 合成するアニメーションの画像を取得する
          ImageData imageData = animationImages[animationIndex];
          animationIndex = animationIndex == animationImages.length - 1 ? 0 : animationIndex + 1;
          drawImage(imageData, 0, 0);
          // アニメーションのフレーム秒間を計算する
          per10msec = imageData.delayTime * 10;
        }

        @Override
        protected Point getSize() {
          return new Point(16,16);
        }
      };
      return icon.createImage();
    }
  }  
  
  ViewLabelProvider(TreeViewer viewer){
    Control tree = viewer.getControl();
    // 描画時のListenerとして登録する
    tree.addListener(SWT.PaintItem, new AnimationListener(tree));
  }