I have a viewflipper which includes a number of layouts. Each layout has a scrollview as the root.
Below the viewflipper I have a horizontal scrollview which contains textviews which acts as a navigation bar.
My original issue was that the fling stopped working as soon as I added a scrollview but I added the following code:
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
super.dispatchTouchEvent(ev);
return gestureDetector.onTouchEvent(ev);
}
And the scrolling now works along with the fling.
The only issue I have now though is that the HSV nav bar now acts a little strange. Sometimes when I try to slide it across, to select new contents, the HSV springs back to where it was. Other times the HSV thinks I want to fling and moves to the next contents. This is of course dependant of the speed I try to scroll it.
I want the nav bar to work independently of the flipper.
I'm trying to understand the code to fix it myself but not getting anywhere.
I have the following in my onCreate:
gestureDetector = new GestureDetector(new MyGestureDetector());
gestureListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
}
return false;
}};
I have the MyGestureDetector class:
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
return false;
// right to left swipe
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
flipper.setInAnimation(flip_in_from_right);
flipper.setOutAnimation(flip_out_to_left);
flipper.showNext();
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
flipper.setInAnimation(flip_in_from_left);
flipper.setOutAnimation(flip_out_to_right);
flipper.showPrevious();
}
} catch (Exception e) {
// nothing
}
And then the override:
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
super.dispatchTouchEvent(ev);
return gestureDetector.onTouchEvent(ev);
}
Can anyone shed any light on this?
Where exactly is your HSV? Is it pinned to the bottom of the screen? If so, here's what I would do. Within your onFling event, check the Y position of the fling and make sure it's not within your HSV. If it is, then return false and you should be good to go.
Related
I have a simple activity which has a root layout.The root layout contains a recyclerview. Recyclerview will contain 20 items created from items.xml ,
which consists of 3 text views.Here items.xml represent single item in the recyclerview.
The recycler view can be scrolled both the ways , it is kind a loop of the items between 1 to 20.
I have a requirement to smoothscroll only 4 items , irrespective of the velocity of the swipe/fling.
Approaches which i have tried many approaches but coudn't succeed till now. If anyone can give any suggestions , that would be very helpful.
SnapHelper - this seems to snap only the item which is in the center.
Using gesture detector - the gestures are getting detected for all the actions , but i am unable to prevent the recyclerview's default scrolling behaviour.
The main issue is the over-scrolling of recycler view due to varing velocities of user scrolls and flings.
Here is my set up for the gesture detector.
public myRecyclerTouchListner(final Context context, final RecyclerView recycleView,
final LinearLayoutManager linearLayoutManager,
final ClickListener clicklistener){
final ViewConfiguration vc = ViewConfiguration.get(context);
final int swipeMinDistance = vc.getScaledPagingTouchSlop();
final int swipeThresholdVelocity = vc.getScaledMinimumFlingVelocity();
final int swipeMaxOffPath = vc.getScaledTouchSlop();
this.clicklistener=clicklistener;
simpleGestureDetector =new GestureDetector(context,new GestureDetector.SimpleOnGestureListener( ){
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d("try", "on Fling called");
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE &&
Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
//From Right to Left
recycleView.smoothScrollToPosition(linearLayoutManager.
findLastCompletelyVisibleItemPosition() + Constants.spanLength);
return true;
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE &&
Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
//From Left to Right
recycleView.smoothScrollToPosition(linearLayoutManager.
findFirstCompletelyVisibleItemPosition() - Constants.spanLength);
return true;
}
return true;
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return true;
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public boolean onDoubleTap(MotionEvent e) {
return true;
}
#Override
public void onLongPress(MotionEvent e) {
Log.d("Try", "Long press gesture");
View child=recycleView.findChildViewUnder(e.getX(),e.getY());
if(child!=null && clicklistener!=null){
clicklistener.onLongClick(child,recycleView.getChildAdapterPosition(child));
}
}
});
I think your problem is that scrolling is starting from last visible position where Fling is detected and that mess up the things.
Try with global variable for example int lastScrolledPosition = 0;.
And OnFling recycleView.smoothScrollToPosition(lastScrolledPosition + Constants.spanLength);
I thing this should do the trick. You should change if lastScrolledPosition < Recycleritems count.
It turns out I had to use Snaphelper for custom scroll behavior in case wherein I need control over programmatic scroll and user-scroll(fling).
This was exactly what I was looking for:
How to snap RecyclerView items so that every X items would be considered like a single unit to snap to?
I hope it will be helpful :)
What I'm trying to do is change the font size and foreground/background color of a webview.
I have done this using the following method (placed inside the activity):
private void updateFont(int fontSize, String fontColor) {
if (fontSize != 0) {
contentWebview.getSettings().setDefaultFontSize(fontSize);
}
if (!fontColor.equals("")) {
contentWebview.loadUrl(fontColor);
}
}
Now this is working fine. However I added something to my contentWebview (which is a custom webview I created) - below is some snippet of the custom webview:
public class ContentWebview extends WebView {
public ContentWebview(Context context) {
super(context);
}
public ContentWebview(Context context, AttributeSet attrs) {
super(context, attrs);
loadWebview();
gestureDetector = new GestureDetector(new MyGestureDetector());
gestureListener = new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
};
setOnTouchListener(gestureListener);
}
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) {
return false;
}
// right to left swipe
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
int currentPosition = getScrollX()/displayWidth +1;
scrollTo(currentPosition*displayWidth, 0);
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
int currentPosition = getScrollX()/displayWidth;
scrollTo(currentPosition*displayWidth, 0);
}
} catch (Exception e) {
// nothing
}
return true;
}
}
public boolean onTouchEvent(MotionEvent ev) {
if(ev.getAction()==MotionEvent.ACTION_UP) {
int currentPosition = getScrollX()/displayWidth;
if(getScrollX()%displayWidth>displayWidth/2) {
currentPosition++;
} else {
// do nothing
}
scrollTo(currentPosition*displayWidth, 0);
}
return super.onTouchEvent(ev);
}
Basically I've created a custom WebView that snaps to a fixed calculated position when it is scrolled or flinged. Now this is where the problem arises - once I scrolled this particular webview, the previous method (updateFont) doesn't work anymore - the webview doesn't change font size or color.
I have also narrowed down the problem to this particular line:
setOnTouchListener(gestureListener);
If I comment out this line, the updateFont method works again even after I scrolled the webview, but the snap-to-page on fling functionality of my webview is also gone.
What am I missing here?
Update: Problem only occurs in froyo devices. 2.3 and above doesn't seem to have the problem
There's a few issues with the WebView and GestureDetectors - try always returning false in your onFling(..) method instead of true - using a gesture detector in the fashion you do is known to block pretty basic stuff (such as even updating the currently displayed page).
after some tinkering with the project, I found out that the property changes loaded in the custom webview is interpreted after some time. The delay is caused by an onNewPictureListener triggering an endless call to a javascript command, which gets interpreted by the webview first, before executing other stuff. Removing this fixed the problem.
I have a ViewFlipper which holds a single ImageView. i want to be able to swipe to change the image / view, and click the image to play a sound.
I have a GestureDetector which is handling OnFling to change the view. I tried to put an onClickListener to the imageview, which worked but stopped the OnFling working.
Lastly, I added an onSingleTapConfirmed to my GestureDetector. This worked, however it registers a click anywhere in the Activity not just the ImageView.
Can anyone suggest how to narrow down the onSingleTapConfirmed to just work on the Imageview?
Here's my code so far:
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
return false;
// right to left swipe
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
Log.v("Swipe", "Right to Left");
viewFlipper.setInAnimation(slideLeftIn);
viewFlipper.setOutAnimation(slideLeftOut);
viewFlipper.addView(nextImage(true));
viewFlipper.showNext();
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
Log.v("Swipe", "Left to Right");
viewFlipper.setInAnimation(slideRightIn);
viewFlipper.setOutAnimation(slideRightOut);
viewFlipper.addView(nextImage(false));
viewFlipper.showNext();
}
} catch (Exception e) {
// nothing
}
return false;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent e)
{
// TODO Auto-generated method stub
Log.v("SingleTap", "OnSingleTapConfirmed Run");
return super.onSingleTapConfirmed(e);
}
}
private View nextImage(boolean righttoleft) {
ImageView imgv1 = new ImageView(this);
if(righttoleft == true)
{
Uri image = Uri.parse("android.resource://com.owentech.brainybaby/drawable/" + variables.letters[variables.currentpos]);
imgv1.setImageURI(image);
imgv1.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
return imgv1;
}
I had encountered this problem in the past.
Maybe you can try to implement single tap by setting an OnTouchListener to ViewFlipper, thus, the ViewFlipper will be the only touch event receiver. Then write the playing sound code in onTouch function. Finally, set onTouch function's return value to false for the sake of keeping the onFling working.
viewFlipper.setOnTouchListener(new OnTouchListener()
{
#Override
public boolean onTouch(View view, MotionEvent e) {
// TODO Auto-generated method stub
switch(e.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
//put the playing sound code here.
break;
}
return false; //means that the listener dosen't consume the event
}
}
);
It's just a simple solution to solve this problem. If you want to achieve more complicated function such as avoiding sound playing when finger is swiping on the screen to change the view, you have to take more effort to distinguish gestures, e.g. calculate the time between ACTION_DOWN event and ACTION_UP event and play the sound if it's less than 300ms.
I hope this will be helpful to you. :)
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.