воскресенье, 27 ноября 2011 г.

Анимация в GWT

    Начиная с версии GWT 2.0 система лейаутов имеет прямую поддержку анимации. Это необходимо для поддержки ряда сценариев использования, потому что система лейаутов должна надлежащим образом поддерживать обработку анимации среди различных ее наборов. Панели которые реализуют интерфейс AnimatedLayout, напр. LayoutPanel, DockLayoutPanel, SplitLayoutPanel, могут анимировать их дочерние виджеты из одного набора значений к другому. Обычно это делается путем создания отношений, которые можно оживить путем вызова метода animate()
     Анимация представляет собой полезный инструмент для изменения свойств виджета в непрерывном движении. С ее помощью можно получить более дружественный интерфейс, при правильном использовании ;) Анимацию использовать достаточно легко, нужно унаследоваться от класса Animation и переопределить метод onUpdate()
  Метод onUpdate(double progress) - обновляется каждые 25 миллисекунд, задано это через Animation.DEFAULT_FRAME_DELAY, значение перемен. progress меняется от 0 до 1, по этому каждые 25 миллисекунд мы получим немножко измененное значение progress. После создания экземпляра AnimationEffect мы запускаем анимацию через вызов метода  run(int duration). Продолжительность является длительностью анимации в миллисекундах. 
     Также можно переопределить другие полезные методы класса Animation:
onStart() - вызывается перед началом анимации
onComplete() - вызывается после окончания анимации
onCancel() - вызывается после отмены анимации
     Давайте рассмотрим на примерах как это все работает.
Пример 1: 
     Создадим анимированнyю FlowPanel, где появление и скрытие будет сопровождаться плавным переходом изменения прозрачности, длительность перехода(анимации) можно изменять через переменную.
/**
* @author Dmitry Nikolaenko
*/
public class AnimatedFlowPanel extends FlowPanel {

    /**
     * how long will take the panel to animate
     */
    private final int ANIMATION_DURATION = 1000;
 
    public AnimatedFlowPanel() {
        super();
    }

    @Override
    public void setVisible(final boolean visible) {
        AnimationEffect animationEffect = new AnimationEffect(visible);
        // run animation
        animationEffect.run(ANIMATION_DURATION);
        // when animation will end, the panel must be hidden
        Timer timer = new Timer() {
            @Override
            public void run() {
                AnimatedFlowPanel.this.setVisible(visible);
            }
        };
    }

    /**
     * animation which will change opacity of the panel depending on the show value,
     * false is disappear, true is appear
     */
    private class AnimationEffect extends Animation {
 
        private boolean show = true;
 
        AnimationEffect(boolean show) {
            super();
            this.show = show;
        }

        @Override
        protected void onUpdate(double progress) {
            double opacityValue = progress;
     
            if (!show) {
                opacityValue = 1.0 - progress;
            }
     
            AnimatedFlowPanel.this.getElement().getStyle().setOpacity(opacityValue);
        }
    }
}


* This source code was highlighted with Source Code Highlighter.
Пример 2:
      Анимированная PopupPanel, продолжительность появления и затухания всплывающей панели можно задавать вручную.
public class AnimatedPopupPanel extends PopupPanel {

    /**
     * decides if panel should hide after some time or not
     */
    private boolean shouldHide = true;

    /**
     * how long will take the panel to animate
     */
    private final int ANIMATION_DURATION = 2000;

    public AnimatedPopupPanel(boolean shouldHide) {
        super(true);
        this.shouldHide = shouldHide;
        setAutoHideEnabled(true);
        setAnimationEnabled(true);
        addStyleName("customPopupPanel");
    }

    @Override
    public void show() {
        PopupPanelAnimation showAnimation = new PopupPanelAnimation();
        showAnimation.run(ANIMATION_DURATION);
        super.show();
 
        if (shouldHide) {
            PopupPanelAnimation hideAnimation = new PopupPanelAnimation(false);
            // run hide animation after some time
            hideAnimation.run(ANIMATION_DURATION, Duration.currentTimeMillis() + ANIMATION_DURATION);
            // when animation will end, the widget must be hidden
            Timer timer = new Timer() {
                @Override
                public void run() {
                    AnimatedPopupPanel.this.hide();
                }
            };
        }
    }

    /**
     * animation which will change opacity of the widget depending on the show value,
     * false is disappear, true is appear
     */
    private class PopupPanelAnimation extends Animation {
        boolean show = true;
 
        PopupPanelAnimation(boolean show) {
            super();
            this.show = show;
        }
 
        public PopupPanelAnimation() {
            this(true);
        }

        @Override
        protected void onUpdate(double progress) {
            double opacityValue = progress;
     
            if (!show) {
                opacityValue = 1.0 - progress;
            }
     
            AnimatedPopupPanel.this.getElement().getStyle().setOpacity(opacityValue);
        }
    }
}


* This source code was highlighted with Source Code Highlighter.
Пример 3: 
     Летающая/плавающая панель (FlyingPanel), которая меняет свою позицию в зависимости от выбранного контента (статьи или комментарии). Пример такой панели можно посмотреть на http://www.membrana.ru открыв любую статью с комментариями. Переход на панели article/comments осуществляется с помощью движения мышки, просто нужно переместить курсор на одну из панелей, и она сразу поменяет свое положение для более удобного чтения. Длительность анимации и поведение свойств панелей можно задавать. В примере меняется только положение панелей.
/**
* @author Dmitry Nikolaenko
*/
public class FlyingPanel extends FlowPanel {

    public enum Toggle {
        ARTICLE,
        COMMENTS;
    }

    /**
     * how long will take the panel to animate
     */
    private final int ANIMATION_DURATION = 500;

    private final int DATA_TOGGLE_WIDTH = 200;
    private final int DATA_TOGGLE_HEIGHT = 100;

    /**
     * left css property of the main panel
     */
    private double leftProperty = 0.0;

    private Label article;
    private Label comments;

    public FlyingPanel() {
        super();
        this.getElement().getStyle().setPosition(Position.RELATIVE);
        this.getElement().getStyle().setWidth(420, Unit.PX);
 
        article = new Label("article");
        comments = new Label("comments");
 
        article.addMouseOverHandler(new FlyingMouseOverHandler(Toggle.ARTICLE));
        comments.addMouseOverHandler(new FlyingMouseOverHandler(Toggle.COMMENTS));
 
        initToggleStyle();
 
        add(article);
        add(comments);
    }

    private void initToggleStyle() {
        article.getElement().getStyle().setWidth(DATA_TOGGLE_WIDTH, Unit.PX);
        comments.getElement().getStyle().setWidth(DATA_TOGGLE_WIDTH, Unit.PX);
 
        article.getElement().getStyle().setHeight(DATA_TOGGLE_HEIGHT, Unit.PX);
        comments.getElement().getStyle().setHeight(DATA_TOGGLE_HEIGHT, Unit.PX);
 
        article.getElement().getStyle().setBorderStyle(BorderStyle.SOLID);
        comments.getElement().getStyle().setBorderStyle(BorderStyle.SOLID);
 
        article.getElement().getStyle().setFloat(Float.LEFT);
        comments.getElement().getStyle().setFloat(Float.LEFT);
    }

    /**
     * mouse over handler for both side of the main panel
     */
    private class FlyingMouseOverHandler implements MouseOverHandler {
 
        private Toggle toggle;
 
        private FlyingMouseOverHandler(Toggle toggle) {
            this.toggle = toggle;
        }
 
        @Override
        public void onMouseOver(MouseOverEvent event) {
            setSelected(toggle);
        }
    }

    public void setSelected(Toggle toggle) {
        if (toggle.equals(Toggle.ARTICLE) || leftProperty >= -1) {
            AnimationEffect animationEffect = new AnimationEffect(toggle);
     
            // run animation
            animationEffect.run(ANIMATION_DURATION);
        }
    }

    /**
     * animation which will change left property of the panel depending on the toggle value
     */
    private class AnimationEffect extends Animation {
 
        private Toggle toggle = Toggle.ARTICLE;
 
        AnimationEffect(Toggle toggle) {
            super();
            this.toggle = toggle;
        }

        @Override
        protected void onUpdate(double progress) {
            double leftValue = progress;
     
            boolean fly = toggle.equals(Toggle.COMMENTS);
     
            if (!fly) {
                leftValue = 1.0 - progress;
            }
     
            leftProperty = (fly ? (-DATA_TOGGLE_WIDTH / 2) : leftProperty) * leftValue;
     
            FlyingPanel.this.getElement().getStyle().setLeft(leftProperty, Unit.PX);
        }
    }
}


* This source code was highlighted with Source Code Highlighter.

пятница, 25 ноября 2011 г.

Использование ClientBundle в GWT

     Ресурсы в развернутых GWT приложениях могут быть условно разделены на: некешируемые (.nocache.js), которые всегда кешируются (.cache.html) и “все остальное” (app.css). Интерфейс ClientBundle перемещает записи из категории “все остальное” в всегда кешируемую категорию. 
     ClientBundle - это механизм в GWT для предотвращения медленного выполнения клиентского приложения, он кеширует различные виды ресурсов, напр. изображения, CSS, тестовые и т.д. Преимущество подхода заключается в том, что необходимый ресурс будет загружен один раз, вместо подтягивания каждый раз во время использования. Также уменьшится общий объем хранимых ресурсов. В результате будет уменьшено расходы для хранения однотипных данных. 
     Пример:
Создадим два интерфейса наследуемые от ClientBundle с методами для доступа к изображениям.
public interface FirstBundle extends ClientBundle{
    @Source("first-icon.png")
    ImageResource red();
}

public interface SecondBundle extends ClientBundle {
    @Source("second-icon.png")
    ImageResource blue();
}


* This source code was highlighted with Source Code Highlighter.
     В используемом виджете с помощью аннотации @Inject в Gin, говорим ему использовать этот конструктор. У Gin-а есть специальная обработка для GWT deferred binding, которая потребует минимальной настройки для работы. Т.к. нам не нужно дополнительное конфигурирование, то метод configure() класса ClientBundleModule останется пустым.
@Inject
public MainWidget(FirstBundle resourcesFirst, SecondBundle resourcesSecond) {
    initWidget(uiBinder.createAndBindUi(this));
    
    leftLabel.add(new Image(resourcesFirst.red()));
    rightLabel.add(new Image(resourcesSecond.blue()));
}

@GinModules(ClientBundleModule.class)
public interface ClientBundleInjector extends Ginjector {
    FirstBundle firstBundle();
    SecondBundle secondBundle();
}

public class ClientBundleModule extends AbstractGinModule {
    @Override
    protected void configure() {
    }
}


* This source code was highlighted with Source Code Highlighter.
      После выполнения кода через GWT компилятор на выходе мы получим war-архив с множеством png файлов, среди которых будут first-icon.png и second-icon.png, как отдельные файлы. Давайте объединим наши изображения в одно, тем самым уменьшим загрузку.
public interface ClientBundleExample extends FirstBundle, SecondBundle {
}

public class ClientBundleModule extends AbstractGinModule {
    @Override
    protected void configure() {
        bind(FirstBundle.class).to(ClientBundleExample.class);
        bind(SecondBundle.class).to(ClientBundleExample.class);
    }
}
.....
private final ClientBundleInjector injector = GWT.create(ClientBundleInjector.class);

@Inject
public MainWidget() {
    initWidget(uiBinder.createAndBindUi(this));
    
    leftLabel.add(new Image(injector.firstBundle().red()));
    rightLabel.add(new Image(injector.secondBundle().blue()));
}


* This source code was highlighted with Source Code Highlighter.
Теперь после компиляции мы получим один png файл содержащий два изображения.