GWT ScrollPanel for Touch Screens
Getting this working is easier than you think. Basically the only tricky bit is that the touch object allows for multiple fingers they are represented in the array ‘touches[]‘. Since we are only scrolling we only need to worry about the first finger which is why I use touches[0].screenY. Enjoy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | package com.peterfranza.gwt.mobileui.client.widgets; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.user.client.ui.ScrollPanel; /** * * @author peter.franza * */ public class TouchScrollPanel extends ScrollPanel { private int initialTouchX = -1; private int initialTouchY = -1; private int initialHorizontalOffset; private int initialVerticalOffset; private boolean moved = false; { attachTouch(getElement()); } public TouchScrollPanel(VerticalPanel body) { super(body); } private native void attachTouch(JavaScriptObject ele) /*-{ var ref = this; ele.ontouchstart = function(evt){ evt.preventDefault(); ref.@com.peterfranza.gwt.mobileui.client.widgets.TouchScrollPanel::setInitialTouch(II)(evt.touches[0].screenX, evt.touches[0].screenY); } ele.ontouchmove = function(evt){ evt.preventDefault(); ref.@com.peterfranza.gwt.mobileui.client.widgets.TouchScrollPanel::doScroll(II)(evt.touches[0].screenX, evt.touches[0].screenY); } ele.ontouchend = function(evt){ evt.preventDefault(); ref.@com.peterfranza.gwt.mobileui.client.widgets.TouchScrollPanel::setEndTouch(II)(evt.pageX, evt.pageY); } }-*/; private native void fireClick(int x, int y) /*-{ var theTarget = $doc.elementFromPoint(x, y); if (theTarget.nodeType == 3) theTarget = theTarget.parentNode; var theEvent = $doc.createEvent('MouseEvents'); theEvent.initEvent('click', true, true); theTarget.dispatchEvent(theEvent); }-*/; @SuppressWarnings("unused") private void setInitialTouch(int x, int y) { initialVerticalOffset = getScrollPosition(); initialHorizontalOffset = getHorizontalScrollPosition(); initialTouchX = x; initialTouchY = y; moved = false; } @SuppressWarnings("unused") private void doScroll(int x, int y) { if (initialTouchY != -1) { moved = true; int vDelta = initialTouchY - y; int hDelta = initialTouchX - x; setScrollPosition(vDelta + initialVerticalOffset); setHorizontalScrollPosition(hDelta + initialHorizontalOffset); } } @SuppressWarnings("unused") private void setEndTouch(int x, int y) { if (!moved) { fireClick(x, y); } initialTouchX = -1; initialTouchY = -1; } } |
Did you enjoy this post? Why not leave a comment below and continue the conversation, or subscribe to my feed and get articles like this delivered automatically to your feed reader.
Comments
“ref.@com.orci.datagateway.OpenIMWeb.mobile.client.widgets.TouchScrollPanel::setInitialTouch(II)(evt.touches[0].screenX, evt.touches[0].screenY);”
?
Hi Peter,
This looks promising, but I have the same question as Jim. How do I get “ref.@com.orci.datagateway.OpenIMWeb.mobile.client.widgets.TouchScrollPanel::setInitialTouch(II)(evt.touches[0].screenX, evt.touches[0].screenY);” to compile?
Thanks!
Todd
Todd,
I discovered that the “ref.@…” statement is the real magic of this post. This allows us to refer to the Java code in the TouchScrollPanel class (which will ultimately be compiled into javascript).
The example above should refer to the class com.peterfranza.gwt.mobileui.client.widgets.TouchScrollPanel for this to work (not com.orci.datagateway.OpenIMWeb.mobile.client.widgets.TouchScrollPanel).
Peter,
For some reason this is not propagating the click event to the widget inside the TouchScrollPanel. Any ideas on why this might be? I’m using UiBinder, and that layout generally looks like this:
<g:DockLayoutPanel unit=’EM’>
<g:center>
<ipad:TouchScrollPanel>
<g:FlexTable ui:field=’orderList’ />
</ipad:TouchScrollPanel>
</g:center>
</g:DockLayoutPanel>
But my FlexTable is not receiving any click events on the iPhone/iPad. I’m not a Javascript guru — any help is appreciated.
pfranza Reply:
November 22nd, 2010 at 3:31 pm
I haven’t used the class with the UIBinder stuff, but what I think might be happening is that the UIBinder isn’t passing the body panel in through the constructor of the ScrollPanel. If you were using the class programmatically you would first construct your oversize panel, then pass it into the constructor of the ScrollPanel and then add that to your root panel (or whatever the parent of the scroll is supposed to be).
I think the UIBinder might be creating the scrollpanel and then adding items to it via an add(…) method.
If you load the screen using a desktop browser (firefox/chrome/IE …) do you see scrollbars around your panel?
I have seen this work on ipad and android so I think its probably just a difference in the way UIBinder initializes the object graph.
I look forward to seeing the resolution and any modifications/improvements you might have.
Thanks,
Jeff Reply:
November 22nd, 2010 at 3:57 pm
Desktop browsers apparently work just fine, showing scrollbars and allowing clicks.
I do believe that Widget.add(Widget) is being called; do you think I should call attachTouch(getElement()) after the widget has been added?
Jeff Reply:
November 23rd, 2010 at 10:47 am
I’m having the same trouble even if I don’t use UIBinder and programmatically add the TouchScrollPanel to the window… I think it has something to do with the target being a table.
Jeff Reply:
November 23rd, 2010 at 12:56 pm
Peter, this doesn’t work for me at all. I’ve produced a simple example of a TouchScrollPane that is placed inside a DockLayoutPanel. The TouchScrollPane wraps a VerticalPanel, which contains a Button and a FlexTable, each of which have ClickHandlers. In any browser other than mobile Safari, the button and table both react to mouse clicks, and show scrollbars which work fine.
On the iPad, the one-finger scrolling works, but no clicks are received by either the Button or FlexTable. If I print out the Node returned by
var theTarget = $doc.elementFromPoint(x, y);
it returns the DIV that represents the DockLayoutPanel.
I can send you the source of the example if you would like to see it.
It works for me:
fireClick(initialTouchX, initialTouchY);
Frank Reply:
December 28th, 2010 at 5:27 am
also
evt.touches[0].clientX, evt.touches[0].clientY
in the functions ontouchstart and ontouchmove.
Frank Reply:
December 28th, 2010 at 5:36 am
alternative would also suffice:
evt.changedTouches[0].clientX, evt.changedTouches[0].clientY
then the first two changes not required
Frank Reply:
December 28th, 2010 at 5:37 am
evt.changedTouches[0].clientX, evt.changedTouches[0].clientY in the function ontouchend
Thanks Frank for your reply. I can get the clicks to work now, but I have a problem with a ListBox that I have inside of my touchscrollpanel. The ListBox won’t work with click events. Any ideas?
Frank Reply:
February 3rd, 2011 at 7:09 am
Hallo Dave – try GWT-Version 2.1.1. that should solve your problem
Thanks Frank for your answer, however I find that this solution seems to only work when the the object being clicked is an immediate child of the vertical panel being enclosed by the TouchScrollPanel. I’m thinking this has to do with the instruction in fireClick
var theTarget = $doc.elementFromPoint(x, y);
if (theTarget.nodeType == 3) theTarget = theTarget.parentNode;
but I am not an expert on javascript so I do not know this for certain. Any ideas anyone?



I’ve updated this post so the scroll will propagate click events if the touch doesn’t move
Reply