I love the new SwipeRefreshLayout! It looks great and its quite easy to use.
But i want to use it in both directions. I have a message screen and i want to
load older messages by swiping from top to bottom and i want to load newer messages by swiping from bottom to top.
This example: http://www.bignerdranch.com/blog/implementing-swipe-to-refresh/
explained quite easily how to get the top to bottom swiping but how can i implement that for both directions?
Based on the source code https://github.com/futuresimple/android-support-v4/blob/master/src/java/android/support/v4/widget/SwipeRefreshLayout.java it's only listening to vertical changes (See public boolean onTouchEvent(MotionEvent event)).
You should be able to override the class and implement this override yourself to enable it to listen to horizontal and vertical swipes but I don't think there's anything out of the box that allows you to do that.
The bit that should be of most interest is this:
case MotionEvent.ACTION_MOVE:
if (mDownEvent != null && !mReturningToStart) {
final float eventY = event.getY();
float yDiff = eventY - mDownEvent.getY();
if (yDiff > mTouchSlop) {
// User velocity passed min velocity; trigger a refresh
if (yDiff > mDistanceToTriggerSync) {
// User movement passed distance; trigger a refresh
startRefresh();
handled = true;
break;
} else {
// Just track the user's movement
setTriggerPercentage(
mAccelerateInterpolator.getInterpolation(
yDiff / mDistanceToTriggerSync));
float offsetTop = yDiff;
if (mPrevY > eventY) {
offsetTop = yDiff - mTouchSlop;
}
updateContentOffsetTop((int) (offsetTop));
if (mPrevY > eventY && (mTarget.getTop() < mTouchSlop)) {
// If the user puts the view back at the top, we
// don't need to. This shouldn't be considered
// cancelling the gesture as the user can restart from the top.
removeCallbacks(mCancel);
} else {
updatePositionTimeout();
}
mPrevY = event.getY();
handled = true;
}
}
}
break;
Please note that I do not recommend this approach. It's not standard interface and no one will expect it to work that way which can be confusing. Overriding this method in a custom layout also has the added risk that you may have to redo everything in the future when a new version comes out and you want to upgrade.
Related
I'm trying to develop an android application similar to "on color measurement" or "color grab" which are already on google play.
I have problem with getting the exact position of a view (which acts like focus square) and move it to the position where the user touches the screen. I have done a lot of searches but all of them ended in onTouchEvent() which I used it but it does not work properly or maybe I have done something wrong.
Actually the view moves but it won't be placed exactly where the user touch, it will go below the touched area in y axis with a distance.
here is my code where the mFocusRectangle is the custom view which I want to move to the touched position:
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
if (event.getPointerCount() > 1) {
// handle multi-touch events
} else {
// handle single touch events
if(action==MotionEvent.ACTION_DOWN){
}
if (action == MotionEvent.ACTION_UP) {
int pointerId = event.getPointerId(0);
int pointerIndex = event.findPointerIndex(pointerId);
// Get the pointer's current position
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
mFocusRectangle.showStart();
mFocusRectangle.setX(x);
mFocusRectangle.setY(y);
}
}
return true;
}
I have also tried MotionEvent.ACTION_DOWN but the result is the same.
Thanks in advance.
try this
float x = event.getX(pointerIndex) - (mFocusRectangle.getWidth() / 2);
float y = event.getY(pointerIndex) - (mFocusRectangle.getHeight() / 2);
credits:
Manitoba - Android move view on touch event
Is it possible to disable SwipeRefreshLayout drag animation on swipe down without class customization?
Try calling:
setEnabled(false)
on your SwipeRefreshLayout view.
Well, disabling SwipeLayoutAnimanion appeared to be a rather simple task, but it involves replication of android.support.v4.widget.SwipeRefreshLayout class inside one's project.
Diving in source code will reveal, that SwipeRefreshLayout consists of three classes:
android.support.v4.widget.SwipeRefreshLayout
android.support.v4.widget.SwipeProgressBar
android.support.v4.widget.BakedBezierInterpolator
All three classes should be included in the project. Then SwipeRefreshLayout can be customized as follows:
Add a new public method which will control either layout should follow the swipe down gesture or not:
private boolean mLayoutMovementEnabled = true;
public void setLayoutMovementEnabled(boolean enabled) {
mLayoutMovementEnabled = enabled;
}
All related computations are performed inside onTouchEvent(). To disable layout following the movement,
updateContentOffsetTop((int) (offsetTop)); string should be changed to
if (mLayoutMovementEnabled) updateContentOffsetTop((int) (offsetTop));
The complete modified routine is below.
#Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
boolean handled = false;
switch (action) {
case MotionEvent.ACTION_DOWN:
mCurrPercentage = 0;
mDownEvent = MotionEvent.obtain(event);
mPrevY = mDownEvent.getY();
break;
case MotionEvent.ACTION_MOVE:
if (mDownEvent != null && !mReturningToStart) {
final float eventY = event.getY();
float yDiff = eventY - mDownEvent.getY();
if (yDiff > mTouchSlop) {
// User velocity passed min velocity; trigger a refresh
if (yDiff > mDistanceToTriggerSync) {
// User movement passed distance; trigger a refresh
startRefresh();
handled = true;
break;
} else {
// Just track the user's movement
setTriggerPercentage(
mAccelerateInterpolator.getInterpolation(
yDiff / mDistanceToTriggerSync));
float offsetTop = yDiff;
if (mPrevY > eventY) {
offsetTop = yDiff - mTouchSlop;
}
if (mLayoutMovementEnabled) updateContentOffsetTop((int) (offsetTop));
if (mPrevY > eventY && (mTarget.getTop() < mTouchSlop)) {
// If the user puts the view back at the top, we
// don't need to. This shouldn't be considered
// cancelling the gesture as the user can restart from the top.
removeCallbacks(mCancel);
} else {
updatePositionTimeout();
}
mPrevY = event.getY();
handled = true;
}
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mDownEvent != null) {
mDownEvent.recycle();
mDownEvent = null;
}
break;
}
return handled;
}
One simple way to disable the swipe is to set the distance to trigger sync to some value too high to be reached.
mSwipeLayout.setDistanceToTriggerSync(999999);
I'm developing a game with 2 functions one is called singleClick() and the other
moveClick(float dx, float dy)
singleClick() is called from MotionEvent.ACTION_UP
moveClick() is called from MotionEvent.ACTION_MOVE (where dx = x- previousX)
My problem: If I try a move action with my finger, the single click is also called. The opposite is also the same, when I single click something with my finger, the moveClick() is called.
I can handle the moveClick() being called at single click but the singleClick() calls when player is moving really screws up my game controls.
How can I know which is which??
I'm thinking this may have something todo with consuming the touch events. In your onclick listener keep in mind that if you return true that signals you have consumed the event, if you return false that event will continue to fall through and trigger the next listener.
Well if you don't want to call singleClick() when the player is moving where he touches, then you can't call singleClick from ACTION_UP, because ACTION_UP gets called each time. Maybe add a flag that keeps track of whether its a move event. If the total euclidean distance, sqrt(dx^2 + dy^2) > threshold, then change the flag isMove to true. If the total distance is less than that threshold, then call singleClick() in your ACTION_UP.
You might also want to think about timing how long the touch event is, or the total absolute distance (i.e. if someone draws a circle and comes back to the first point, my previous method would register as a single click). Think about how you want the flag to work.
I was hoping for an elegant solution, maybe something built-in with android development.. but I guess there is none so I took Benoir's advice and wrote this very not-elegant code (which seems to work well enough)
long millisLastTouch=0, millisDif,millisStartTouch=0;
final long minMillisSm = 200, minMillisBig = 300;
boolean isMove = true;
public void run(){//my main thread/loop
.....
millisDif = System.currentTimeMillis() - millisLastTouch;
if (!isMove && millisDif>minMillisBig) {
G.currentScreen.singleClick(mPreviousX,mPreviousY);
isMove = true;//to prevent multiple single clicks
}
.....
}//end of main loop
public boolean onTouchEvent(MotionEvent e) {
float x = e.getX(); float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN: { }//do nothing
case MotionEvent.ACTION_UP:{
mPreviousX = x; mPreviousY = y; }
case MotionEvent.ACTION_MOVE:{
boolean newAction;
millisDif = System.currentTimeMillis() - millisLastTouch;
if (millisDif> minMillisSm) {
newAction = true;
millisStartTouch = System.currentTimeMillis();
}
else newAction = false;
millisLastTouch = System.currentTimeMillis();
millisDif = System.currentTimeMillis() - millisStartTouch;
if (newAction == false && millisDif > minMillisSm){
float dx = x - mPreviousX; float dy = y - mPreviousY;
G.currentScreen.clickMove(x,y,dx,dy);
isMove = true;
}
else isMove = false;
mPreviousX = x; mPreviousY = y;
}
}//switch
return true;
}//onTouchEvent
It's time-based only, not distance. If the user touches screen for enough time it will translate as a move action. Otherwise, it remembers the x and y position and will call singleClick() from the main thread
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.
Desired effect
I have a bunch of small images that I'd like to show on a "wall" and then let the user fling this wall in any direction and select an image.
Initial Idea
as a possible implementation I was thinking a GridView that is larger than the screen can show - but all examples of using this widget indicate that the Gallery doesn't extend beyond the size of the screen.
Question
What is the best widget to use to implement the desired effect ?
A code sample would be especially beneficial.
EDIT...
if someone has example code that will let me put about 30 images on a "wall" (table would be good) then I will accept that as the answer. Note that the "wall" should look like it extends beyond the edges of the display and allow a user to use the finger to drag the "wall" up down left right. Dragging should be in "free-form" mode. A single tap on an image selects it and a callback shall be detectable. I have created a bounty for this solution.
The solution is actually quite simple, but a pain. I had to implement exactly this within a program I recently did. There are a few tricksy things you have to get around though. It must be noted that different OS versions have different experiences. Certain expertise or cludging around is required to make this work as drawing is sometimes adversely affected by this method. While I have a working copy, the code I provide is not for everyone, and is subject to whatever other changes or customizations have been made in your code.
set android:layout_width to wrap_content
set android:layout_height to wrap_content
In your code:
determine how many rows you will have.
divide number of items by the number of rows.
add gridView.setStretchMode(NO_STRETCH);
add gridView.setNumColumns( number of Columns );
add gridView.setColumnWidth( an explicit width in pixels );
To Scroll:
You may simply use gridView.scrollBy() in your onTouchEvent()
These are the steps. All are required in order to get it to work. The key is the NO_STRETCH with an explicit number of columns at a specific width. Further details can be provided, if you need clarification. You may even use a VelocityTracker or onFling to handle flinging. I have snappingToPage enabled in mine. Using this solution, there is not even a requirement to override onDraw() or onLayout(). Those three lines of code get rid of the need for a WebView or any other embedding or nesting.
Bi-directional scrolling is also quite easy to implement. Here is a simple code solution:
First, in your class begin tracking X and Y positions by making two class members. Also add State tracking;
// Holds the last position of the touch event (including movement)
int myLastX;
int myLastY;
// Tracks the state of the Touch handling
final static private int TOUCH_STATE_REST = 0;
final static private int TOUCH_STATE_SCROLLING = 1;
int myState = TOUCH_STATE_REST;
Second, make sure to check for Scrolling, that way you can still click or longclick the images themselves.
#Override public boolean onInterceptTouchEvent(final MotionEvent ev)
{//User is already scrolling something. If we haven't interrupted this already,
// then something else is handling its own scrolling and we should let this be.
// Once we return TRUE, this event no longer fires and instead all goes to
// onTouch() until the next TouchEvent begins (often beginning with ACTION_DOWN).
if ((ev.getAction() == MotionEvent.ACTION_MOVE)
&& (myState != TOUCH_STATE_REST))
return false;
// Grab the X and Y positions of the MotionEvent
final float _x = ev.getX();
final float _y = ev.getY();
switch (ev.getAction())
{ case MotionEvent.ACTION_MOVE:
final int _diffX = (int) Math.abs(_x - myLastX);
final int _diffY = (int) Math.abs(_y - myLastY);
final boolean xMoved = _diffX > 0;
final boolean yMoved = _diffY > 0;
if (xMoved || yMoved)
myState = TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_DOWN:
// Remember location of down touch
myLastX = _x;
myLastY = _y;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (myState == TOUCH_STATE_SCROLLING)
// Release the drag
myState = TOUCH_STATE_REST;
}
//If we are not At Rest, start handling in our own onTouch()
return myState != TOUCH_STATE_REST;
}
After the GridView knows that you are Scrolling, it will send all Touch Events to onTouch. Do your Scrolling here.
#Override public boolean onTouchEvent(final MotionEvent ev)
{
final int action = ev.getAction();
final float x = ev.getX();
final float y = ev.getY();
final View child;
switch (action)
{
case MotionEvent.ACTION_DOWN:
//Supplemental code, if needed
break;
case MotionEvent.ACTION_MOVE:
// This handles Scrolling only.
if (myState == TOUCH_STATE_SCROLLING)
{
// Scroll to follow the motion event
// This will update the vars as long as your finger is down.
final int deltaX = (int) (myLastX - x);
final int deltaY = (int) (myLastY - y);
myLastX = x;
myLastY = y;
scrollBy(deltaX, deltaY);
}
break;
case MotionEvent.ACTION_UP:
// If Scrolling, stop the scroll so we can scroll later.
if (myState == TOUCH_STATE_SCROLLING)
myState = TOUCH_STATE_REST;
break
case MotionEvent.ACTION_CANCEL:
// This is just a failsafe. I don't even know how to cancel a touch event
myState = TOUCH_STATE_REST;
}
return true;
}
If course, this solution moves at X and Y at the same time. If you want to move just one direction at a time, you can differentiate easily by checking the greater of the X and Y differences. (i.e. Math.abs(deltaX) > Math.abs(deltaY)) Below is a partial sample for one directional scrolling, but can switch between X or Y direction.
3b. Change this in your OnTouch() if you want to handle one direction at a time:
case MotionEvent.ACTION_MOVE:
if (myState == TOUCH_STATE_SCROLLING)
{
//This will update the vars as long as your finger is down.
final int deltaX = (int) (myLastX - x);
final int deltaY = (int) (myLastY - y);
myLastX = x;
myLastY = y;
// Check which direction is the bigger of the two
if (Math.abs(deltaX) > Math.abs(deltaY))
scrollBy(deltaX, 0);
else if (Math.abs(deltaY) > Math.abs(deltaX))
scrollBy(0, deltaY);
// We do nothing if they are equal.
}
break;
FuzzicalLogic
A GridView can have content that extends beyond the size of the screen, but only in the vertical direction.
Question: What is the best widget to use to implement the desired effect ?
The are no default widgets (at least prior to 3.0) which implement 2D scrolling. All of them implement 1D (scrolling in one direction) only. Sadly the solution to your problem is not that easy.
You could:
Place a TableLayout inside a custom ScrollView class, and handle the touch events yourself. This would allow you to do proper 2D panning, but would not be so good for large data-sets since there would be no view recycling
Modify GridView if possible to enable horizontal scrolling functionality (probably the best solution if you can do it)
Try putting a TableLayout inside a ScrollView (with layout_width="wrap_content) which you put inside a HorizontalScrollView - though this may work it wouldn't be the optimum solution
Do something with OpenGL and draw straight to a canvas - this is how the default Gallery 3D app does it's "wall of pictures"
I think that the only Android native solution for this is to have an embebed WebView in which you can have that bi-dimensional scroll (and by the way is the only thing that has this capability).
Be pressing an image you can control the behavior by a callback from JavaScript to JAva.
This is not very "pretty" but... it's the only viable way to do what you want "out of the box".
I have implemented a GridView that overrides the onInterceptTouchEvent methods. I set the OnTouchListeneras explained by Fuzzical Logic. My issue is that setting a custom OnTouchListener, the objects of my gridView are not clickable anymore. Is that because the default OnTouchListener of a GridView calls onItemClickListener ?
I would like to have a 2 directions scrollable GridView but that has still cliackable objects. Should I implement my own method to check whether a touch matches a item or is there a easier way to achieve this ?
Grodak