I am finishing my live wallpaper and it is working well apart from performance issues, for now I applied a script that helps for swipe gestures and touch detection.
public void onTouchEvent(MotionEvent touchevent)
{
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
if (prefs.getBoolean("Touch_lock_key",true))
{
switch (touchevent.getAction())
{
// when user first touches the screen we get x and y coordinate
case MotionEvent.ACTION_DOWN:
{
x1 = touchevent.getX();
y1 = touchevent.getY();
break;
}
case MotionEvent.ACTION_MOVE:
{
x2 = touchevent.getX();
y2 = touchevent.getY();
//right swipe
if (x1-x2< 0 )
{
try {
mImagesArrayIndex++;
Thread.sleep(SeekBarPreference1.mCurrentValue1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// left swipe
if (x1-x2>0 )
{
try {
mImagesArrayIndex--;
Thread.sleep(SeekBarPreference1.mCurrentValue1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
break;
}
}
return;
}
}
As you can see I utilize .getaction function to manage touch and swipe gestures. Its working but not as I expected. The problems are:
ultra sensitive to the touch and becomes annoying when using this live wallpaper.
doesn't follow left-right swipes properly.
left-right swipes doesn't stop when finger swipe stops across the screen.
What I want:
any other swipe gesture manager script that might help with my case (live wallpaper - no activity is involved here)
smooth swipe effect like we see on any live wallpapers out there, take the titanfall LW as a sample.
Here's a video that might help you understand the situation:
https://drive.google.com/file/d/0B44QwXQHh5irOGlXNDNpOThYb3c/view?usp=sharing
onTouch() is called several times for any given gesture, so you're incrementing much more than you want to. A start would be to use GestureDetector and GestureDetector.SimpleGestureListener.
Sic:
private GestureDetector mDetector
...
#Override
protected void onCreate(...) {
...
mDetector = new GestureDetector(this, mGestureListener);
setOnTouchListener(new CustomTouchListener());
...
}
...
private class CustomTouchListener implements View.OnTouchListener {
#Override
public boolean onTouch(View v, MotionEvent e) {
return mDetector.onTouchEvent(e);
}
}
...
private GestureDetector.SimpleOnGestureListener mGestureListener
= new GestureDetector.SimpleOnGestureListener {
#Override
public boolean onDown(MotionEvent e) {
...
}
// the IDE will create all these methods for you
//EDIT: I forgot SimpleOnGestureListener isn't abstract, so it won't do this. OnGestureListener is, so the IDE will.
};
The basic sequence of a swipe looks like this
onDown()
onScroll()
onScroll()
....
onScroll()
onFling()
So if you really want to handle the gesture yourself, the events you're looking for are onDown and on Fling (you could do this with onTouch ACTION_DOWN and ACTION_UP, but SimpleOnGestureListener gives you more functions).
But I would really suggest looking into a view that already handles all this for you, like ViewPager
I found a solution, this code works the same as the previous one I just added some modifications concerning precision and touch tracking..
#Override
public void onTouchEvent(MotionEvent event) {
//when user touches the screen
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
//reset deltaX and deltaY
deltaX=0;
//get initial positions
initialX = event.getRawX();
initialY = event.getRawY();
}
//swipe gestures tracking
if(event.getAction() == MotionEvent.ACTION_MOVE)
{
deltaX = event.getRawX() - initialX;
deltaY = event.getRawY() - initialY;
if(deltaY < 0)
{
//Actions UP
}
else
{
//Actions DOWN
}
if(deltaX < 0)
{
initialX = event.getRawX();
// actions RIGHT
}
}
else
{
initialX = event.getRawX();
// actions LEFT
}
}
return;
}
}
here's a result:
https://drive.google.com/file/d/0B44QwXQHh5irZUtEM3ZZQmRHcFk/view?usp=sharing
I hope someone finds this helpful &... HAPPY CODING !
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've implemented SwipeRefreshLayout and ViewPager in my app but there is a big trouble: whenever I'm going to swipe left / right to switch between pages the scrolling is too sensitive. A little swipe down will trigger the SwipeRefreshLayout refresh too.
I want to set a limit to when horizontal swipe starts, then force horizontal only until swiping is over. In other words, I want to cancel vertical swipping when finger is moving horizontally.
This problem only occurs on ViewPager, if I swipe down and SwipeRefreshLayout refresh function is triggered (the bar is shown) and then I move my finger horizontally, it still only allows vertical swipes.
I've tried to extend the ViewPager class but it isn't working at all:
public class CustomViewPager extends ViewPager {
public CustomViewPager(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean in = super.onInterceptTouchEvent(ev);
if (in) {
getParent().requestDisallowInterceptTouchEvent(true);
this.requestDisallowInterceptTouchEvent(true);
}
return false;
}
}
Layout xml:
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/viewTopic"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.myapp.listloader.foundation.CustomViewPager
android:id="#+id/topicViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
any help would be appreciated, thanks
I am not sure if you still have this issue but Google I/O app iosched solves this problem thusly:
viewPager.addOnPageChangeListener( new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled( int position, float v, int i1 ) {
}
#Override
public void onPageSelected( int position ) {
}
#Override
public void onPageScrollStateChanged( int state ) {
enableDisableSwipeRefresh( state == ViewPager.SCROLL_STATE_IDLE );
}
} );
private void enableDisableSwipeRefresh(boolean enable) {
if (swipeContainer != null) {
swipeContainer.setEnabled(enable);
}
}
I have used the same and works quite well.
EDIT: Use addOnPageChangeListener() instead of setOnPageChangeListener().
Solved very simply without extending anything
mPager.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
mLayout.setEnabled(false);
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
mLayout.setEnabled(true);
break;
}
return false;
}
});
work like a charm
I've met your problem. Customize the SwipeRefreshLayout would solve the problem.
public class CustomSwipeToRefresh extends SwipeRefreshLayout {
private int mTouchSlop;
private float mPrevX;
public CustomSwipeToRefresh(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPrevX = MotionEvent.obtain(event).getX();
break;
case MotionEvent.ACTION_MOVE:
final float eventX = event.getX();
float xDiff = Math.abs(eventX - mPrevX);
if (xDiff > mTouchSlop) {
return false;
}
}
return super.onInterceptTouchEvent(event);
}
See the ref: link
I based this off a previous answer but found this to work a bit better. The motion starts with an ACTION_MOVE event and ends in either ACTION_UP or ACTION_CANCEL in my experience.
mViewPager.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mSwipeRefreshLayout.setEnabled(false);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mSwipeRefreshLayout.setEnabled(true);
break;
}
return false;
}
});
For some reason best known only to them, the support library developer team saw fit to forcefully intercept all vertical drag motion events from SwipeRefreshLayout's child layout, even when a child specifically requests ownership of the event. The only thing they check for is that vertical scroll state of it's main child is at zero (in the case that it's child is vertically scrollable). The requestDisallowInterceptTouchEvent() method has been overridden with an empty body, and the (not so) illuminating comment "Nope".
The easiest way to solve this issue would be to just copy the class from the support library into your project and remove the method override. ViewGroup's implementation uses internal state for handling onInterceptTouchEvent(), so you cannot simply override the method again and duplicate it. If you really want to override the support library implementation, then you will have to set up a custom flag upon calls to requestDisallowInterceptTouchEvent(), and override onInterceptTouchEvent() and onTouchEvent() (or possibly hack canChildScrollUp()) behavior based on that.
There is one problem with the solution of nhasan:
If the horizontal swipe that triggers the setEnabled(false) call on the SwipeRefreshLayout in the OnPageChangeListener happens when the SwipeRefreshLayout has already recognized a Pull-to-Reload but has not yet called the notification callback, the animation disappears but the internal state of the SwipeRefreshLayout stays on "refreshing" forever as no notification callbacks are called that could reset the state. From a user perspective this means that Pull-to-Reload is not working anymore as all pull gestures are not recognized.
The problem here is that the disable(false) call removes the animation of the spinner and the notification callback is called from the onAnimationEnd method of an internal AnimationListener for that spinner which is set out of order that way.
It took admittedly our tester with the fastest fingers to provoke this situation but it can happen once in a while in realistic scenarios as well.
A solution to fix this is to override the onInterceptTouchEvent method in SwipeRefreshLayout as follows:
public class MySwipeRefreshLayout extends SwipeRefreshLayout {
private boolean paused;
public MySwipeRefreshLayout(Context context) {
super(context);
setColorScheme();
}
public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setColorScheme();
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (paused) {
return false;
} else {
return super.onInterceptTouchEvent(ev);
}
}
public void setPaused(boolean paused) {
this.paused = paused;
}
}
Use the MySwipeRefreshLayout in your Layout - File and change the code in the solution of mhasan to
...
#Override
public void onPageScrollStateChanged(int state) {
swipeRefreshLayout.setPaused(state != ViewPager.SCROLL_STATE_IDLE);
}
...
I've found a solution for ViewPager2. I use reflection for reducing drag sensitivity like this:
/**
* Reduces drag sensitivity of [ViewPager2] widget
*/
fun ViewPager2.reduceDragSensitivity() {
val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView")
recyclerViewField.isAccessible = true
val recyclerView = recyclerViewField.get(this) as RecyclerView
val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop")
touchSlopField.isAccessible = true
val touchSlop = touchSlopField.get(recyclerView) as Int
touchSlopField.set(recyclerView, touchSlop*8) // "8" was obtained experimentally
}
It works like a charm for me.
2020-10-17
a minimal addition to #nhasan perfect answer.
if you have migrated from ViewPager to ViewPager2, use
registerOnPageChangeCallback method for listening scroll events
mPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
swipe.setEnabled(state == ViewPager2.SCROLL_STATE_IDLE);
}
});
There could be a problem with #huu duy answer when the ViewPager is placed in a vertically-scrollable container which, in turn, is placed in the SwiprRefreshLayout
If the content scrollable container is not fully scrolled-up, then it may be not possible to activate swipe-to-refresh in the same scroll-up gesture.
Indeed, when you start scrolling the inner container and move finger horizontally more then mTouchSlop unintentionally (which is 8dp by default),
the proposed CustomSwipeToRefresh declines this gesture. So a user has to try once more to start refreshing.
This may look odd for the user.
I extracted the source code f the original SwipeRefreshLayout from the support library to my project and re-wrote the onInterceptTouchEvent().
private float mInitialDownY;
private float mInitialDownX;
private boolean mGestureDeclined;
private boolean mPendingActionDown;
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ensureTarget();
final int action = ev.getActionMasked();
int pointerIndex;
if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
}
if (!isEnabled() || mReturningToStart || mRefreshing ) {
// Fail fast if we're not in a state where a swipe is possible
if (D) Log.e(LOG_TAG, "Fail because of not enabled OR refreshing OR returning to start. "+motionEventToShortText(ev));
return false;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop());
mActivePointerId = ev.getPointerId(0);
if ((pointerIndex = ev.findPointerIndex(mActivePointerId)) >= 0) {
if (mNestedScrollInProgress || canChildScrollUp()) {
if (D) Log.e(LOG_TAG, "Fail because of nested content is Scrolling. Set pending DOWN=true. "+motionEventToShortText(ev));
mPendingActionDown = true;
} else {
mInitialDownX = ev.getX(pointerIndex);
mInitialDownY = ev.getY(pointerIndex);
}
}
return false;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER) {
if (D) Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
return false;
} else if (mGestureDeclined) {
if (D) Log.e(LOG_TAG, "Gesture was declined previously because of horizontal swipe");
return false;
} else if ((pointerIndex = ev.findPointerIndex(mActivePointerId)) < 0) {
return false;
} else if (mNestedScrollInProgress || canChildScrollUp()) {
if (D) Log.e(LOG_TAG, "Fail because of nested content is Scrolling. "+motionEventToShortText(ev));
return false;
} else if (mPendingActionDown) {
// This is the 1-st Move after content stops scrolling.
// Consider this Move as Down (a start of new gesture)
if (D) Log.e(LOG_TAG, "Consider this move as down - setup initial X/Y."+motionEventToShortText(ev));
mPendingActionDown = false;
mInitialDownX = ev.getX(pointerIndex);
mInitialDownY = ev.getY(pointerIndex);
return false;
} else if (Math.abs(ev.getX(pointerIndex) - mInitialDownX) > mTouchSlop) {
mGestureDeclined = true;
if (D) Log.e(LOG_TAG, "Decline gesture because of horizontal swipe");
return false;
}
final float y = ev.getY(pointerIndex);
startDragging(y);
if (!mIsBeingDragged) {
if (D) Log.d(LOG_TAG, "Waiting for dY to start dragging. "+motionEventToShortText(ev));
} else {
if (D) Log.d(LOG_TAG, "Dragging started! "+motionEventToShortText(ev));
}
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged = false;
mGestureDeclined = false;
mPendingActionDown = false;
mActivePointerId = INVALID_POINTER;
break;
}
return mIsBeingDragged;
}
See my example project on Github.
I have some ImageViews inside a HorizontalScrollView.
I would like to be able to drag and drop the ImageViews somewhere else, but still maintain scrolling capability. Dragging should only be activated when the user starts a mostly vertical motion with their finger.
For now, I have drag and drop activate on long-press, but that is not a good solution.
To illustrate:
I had to do exactly this as well. After reading http://techin-android.blogspot.in/2011/11/swipe-event-in-android-scrollview.html I adapted the code as follows:
class MyOnTouchListener implements View.OnTouchListener {
static final int MIN_DISTANCE_Y = 40;
private float downY, upY;
#Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
downY = event.getY();
return true;
}
case MotionEvent.ACTION_MOVE: {
upY = event.getY();
float deltaY = downY - upY;
// swipe vertical?
if (Math.abs(deltaY) > MIN_DISTANCE_Y) {
if (deltaY < 0) {
//Start your drag here if appropriate
return true;
}
if (deltaY > 0) {
//Or start your drag here if appropriate
return true;
}
}
}
}
return false;
}
}
And then set the listener on the ImageViews:
imageView.setOnTouchListener(new MyOnTouchListener());
In this version of the code I am only checking for movement in the vertical direction (I also changed the minimum movement to be 40 instead of 100 as in the original code). If a vertical movement is detected, the specific ImageView can begin to drag or do any other actions you want. If a vertical movement is not detected, the ImageView's MyTouchListener returns false which means the ImageView does not consume the touch event. This allows the parent ScrollView to eventually get the touch event and consume it (for scroll detection). The answers here are helpful for understanding touch events: MotionEvent handling in ScrollView in Android.
I have a Gallery of views that contain a TextView Label and then a listview below that. It works excellent except that in order to get it to flip from element to element, the user has to touch either above the listview (near the label) and fling or in between gallery objects. Sometimes below the listview works too.But I really want to be able to fling while touching the listview too because it takes up a majority of the screen. How can this be done? What code do you need to see?
I had a similar problem and solved this by overriding the Gallery and implementing the onInterceptTouchEvent to ensure that move events are intercepted by the Gallery, and all other events are handled normally.
Returning true in the onInterceptTouchEvent causes all following touch events in this touch sequence to be sent to this view, false leaves the event for it's children.
TouchSlop is needed as when doing a click there is sometimes a small amount of movement.
Would love to claim this as my own idea, but got the basics of the code from the default Android Launcher code.
public class MyGallery extends Gallery{
private MotionEvent downEvent;
private int touchSlop;
private float lastMotionY;
private float lastMotionX;
public MyGallery(Context context) {
super(context);
initTouchSlop();
}
private void initTouchSlop() {
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
touchSlop = configuration.getScaledTouchSlop();
}
#Override public boolean onInterceptTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
final int xDiff = (int) Math.abs(x - lastMotionX);
final int yDiff = (int) Math.abs(y - lastMotionY);
// have we moved enough to consider this a scroll
if (xDiff > touchSlop || yDiff > touchSlop) {
// this is the event we want, but we need to resend the Down event as this could have been consumed by a child
Log.d(TAG, "Move event detected: Start intercepting touch events");
if (downEvent != null) this.onTouchEvent(downEvent);
downEvent = null;
return true;
}
return false;
}
case MotionEvent.ACTION_DOWN: {
// need to save the on down event incase this is going to be a scroll
downEvent = MotionEvent.obtain(ev);
lastMotionX = x;
lastMotionY = y;
return false;
}
default: {
// if this is not a down or scroll event then it is not for us
downEvent = null;
return false;
}
}
}
You would want to set the onTouchListener() on the listview, or maybe the entire Linear/Relative layout.
getListView().setOnTouchListener(yourlistener) OR set it on the entire layout. If you post a little code, I could help you further. XML and how you are using in with the Java class would be most helpful.
My class extends View and I need to get continuous touch events on it.
If I use:
public boolean onTouchEvent(MotionEvent me) {
if(me.getAction()==MotionEvent.ACTION_DOWN) {
myAction();
}
return true;
}
... the touch event is captured once.
What if I need to get continuous touches without moving the finger?
Please, tell me I don't need to use threads or timers. My app is already too much heavy.
Thanks.
Use if(me.getAction() == MotionEvent.ACTION_MOVE). It's impossible to keep a finger 100% completely still on the screen so Action_Move will get called every time the finger moves, even if it's only a pixel or two.
You could also listen for me.getAction() == MotionEvent.ACTION_UP - until that happens, the user must still have their finger on the screen.
You need to set this properties for the element
android:focusable="true"
android:clickable="true"
if not, just produce the down action.
Her is the simple code snippet which shows that how you can handle the continues touch event. When you touch the device and hold the touch and move your finder, the Touch Move action performed.
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
if(isTsunami){
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Write your code to perform an action on down
break;
case MotionEvent.ACTION_MOVE:
// Write your code to perform an action on contineus touch move
break;
case MotionEvent.ACTION_UP:
// Write your code to perform an action on touch up
break;
}
}
return true;
}
Try this. It works to me:
public static OnTouchListener loadContainerOnTouchListener() {
OnTouchListener listener = new OnTouchListener(){
#Override
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()))
{
Log.d(this.getClass().getName(), String.format("Over view.id[%d]", view.getId()));
}
}
}
Remember: the listener you´ll set must be a container layout (Grid, Relative, Linear).
LinearLayout layout = findViewById(R.id.yourlayoutid);
layout.setOnTouchListener(HelperClass.loadContainerOnTouchListener());
This might help,
requestDisallowInterceptTouchEvent(true);
on the parent view, like this -
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
view.getParent().requestDisallowInterceptTouchEvent(true);
switch(motionEvent.getAction()){
}
return false;
}
I was making a game with a custom view used as a thumb control. . . here is what I did
float x = 0, y = 0;
#Override
public boolean onTouchEvent(MotionEvent event) {
x = event.getX();
y = event.getY();
// handle touch events with
switch( event.getActionMasked() ) {
case MotionEvent.ACTION_DOWN :
if(cont)
{
// remove any previous callbacks
removeCallbacks(contin);
// post new runnable
postDelayed(contin, 10);
}
invalidate();
return true;
case MotionEvent.ACTION_MOVE :
if(!cont && thumbing != null)
{
// do non-continuous operations here
}
invalidate();
return true;
case MotionEvent.ACTION_UP :
// set runnable condition to false
x = 0;
// remove the callbacks to the thread
removeCallbacks(contin);
invalidate();
return true;
default :
return super.onTouchEvent(event);
}
}
public boolean cont = false;
// sets input to continuous
public void set_continuous(boolean b) { cont = b; }
public Runnable contin = new Runnable()
{
#Override
public void run() {
if(x != 0)
{
// do continuous operations here
postDelayed(this, 10);
}
}
};
A quick note however, make sure in your main activity that is calling this view removes the callbacks manually via the onPause method as follows
#Override
protected void onPause() {
if(left.cont) left.removeCallbacks(left.contin);
if(right.cont) right.removeCallbacks(left.contin);
super.onPause();
}
That way if you pause and come back touch events aren't being handled twice and the view is free from it's thread's overhead.
** tested on Samsung Galaxy S3 with hardware acceleration on **
All these answer are partially correct but they do not resolve in the right way the problem.
First of all, for everyone out there that decide to track when the event is ACTION_MOVE. Well that works only guess when? When user move his finger, so could if you decide to implement a custom thumb control is okay but for a normal custom button that's not the case.
Second, using a flag inside ACTION_DOWN and check it in ACTION_UP seems the logic way to do it, but as Clusterfux find out if you implement a while(!up_flag) logic you get stuck into troubles ;)
So the proper way to do it is mentioned here:
Continuous "Action_DOWN" in Android
Just keep in mind that if the logic you're going to write during the continuous press has to modify the UI in some way, you have to do it from the main thread in all the other cases it's better use another thread.
You can use the below code snippet as a reference in which I used the background to detect if the screen is held or not...
Main_Layout.setOnTouchListener(new View.OnTouchListener() {
#SuppressLint("ResourceAsColor")
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
Main_Layout.setBackgroundColor(R.color.green);
event.setAction(MotionEvent.ACTION_DOWN);
break;
default:
Main_Layout.setBackgroundColor(R.color.blue);
break;
}
return false;
}
});