I'm having a problem implementing a long press within my custom view, based on a HorizontalScrollView.
The HorizontalScrollView has a child LinearLayout, which in turn has a child View. The View draws bitmaps to the canvas via OnDraw().
I'd like to allow the HorizontalScrollView to scroll normally, either fast or slow. But, if the user holds their finger (even if scrolling) on one of the images, it would immediately cancel the scrolling and allow the user to perform a function with the selected image. (In this particular case, they'd be moving the image around the screen, but it could really be any number of functions.)
I've tried many combinations of handling the events (true, false, super) within each layer (HorizontalScrollView and View) but none seem to work 100%. Some combinations get there most of the way, some others part of the way, but they always seem to be missing one feature or another (scroll, hit test, etc.).
The closest I've gotten is to return false within the HorizontalScrollView's onInterceptTouchEvent() and true within the View's onTouch() event. This allows the scroll and also registers the hit test on the image. But, it immediately passes control back to the onTouch() event of the HorizontalScrollView. That makes it impossible to check if the image has been pressed for a number of seconds (long press).
If I return true within the View's onTouch() event, the hit test registers, and I'm able to check if the user has long pressed the image within ACTION_MOVE. But, then the HorizontalScrollView doesn't scroll.
Am I missing something completely obvious, or have I simply chosen two views that don't play well together? Any insight is appreciated.
right,
dont know if you have sorted this or not, I have mashed some bits together that I think do what you ask, if not then hey ho.
I have an activity that loads in the horizontal scroller, this might not be the best way but it works for me:
HolderActivity class (the one that loads in the HorizontalScrollView class) I have:
int selectedItem;
public boolean onLongClick(View v, int position) {
selectedItem = position;
openContextMenu(v);
return true;
}
public boolean onItemClick(int position) {)//do what you want here on click (press)
#Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
String[] menuItems = {"Menu item 1 text", "Cancel"};
for (int i = 0; i<menuItems.length; i++) {
menu.add(Menu.NONE, i, i, menuItemsRemove[i]);
}
menu.setHeaderTitle("My menu title");
}
in your HorizontalScrollView class's constructor pass I pass through a context in there like so:
public MyScroller(Context context) {
super(context);
this.context = context;
}
I have a method for creating the items from an ArrayList called setFeatureItems like so:
public void setFeatureItems(ArrayList<MyListEntity> items){}
Within this method I add a GestureDetector passing the context to it to each item like so:
mGestureDetector = new GestureDetector(context, new MyGestureDetector());
And the MyGestureDetector nested class which has the reference to the all important parentActivity is like this:
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public void onLongPress(MotionEvent arg0) {
parentActivity.onLongClick(MyScroller.this, mActiveFeature);
};
#Override
public boolean onSingleTapUp(MotionEvent arg0) {
parentActivity.onItemClick(mActiveFeature);
return true;
};
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
//right to left
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
int featureWidth = getMeasuredWidth();
mActiveFeature = (mActiveFeature < (mItems.size() - 1))? mActiveFeature + 1:mItems.size() -1;
smoothScrollTo(mActiveFeature*featureWidth, 0);
return true;
}
//left to right
else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
int featureWidth = getMeasuredWidth();
mActiveFeature = (mActiveFeature > 0)? mActiveFeature - 1:0;
smoothScrollTo(mActiveFeature*featureWidth, 0);
return true;
}
} catch (Exception e) {
Log.e("Fling", "There was an error processing the Fling event:" + e.getMessage());
}
return false;
}
}
I have cut this from an existing proj so there might be remnants where I have not made it generic enough, I hope this makes sense or helps, let me know If i can add any more detail
Related
TL;DR
How can I detect whether Android WebView consumed a touch event? onTouchEvent always returns true and WebViewClient's onUnhandledInputEvent is never triggered.
Detailed description
I have multiple WebViews inside a TwoDScrollView. As its name suggests, the TwoDScrollView can be scrolled both vertically and horizontally. The contents of TwoDScrollView can be zoomed in / out. When the user drags his finger or uses pinch-to-zoom, I want to dispatch the touch event to:
WebView if its content is scrollable / zoomable (i.e. only the inside of the WebView will scroll / zoom)
TwoDScrollView if the above condition is false (all contents of the TwoDScrollView will scroll / zoom)
I have partially achieved this by using the canScrollHorizontally and canScrollVertically methods. But these methods only work for "native scrolling". However, in some cases, some JavaScript inside the WebView consumes the touch event, for example Google Maps. In this case, the methods return false. Is there any way to find out whether the WebView's contents consumes the touch events, i.e. is scrollable / zoomable? I cannot change the contents of the WebView, therefore my question is different from this one.
I have considered checking touch handlers by executing some JavaScript inside the Webview by the evaluateJavaScript method, but according to this answer there is no easy way to achieve this and also the page can have some other nested iframes. Any help will be appreciated.
What I've already tried
I overrode WebView's onTouchEvent and read super.onTouchEvent() which always returns true, no matter what.
canScrollHorizontally and canScrollVertically only partially solve this problem, as mentioned above
onScrollChanged isn't useful either
WebViewClient.onUnhandledInputEvent is never triggered
I considered using JavaScript via evaluateJavaScript, but it is a very complicated and ugly solution
I tried to trace the MotionEvent by Debug.startMethodTracing. I found out it is propagated as follows:
android.webkit.WebView.onTouchEvent
com.android.webview.chromium.WebViewChromium.onTouchEvent
com.android.org.chromium.android_webview.AwContents.onTouchEvent
com.android.org.chromium.android_webview.AwContents$AwViewMethodsImpl.onTouchEvent
com.android.org.chromium.content.browser.ContentViewCore.onTouchEventImpl
According to ContentViewCore's source code the touch event ends up in a native method nativeOnTouchEvent and I don't know what further happens with it. Anyway, onTouchEvent always returns true and even if it was possible to find out somewhere whether the event was consumed or not, it would require using private methods which is also quite ugly.
Note
I don't need to know how to intercept touch events sent to WebView, but whether the WebView is consuming them, i.e. is using them for doing anything, such as scrolling, dragging etc.
According to this issue report, not possible.
If the web code is under your control, you can implement some JavaScriptInterface to workaround this. If not, I am afraid there is no solution here.
You can pass all touch events to GestureDetector by overriding onTouchEvent of WebView, so you can know when Android WebView is consuming touch events anywhere, anytime by listening to GestureDetector.
Try like this:
public class MyWebView extends WebView {
private Context context;
private GestureDetector gestDetector;
public MyWebView(Context context) {
super(context);
this.context = context;
gestDetector = new GestureDetector(context, gestListener);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return gd.onTouchEvent(event);
}
GestureDetector.SimpleOnGestureListener gestListener= new GestureDetector.SimpleOnGestureListener() {
public boolean onDown(MotionEvent event) {
return true;
}
public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) {
//if (event1.getRawX() > event2.getRawX()) {
// show_toast("swipe left");
//} else {
// show_toast("swipe right");
//}
//you can trace any touch events here
return true;
}
};
void show_toast(final String text) {
Toast t = Toast.makeText(context, text, Toast.LENGTH_SHORT);
t.show();
}
}
I hope you be inspired.
This code will handle your scrolling events in a webview. This catch the click down and the click up events, and compares the positions of each one. It never minds that the content within the webview is scrollable, just compare the coordinates in the area of webview.
public class MainActivity extends Activity implements View.OnTouchListener, Handler.Callback {
private float x1,x2,y1,y2; //x1, y1 is the start of the event, x2, y2 is the end.
static final int MIN_DISTANCE = 150; //min distance for a scroll event
private static final int CLICK_ON_WEBVIEW = 1;
private static final int CLICK_ON_URL = 2;
private static final int UP_ON_WEBVIEW = 3;
private final Handler handler = new Handler(this);
public WebView webView;
private WebViewClient client;
private WebAppInterface webAppInt = new WebAppInterface(this);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = (WebView)findViewById(R.id.myWebView);
webView.setOnTouchListener(this);
client = new WebViewClient();
webView.setWebViewClient(client);
webView.loadDataWithBaseURL("file:///android_asset/", "myweb.html", "text/html", "UTF-8", "");
}
//HERE START THE IMPORTANT PART
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.myWebView && event.getAction() == MotionEvent.ACTION_DOWN){
x1 = event.getX();
y1 = event.getY();
handler.sendEmptyMessageDelayed(CLICK_ON_WEBVIEW, 200);
} else if (v.getId() == R.id.myWebView && event.getAction() == MotionEvent.ACTION_UP){
x2 = event.getX();
y2 = event.getY();
handler.sendEmptyMessageDelayed(UP_ON_WEBVIEW, 200);
}
return false;
}
#Override
public boolean handleMessage(Message msg) {
if (msg.what == CLICK_ON_URL){ //if you clic a link in the webview, thats not a scroll
handler.removeMessages(CLICK_ON_WEBVIEW);
handler.removeMessages(UP_ON_WEBVIEW);
return true;
}
if (msg.what == CLICK_ON_WEBVIEW){
//Handle the click in the webview
Toast.makeText(this, "WebView clicked", Toast.LENGTH_SHORT).show();
return true;
}
if (msg.what == UP_ON_WEBVIEW){
float deltaX = x2 - x1; //horizontal move distance
float deltaY = y2 - y1; //vertical move distance
if ((Math.abs(deltaX) > MIN_DISTANCE) && (Math.abs(deltaX) > Math.abs(deltaY)))
{
// Left to Right swipe action
if (x2 > x1)
{
//Handle the left to right swipe
Toast.makeText(this, "Left to Right swipe", Toast.LENGTH_SHORT).show ();
}
// Right to left swipe action
else
{
//Handle the right to left swipe
Toast.makeText(this, "Right to Left swipe", Toast.LENGTH_SHORT).show ();
}
}
else if ((Math.abs(deltaY) > MIN_DISTANCE) && (Math.abs(deltaY) > Math.abs(deltaX)))
{
// Top to Bottom swipe action
if (y2 > y1)
{
//Handle the top to bottom swipe
Toast.makeText(this, "Top to Bottom swipe", Toast.LENGTH_SHORT).show ();
}
// Bottom to top swipe action -- I HIDE MY ACTIONBAR ON SCROLLUP
else
{
getActionBar().hide();
Toast.makeText(this, "Bottom to Top swipe [Hide Bar]", Toast.LENGTH_SHORT).show ();
}
}
return true;
}
return false;
}
}
You can also try to control the speed of the swipe, to detect it as a real swipe or scrolling.
I hope that helps you.
Try to set the android:isClickable="true" in the XML and create an onClickListener in the Java code.
Actually now Touch Actions are not supported for webview. But some workarounds are available;
I am going to show it with a longpress example : I am using Pointoption because i will get the coordinate of element and will use it for longpress.
public void longpress(PointOption po) {
//first you need to switch to native view
driver.switchToNativeView();
TouchAction action = new TouchAction((PerformsTouchActions) driver);
action.longPress(po).waitAction(WaitOptions.waitOptions(Duration.ofSeconds(2)));
action.release();
action.perform();
driver.switchToDefaultWebView();
}
For to get the coordinate of element i designed below methood
public PointOption getElementLocation(WebElement element) {
int elementLocationX;
int elementLocationY;
//get element location in webview
elementLocationX = element.getLocation().getX();
elementLocationY = element.getLocation().getY();
//get the center location of the element
int elementWidthCenter = element.getSize().getWidth() / 2;
int elementHeightCenter = element.getSize().getHeight() / 2;
int elementWidthCenterLocation = elementWidthCenter + elementLocationX;
int elementHeightCenterLocation = elementHeightCenter + elementLocationY;
//calculate the ratio between actual screen dimensions and webview dimensions
float ratioWidth = device.getDeviceScreenWidth() / ((MobileDevice) device)
.getWebViewWidth().intValue();
float ratioHeight = device.getDeviceScreenHeight() / ((MobileDevice) device)
.getWebViewHeight().intValue();
//calculate the actual element location on the screen , if needed you can increase this value,for example i used 115 for one of my mobile devices.
int offset = 0;
float elementCenterActualX = elementWidthCenterLocation * ratioWidth;
float elementCenterActualY = (elementHeightCenterLocation * ratioHeight) + offset;
float[] elementLocation = {elementCenterActualX, elementCenterActualY};
int elementCoordinateX, elementCoordinateY;
elementCoordinateX = (int) Math.round(elementCenterActualX);
elementCoordinateY = (int) Math.round(elementCenterActualY);
PointOption po = PointOption.point(elementCoordinateX, elementCoordinateY);
return po;
}
now you have a longpress(PointOption po) and getElementLocation(Webelement element) methods that gives you po. Now everything is ready and you can use them as below..
longpress(getElementLocation(driver.findElement(By.id("the selector can be any of them(xpath,css,classname,id etc.)")));
I have a scrollview with a lot of content. Now when user do a fling or scroll down, I want the scrollview to stop at a particular view location, where I am doing some animation, and then user can again fling or scroll down.
I have tried the disabling of scrollview as mentioned here but It only disables when the scrollview completely stops and cannot stop in the middle of a fling.
Is there any way I can stop a fling of the scrollview when a certain view location or a certain y value is reached?
I had the same problem my solution was.
listView.smoothScrollBy(0,0)
This will stop the scrolling.
To stop a fling at a particular point simply call
fling(0)
If you are only concerned about flings this is the most logical way to do so in my opinion, because velosityYis set to 0 and thereby the fling is stopped immediately.
Here is the javadoc of the fling method:
/**
* Fling the scroll view
*
* #param velocityY The initial velocity in the Y direction. Positive
* numbers mean that the finger/cursor is moving down the screen,
* which means we want to scroll towards the top.
*/
One approach may be to use smoothScrollToPosition, which stops any existing scrolling motion. Note this method requires API level >= 8 (Android 2.2, Froyo).
Note that if the current position is far away from the desired position, then the smooth scrolling will take quite a long time and look a bit jerky (at least in my testing on Android 4.4 KitKat). I also found that a combination of calling setSelection and smoothScrollToPosition could sometimes causes the position to "miss" slightly, this seems to happen only when the current position was very close to the desired position.
In my case, I wanted my list to jump to the top (position=0) when the user pressed a button (this is slightly different from your use case, so you will need to adapt this to your needs).
I used the following method to
private void smartScrollToPosition(ListView listView, int desiredPosition) {
// If we are far away from the desired position, jump closer and then smooth scroll
// Note: we implement this ourselves because smoothScrollToPositionFromTop
// requires API 11, and it is slow and janky if the scroll distance is large,
// and smoothScrollToPosition takes too long if the scroll distance is large.
// Jumping close and scrolling the remaining distance gives a good compromise.
int currentPosition = listView.getFirstVisiblePosition();
int maxScrollDistance = 10;
if (currentPosition - desiredPosition >= maxScrollDistance) {
listView.setSelection(desiredPosition + maxScrollDistance);
} else if (desiredPosition - currentPosition >= maxScrollDistance) {
listView.setSelection(desiredPosition - maxScrollDistance);
}
listView.smoothScrollToPosition(desiredPosition); // requires API 8
}
In my action handler for the button I then called this as follows
case R.id.action_go_to_today:
ListView listView = (ListView) findViewById(R.id.lessonsListView);
smartScrollToPosition(listView, 0); // scroll to top
return true;
The above does not directly answer your question, but if you can detect when the current position is at or near your desired position, then maybe you could use smoothScrollToPosition to stop the scrolling.
You need to disable ScrollView handling of the fling operation. To do this simply override the fling method in ScrollView and comment super.fling(). Let me know if this works !
public class CustomScrollView extends ScrollView {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
return false;
}
#Override
public void fling (int velocityY)
{
/*Scroll view is no longer gonna handle scroll velocity.
* super.fling(velocityY);
*/
}
}
I'm trying to achieve a similar situation. I have a partial solution:
I've managed to stop the ScrollView at a particular y coordinate by overriding onScrollChanged and then calling smoothScrollTo. I allow the user to continue scrolling down past that point by keeping a boolean that indicates if this was the first fling/drag or not. And the same goes for scrolling from bottom to top - I stop the scroll at the same point again. And then allow the user to continue scrolling upward again.
The code looks something like this:
boolean beenThereFromTop = false;
boolean beenThereFromBottom = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ScrollView scrollView = (ScrollView) findViewById(R.id.scrollView);
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
TextView whereToStop = (TextView) findViewById(R.id.whereToStop);
final int y = whereToStop.getBottom();
int scrollY = scrollView.getScrollY();
// manage scrolling from top to bottom
if (scrollY > y) {
if (!beenThereFromTop) {
beenThereFromTop = true;
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.smoothScrollTo(0, y);
}
});
}
}
if (scrollY < y && beenThereFromTop) {
beenThereFromTop = false;
}
// manage scrolling from bottom to top
if (scrollY < y) {
if (!beenThereFromBottom) {
beenThereFromBottom = true;
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.smoothScrollTo(0, y);
}
});
}
}
if (scrollY > y && beenThereFromBottom) {
beenThereFromBottom = false;
}
}
});
}
Well im actualy using this method:
list.post(new Runnable()
{
#Override
public void run()
{
list.smoothScrollToPositionFromTop(arg2, 150);
}
});
I have a TabHost hosting 3 Activities. In addition to the tabs, I want support for swipe gestures to change the current tab. My issue is that one of the views holds a horizontal scrollview, and I cannot figure out how to prevent touches in the horizontal scrollview from changing the current tab.
plateView is the horizontal scrollview that needs to be handled. Finding it's bottom with plateView.getBottom() and not counting touches above that works without the TabHost, but now it returns null and crashes, regardless of where it's called.
onTouchEvent counts touches everywhere and dispatchTouchEvent doesn't count touches on any widget. It seems some combination of them would be great, but together they yield the same functionality as onTouchEvent alone. Swipes anywhere count to change the Activity. My understanding of these is a little fuzzy though.
Why does getBottom() return null? How can I get this to work?
gestureDetector = new GestureDetector(new CalcGestureDetector());
gestureListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG,"onTOUCH");
if(gestureDetector.onTouchEvent(event)){
return true;
}
return false;
}
};
//Takes the touch and interprets it. Handles it. Changes tabs on fling.
class CalcGestureDetector extends SimpleOnGestureListener{
#Override
public boolean onFling(MotionEvent eOne, MotionEvent eTwo, float velocityX, float velocityY){
Log.d(TAG,"WTF "+plateView.getBottom());
if(false){
}
else{
try{
if (Math.abs(eOne.getY() - eTwo.getY()) > flingMaxOffPath)
return false; //Too much of an arc in the fling.
// right to left swipe
if(eOne.getX() - eTwo.getX() > flingMinDistance && Math.abs(velocityX) > flingMinVelocity) {
tabHost.setAnimation(slideLeftIn);
tabHost.setAnimation(slideLeftOut);
tabHost.setCurrentTab((tabHost.getCurrentTab()+1)%3);
} else if (eTwo.getX() - eOne.getX() > flingMinDistance && Math.abs(velocityX) > flingMinVelocity) {
tabHost.setAnimation(slideRightIn);
tabHost.setAnimation(slideRightOut);
tabHost.setCurrentTab((tabHost.getCurrentTab()+2)%3);
}
}
catch(Exception e){
}
}
return false;
}
}
//This method alone keeps a touch in the weights from changing tabs, but won't register
//touches on ANY widget on any screen (like text views).
public boolean dispatchTouchEvent(MotionEvent event){
super.dispatchTouchEvent(event);
return gestureDetector.onTouchEvent(event);
}
//This method alone counts touches for swipes anywhere and everywhere.
#Override
public boolean onTouchEvent(MotionEvent event) {
if (gestureDetector.onTouchEvent(event))
return true;
else
return false;
}
I'm trying to implement a view that has both a longClickListener and a gesture dectector. Basically, I need a button to show a view when the user long clicks on the first view, and then I want to dectect a fling motion up. I would like to make it so that the user does not have to lift their finger at all, and hit both the longclick, and the fling motion.
Here is my code for the longClickListener:
flipCard.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View view) {
answerRight.setVisibility(View.VISIBLE);
answerRight.startAnimation(AnimationUtils.loadAnimation(view.getContext(), R.anim.grow_from_middle));
answerWrong.setVisibility(View.VISIBLE);
answerWrong.startAnimation(AnimationUtils.loadAnimation(view.getContext(), R.anim.grow_from_middle));
return false;
}
});
Here is the code for my gesture dector:
gestureDetectorScore = new GestureDetector(new ScoreGestureDetector());
gestureListenerScore = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetectorScore.onTouchEvent(event)) {
return true;
}
return false;
}
};
private class ScoreGestureDetector extends SimpleOnGestureListener {
private static final int SWIPE_MIN_DISTANCE = 5;
private static final int SWIPE_THRESHOLD_VELOCITY = 2;
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
//if (Math.abs(e1.getX() - e2.getX()) > SWIPE_MAX_OFF_PATH)
//return false;
// right to left swipe
//if(e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
if(e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE){
Toast.makeText(AndroidOrientationSensor.this, "Up Swipe", Toast.LENGTH_SHORT).show();
}
//else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE){
Toast.makeText(AndroidOrientationSensor.this, "Down Swipe", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
// nothing
}
return false;
}
#Override
public boolean onDown(MotionEvent e1){
Toast.makeText(AndroidOrientationSensor.this, "Up Swipe", Toast.LENGTH_SHORT).show();
return true;
}
}
Finally, I am setting the gesture dectector to the the "flipCard" view like this:
flipCard.setOnTouchListener(gestureListenerScore);
Any help would be greatly appreciated.
I think the problem you're going to have is if the touch event is consumed by the button, then the second view won't receive it.
Even if it did, the fling I imagine will only get called when the user performs a fling from start to finish.
The only thing I can think of doing (despite it being very horrible / hacky) is to try and inset a fake up touch event. This might then allow the user to perform the fling, but this isn't particularly stable either.
Better way to handle it would be to have a view group consume the touch events, pass the touch events to a gesture detector to detect a long click, then set a flag within the view groups touch listener to indicate a fling is expected, then manually detect when prev y and current y go beyond a threshold. This will give you some idea of velocity but probably no where near as meaningful as the velocity provided in the onFling callback method.
What I ended up doing to solve this problem is placing a gestureDectector on the view and overriding the onDown method. This allowed me to simulate a click event. I was unable to get both a long click and a swipe event, but for my purposes the click event triggers during the swipe which seems to work well enough. Thanks to GauntFace for the inspiration.
The GestureListener also has a onLongPress event.
While similar questions have been asked in the past they don't seem to really have been answered which might be due to confusion as to what's being asked.
Put simply, I'd like to detect which view is being entered as your finger slides over the screen. The best example of this in action is the soft keyboard on any android phone. When you press any key it shows up as a popup to tell you what letter is under your finger. If you now move your finger over the keyboard in a single gesture the various letters pop up as you move over the various letters of the alphabet.
What listeners are used for this type of behaviour. I've tried OnTouchListeners but they seem to be only when you 'touch' the button as opposed to 'finger past' them
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {doStuff();}
});
button.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
doStuff();
return false;
}
});
OnFocusChangeListener don't help either.
create a Layout
add Views to your Layout
set the setOnTouchListener to your Layout
override the onTouch method with the following:
public boolean onTouch(View v, MotionEvent event)
{
LinearLayout layout = (LinearLayout)v;
for(int i =0; i< layout.getChildCount(); i++)
{
View view = layout.getChildAt(i);
Rect outRect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
if(outRect.contains((int)event.getX(), (int)event.getY()))
{
// over a View
}
}
}
EDIT:
I saw keyboard. I guess, it just one view and coordinates of every letter is known. So you can easily compute which letter the user slides through
AND NOW THE ANSWER:
I'm not sure, but probably this code helps your.
It's so far away, I wrote it for me. But the idea is following.
If I remember right, there is no gesturedetector for views, but you can combine touchlistener of the view with geturelistener of your activity.
Once you've touched your view, you have
private GestureDetector mGestureDetector;
// x and y coordinates within our view
private static float sideIndexX;
private static float sideIndexY;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
...
mGestureDetector = new GestureDetector(this, new SideIndexGestureListener());
}
class MyGestureListener extends
GestureDetector.SimpleOnGestureListener
{
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY)
{
// we know already coordinates of first touch
// we know as well a scroll distance
sideIndexX = sideIndexX - distanceX;
sideIndexY = sideIndexY - distanceY;
// when the user scrolls within our side index
// we can show for every position in it a proper
// item in the country list
if (sideIndexX >= 0 && sideIndexY >= 0)
{
doStuff();
}
return super.onScroll(e1, e2, distanceX, distanceY);
}
}
button.setOnTouchListener(new OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
// now you know coordinates of touch
// store them
sideIndexX = event.getX();
sideIndexY = event.getY();
doStuff();
return false;
}
});
You may want to try GestureDetector.
http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
it's geared to multitouch, but this is a good start toward understanding android touch/gestures, next stop, api docs/samples
The simple answer is you can't - not like the iPhone when in accessibility mode.
Until Ice Cream Sandwich that is. It now has the iPhone-like capability of being able to identify elements under your finger without having to lift it.
It's fairly straight forward to handle this manually.
Using your parent layout as the onTouchListener (in the following example, I extend a RelativeLayout), you can check for collisions between a MotionEvent and the child Views using simple co-ordinate comparison logic:
/** Returns the View colliding with the TouchEvent. */
private final View getCollisionWith(final MotionEvent pMotionEvent) {
// Declare the LocationBuffer.
final int[] lLocationBuffer = new int[2];
// Iterate the children.
for(int i = 0; i < this.getChildCount(); i++) { /** TODO: Order. */
// Fetch the child View.
final View lView = this.getChildAt(i);
// Fetch the View's location.
lView.getLocationOnScreen(lLocationBuffer);
// Is the View colliding?
if(pMotionEvent.getRawX() > lLocationBuffer[0] && pMotionEvent.getRawX() < lLocationBuffer[0] + lView.getWidth() && pMotionEvent.getRawY() > lLocationBuffer[1] && pMotionEvent.getRawY() < lLocationBuffer[1] + lView.getHeight()) {
// Return the colliding View.
return lView;
}
}
// We couldn't find a colliding View.
return null;
}
Calls to getCollisionWith will return View references that may be manipulated arbitrarily.