/* * Copyright 2011 SWM Services GmbH. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package de.swm.commons.mobile.client.widgets.scroll; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; import de.swm.commons.mobile.client.SWMMobile; import de.swm.commons.mobile.client.base.PanelBase; import de.swm.commons.mobile.client.event.*; import de.swm.commons.mobile.client.utils.Utils; /** * Scroll panel - has the ability to keep the {@link de.swm.commons.mobile.client.widgets.itf.IHeaderPanel} implementations always on the top. */ public class ScrollPanel extends PanelBase implements HasWidgets, DragEventsHandler, SwipeEventsHandler, IScrollPanel { private static final int TIME_FACTOR = 3000; private static final double DISTANCE_FACTOR = 0.25; private static final int DEFAULT_TRANSITION_DURATION = 500; private static final int OFFSET = 2; private boolean myHasTextBox = false; private IScrollMonitor scrollMonitor; private ScrollPanelEventsHandler scrollPanelEventsHandler; private int offsetHeight = -1; /** * Defines how many pixels a button overscrolling should be possible (e.g 50px). */ private int overscrollButtonTolerance = 0; /** * Default constructor. */ public ScrollPanel() { setStyleName(SWMMobile.getTheme().getMGWTCssBundle().getScrollPanelCss().scrollPanel()); } /** * A scroll monitor provides callback when the scrolling is done. * * @param scrollMonitor */ @Override public void setScrollMonitor(IScrollMonitor scrollMonitor) { this.scrollMonitor = scrollMonitor; } public void setHasTextBox(boolean hasTextBox) { myHasTextBox = hasTextBox && SWMMobile.getOsDetection().isAndroid(); } public boolean getHasTextBox() { return myHasTextBox; } @Override public void onLoad() { DragController.get().addDragEventsHandler(this); DragController.get().addSwipeEventsHandler(this); } @Override public void onUnload() { DragController.get().removeDragEventsHandler(this); DragController.get().removeSwipeEventHandler(this); } @Override public Widget getWidget() { return myFlowPanel.getWidget(0); } public ScrollPanelEventsHandler getScrollPanelEventsHandler() { return scrollPanelEventsHandler; } /** * Neuen Handler fuer Scroll Panel Events registrieren. * * @param scrollPanelEventsHandler eventhandler */ public void addScrollPanelEventsHandler(ScrollPanelEventsHandler scrollPanelEventsHandler) { this.scrollPanelEventsHandler = scrollPanelEventsHandler; } /** * Sets the button overscroll tolerance in px * @param overscrollButtonTolerance */ public void setOverscrollButtonTolerance(int overscrollButtonTolerance) { this.overscrollButtonTolerance = overscrollButtonTolerance; } /** * Scrolls to the default position. */ public void reset() { Utils.setTransitionDuration(getWidget().getElement(), 0); Utils.setTranslateY(getWidget().getElement(), 0); } /** * Scrolls to the top. */ @Override public void setPostionToTop() { Utils.setTransitionDuration(getWidget().getElement(), 0); Utils.setTranslateY(getWidget().getElement(), 0); } /** * Scrolls to the button. */ @Override public void setPositionToBottom() { Utils.setTransitionDuration(getWidget().getElement(), 0); Utils.setTranslateY(getWidget().getElement(), this.getElement().getClientHeight() - this.getElement().getScrollHeight()); } /** * Sets the croll position * * @param pos the x axis pos */ @Override public void setScrollPosition(int pos) { if (myHasTextBox) { setStyleTop(pos); } else { Element element = getWidget().getElement(); Utils.setTranslateY(element, pos); } } /** * Returns the current scroll position. * * @return the position */ @Override public int getScrollPosition() { if (myHasTextBox) { return getStyleTop(); } else { Element element = getWidget().getElement(); return Utils.getTranslateY(element); } } /** * Returns the next scroll position * * @return then next scroll position */ @Override public int getScrollToPosition() { if (myHasTextBox) { return getStyleTop(); } else { Element element = getWidget().getElement(); return Utils.getMatrixY(element); } } @Override public void onDragStart(DragEvent e) { if (scrollMonitor != null) { scrollMonitor.onScrollStart(); } int matrix = getScrollToPosition(); int current = getScrollPosition(); Utils.setTransitionDuration(getWidget().getElement(), 0); if (current != matrix) { // scroll on going int diff = current - matrix; int offset = diff > OFFSET ? OFFSET : diff > -OFFSET ? diff : -OFFSET; setScrollPosition(matrix + offset); DragController.get().suppressNextClick(); } } @Override public void onDragMove(DragEvent e) { Element widgetEle = getWidget().getElement(); int panelHeight = Utils.getHeight(this.getElement()); int widgetHeight = calcOffsetHeight(widgetEle); int current = getScrollPosition(); if (current > 0) { // exceed top boundary if (e.getOffsetY() > 0) { // resist scroll down. current += (int) (e.getOffsetY() / OFFSET); // need the cast for production mode. } else { current += e.getOffsetY() * OFFSET; } } else if (-current + panelHeight > widgetHeight) { //correct only scroll position if overscroll is prohibited if (overscrollButtonTolerance == 0) { // exceed bottom boundary if (e.getOffsetY() < 0) { // resist scroll up. current += (int) (e.getOffsetY() / OFFSET); } else { current += e.getOffsetY() * OFFSET; } } else { //if button overscrolling is allowed current += e.getOffsetY(); } } else { current += e.getOffsetY(); } setScrollPosition(current); } private int calcOffsetHeight(Element widgetEle) { return ((this.offsetHeight > 0) ? this.offsetHeight : widgetEle.getOffsetHeight()); } /** * A value greater zero will set the offset height of the panel manually. * Otherwise it will be calculated automatically. * * @param offsetHeight the offset height. */ @Override public void setOffsetHeight(int offsetHeight) { this.offsetHeight = offsetHeight; } @Override public void onDragEnd(DragEvent e) { Element widgetEle = getWidget().getElement(); if (scrollMonitor != null) { scrollMonitor.onScrollEnd(); } int current = getScrollPosition(); if (current == 0) { return; } int panelHeight = Utils.getHeight(this.getElement()); int widgetHeight = getHeightOfElementOrChildElements(widgetEle); if (current > 0 // exceed top boundary || panelHeight > widgetHeight) { // fire eventshandler for top boundary if (getScrollPanelEventsHandler() != null) { getScrollPanelEventsHandler().onTop(e); } Utils.setTransitionDuration(widgetEle, DEFAULT_TRANSITION_DURATION); setScrollPosition(0); } else if (-current + panelHeight > widgetHeight) { // exceed bottom boundary // fire eventshandler for bottom boundary if (getScrollPanelEventsHandler() != null) { getScrollPanelEventsHandler().onBottom(e); } Utils.setTransitionDuration(widgetEle, DEFAULT_TRANSITION_DURATION); setScrollPosition((panelHeight - overscrollButtonTolerance) - widgetHeight); } } @Override public void onSwipeVertical(SwipeEvent e) { Element widgetEle = getWidget().getElement(); int panelHeight = Utils.getHeight(this.getElement()); int widgetHeight = widgetEle.getOffsetHeight(); long current = getScrollPosition(); if ((current >= 0) // exceed top boundary || (-current + panelHeight >= widgetHeight)) { // exceed bottom boundary return; } double speed = e.getSpeed(); double timeFactor = TIME_FACTOR; long time = (long) Math.abs(speed * timeFactor); double dicstanceFactor = DISTANCE_FACTOR; long distance = (long) (speed * time * dicstanceFactor); // Utils.Console("speed " + speed + " time " + time + " distance " + distance + " current " + current); current += distance; // exceed top boundary? if (current > 0) { double timeAdj = 1 - (double) current / distance; time = (long) (time * timeAdj); current = 0; } else if (-current + panelHeight > widgetHeight) { // exceed bottom boundary long bottom = panelHeight - widgetHeight; double timeAdj = 1 - (double) (current - bottom) / distance; time = (long) (time * timeAdj); current = bottom; } Utils.setTransitionDuration(widgetEle, time); setScrollPosition((int) current); } @Override public void onSwipeHorizontal(SwipeEvent e) { } @Override public void add(Widget w) { super.add(w); if (SWMMobile.getOsDetection().isIOs()) { Utils.setTranslateY(w.getElement(), 0); // anti-flickering on iOS. } } /** * Returns the height of the given element. If the element has a height of 0 it searches * recursively for the height of the next visible child (e.g. the next visible slide of a * SliderPanel). * * @param element parent element * @return height of the element of a visible child */ private int getHeightOfElementOrChildElements(Element element) { if (this.offsetHeight > 0) { return this.offsetHeight; } // current element has height? Return this height. if (element.getOffsetHeight() != 0) { return element.getOffsetHeight(); } // current element is hidden? no height for this element. if (element.getStyle().getVisibility().equals("hidden") || element.getStyle().getDisplay().equals("none")) { return 0; } // search children for height NodeList<Node> children = element.getChildNodes(); if (children.getLength() == 0) { return 0; } int height = 0; for (int i = 0; i < children.getLength(); i++) { Node currentNode = children.getItem(i); if (currentNode instanceof Element) { height = getHeightOfElementOrChildElements((Element) currentNode); } if (height != 0) { return height; } } return 0; } /** * Returns the top style * * @return the top style in px */ private int getStyleTop() { Style style = getWidget().getElement().getStyle(); String top = style.getTop(); if (top.isEmpty()) { return 0; } else { return Integer.parseInt(top.replace("px", "")); } } /** * Sets the top stype in pixel * * @param top the top syle */ private void setStyleTop(int top) { Style style = getWidget().getElement().getStyle(); style.setTop(top, Unit.PX); } }