scrolling within grid view - android

I have a set of images. I am displaying it on the screen as a grid.
So based upon selecting each image i want to do actions.
That I have done. But one more requirement is that when we move our hand through these images then also I have to perform the same actions. That is, I will have to track on which image I have touched right now and perform the actions. How will I implement it? Does anyone have any idea? Please respond..

Try onTouch() event From View.OnTouchListener. This is called when the user performs an action qualified as a touch event, including a press, a release, or any movement gesture on the screen (within the bounds of the item).
Hope this helps.

You can set listeners to your images, i.e.
imgView.setOnTouchListener(...)
imgView.setOnFocusChangeListener(...)
or
imgView.setOnClickListener()
and then perform the action in these listeners.
If you use setOnFocusChangeListener, then you should be able to handle all cases regardless in which way you selected the image, via touch or trackball.

I got it done.
In the below code colorGV is my grid name.
Add listener to it.
colorGV.setOnTouchListener(new OnTouchClick(context));
And define onTouchClick as:
private class OnTouchClick implements OnTouchListener {
Context context;
OnTouchClick(Context context) {
this.context = context;
}
public boolean onTouch(View v, MotionEvent event) {
try {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
int x = (int) event.getX();
int y = (int) event.getY();
int position = -1;
for (int i = 0; i < colorGV.getChildCount(); i++) {
Rect ButtonRect = new Rect();
colorGV.getChildAt(i).getHitRect(ButtonRect);
if (ButtonRect.contains(x, y)) {
position = i;
break;
}
}
if (position >= 0 && prevPos != position) {
System.out.println("Position changed :" + position);
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}

Related

Android Drag and Drop, Click, and Scroll in a Listview

So I have a layout as such:
I am using a Listview right now and each row has an Imageview and 2 Textviews.
I want to be able to do 3 things on this page:
Clicking on a row (or just clicking on the Imageview in that row works for me too) brings me to another Fragment.
You can drag the image in each Listview row. It uses DragShadowBuilder and therefore can detect if you drop it into that darker gray zone in the bottom.
You can scroll up and down in the Listview to get to the other items that are overflowing right now.
As you can imagine, these three cases are difficult to capture because its hard to differentiate between the three (bc of overlaps in functionality).
I would prefer not to use onItemLongClickListener to do the drag and drop bc users usually don't think to hold their fingers down a long time to start drag and drop.
Any suggestions on how to implement this to capture all three use cases? Actually, it can be thought of as 2 use cases because if I dropped an image back into its original container, it could count as a click for me. The most complex part is to make that somehow work with scrolling up and down the Listview...
Thanks for your help in advance!
p.s. This entire view is rendered in a Fragment and clicking on a view or successfully dropping one into the gray area opens up a separate fragment.
/**
* Call this from a drag source view.
*/
#SuppressLint("NewApi")
public boolean onTouchEvent(MotionEvent ev) {
if (!mDragging) {
return false;
}
final int action = ev.getAction();
final int screenX = clamp((int)ev.getRawX(), 0, mDisplayMetrics.widthPixels);
final int screenY = clamp((int)ev.getRawY(), 0, mDisplayMetrics.heightPixels);
switch (action) {
case MotionEvent.ACTION_DOWN:
// Remember where the motion event started
mMotionDownX = screenX;
mMotionDownY = screenY;
break;
case MotionEvent.ACTION_MOVE:
//Set background color of remove comment box layout
if(((int)ev.getY() <= 50))
ImageEditingActivityNew.rl_remove.setBackgroundColor(Color.RED);
else
ImageEditingActivityNew.rl_remove.setBackgroundColor(Color.TRANSPARENT);
// Update the drag view. Don't use the clamped pos here so the dragging looks
// like it goes off screen a little, intead of bumping up against the edge.
mDragView.move((int)ev.getRawX(), (int)ev.getRawY());
// Drop on someone?
final int[] coordinates = mCoordinatesTemp;
DropTarget dropTarget = findDropTarget(screenX, screenY, coordinates);
if (dropTarget != null) {
if (mLastDropTarget == dropTarget) {
dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
} else {
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
}
dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1],
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
}
} else {
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
}
}
mLastDropTarget = dropTarget;
/* The original Launcher activity supports a delete region and scrolling.
It is not needed in this example.
// Scroll, maybe, but not if we're in the delete region.
boolean inDeleteRegion = false;
if (mDeleteRegion != null) {
inDeleteRegion = mDeleteRegion.contains(screenX, screenY);
}
//Log.d(TAG, "inDeleteRegion=" + inDeleteRegion + " screenX=" + screenX
// + " mScrollZone=" + mScrollZone);
if (!inDeleteRegion && screenX < mScrollZone) {
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
mScrollRunnable.setDirection(SCROLL_LEFT);
mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
}
} else if (!inDeleteRegion && screenX > scrollView.getWidth() - mScrollZone) {
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
mScrollRunnable.setDirection(SCROLL_RIGHT);
mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
}
} else {
if (mScrollState == SCROLL_WAITING_IN_ZONE) {
mScrollState = SCROLL_OUTSIDE_ZONE;
mScrollRunnable.setDirection(SCROLL_RIGHT);
mHandler.removeCallbacks(mScrollRunnable);
}
}
*/
break;
case MotionEvent.ACTION_UP:
//When touch up then remove comment box
if(((int)ev.getY() <= 50)) {
ImageEditingActivity.rl_imageEdit_comment.removeAllViews();
ImageEditingActivity.rl_imageEdit_comment.invalidate();
//ImageEditingActivity.mDragLayer.removeView(ImageEditingActivity.rl_imageEdit_comment);
ImageEditingActivity.addComment = true;
ImageEditingActivity.changeIconBackground("Remove");
ImageEditingActivity.editParam.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
}
if (mDragging) {
drop(screenX, screenY);
}
endDrag();
break;
case MotionEvent.ACTION_CANCEL:
cancelDrag();
}
return true;
}
Instead of long press and hold you can use fling/swipe gesture. You can override onFling method of SimpleonGestureListner and detect fling based on the threshold.
Your requirements conflicts. I would do the next thing in your case :
Your row can be drag only with long pressed (then I would use the shadow drag)
also I would do that the view of the drop only appears when long press occurs.
override the onclick method of the ImageView that way you can click it and then call your fragment to expand
Now because you didn't override any onTouchListeners or onClick on the row you can scroll freely as long as you don't click a picture.

Get Item from ListView only with OnTouchListener

In my application I have a Touchlistener for my ListView. With the TouchListener I'm able to get the X and Y coordinates from the touch event. Now I want to get the clicked item from the ListView. How can I get the position of the click item in the listView only with an onTouchListener? I do not want to use a onItemClickedListener.
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (mPaused) {
return false;
}
// TODO: ensure this is a finger, and set a flag
// Find the child view that was touched (perform a hit test)
Rect rect = new Rect();
int childCount = mListView.getChildCount();
int[] listViewCoords = new int[2];
mListView.getLocationOnScreen(listViewCoords);
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = mListView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child; // This is your down view
break;
}
}
if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownPosition = mListView.getPositionForView(mDownView);
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
}
view.onTouchEvent(motionEvent);
return true;
}
i am get it from SwipeListView Jake Wharton
the best way it to use this methods pointToPosition(int x, int y) and pointToRowId(int x, int y)
If it is not mandatory that you use a TouchListener, I'd highly recommend using an OnItemClickListener. It will simplify your life greatly.
Other than that, you'll have to get the current offset and scroll position of the ListView, the height of each row and then do some math to determine whether the point (x,y) lies in the polygon of each row. Since the rows are rectangles that math isn't too difficult, but using OnItemClickListener would be much, much easier.
Additionally, if you do need to use TouchListener, you could also use OnItemClickListener to set a value on your Adapter class (or somewhere else) indicating the most recent item clicked. Then, in your TouchListener you could check that value and use that to correlate the (x,y) coordinate and ListView item. This, of course, depends on the OnItemClickListener being run first. If the inverse is true, you could make it work but instead store the touch position in using the TouchListener and then correlate it in the OnItemClickListener.
You class can implement both View.OnTouchListener, AdapterView.OnItemClickListener
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if(motionEvent.getAction() == MotionEvent.ACTION_UP){
Log.d(TAG, "ontouch: UP");
**// Here you can figure if it was simple item click event.
// We return false only when user touched once on the view.
// this will be handled by onItemClick listener.**
if(lastAction == -1){
lastAction = MotionEvent.ACTION_UP;
view.clearFocus();
return true;
}
return false;
}
else if(motionEvent.getAction() == MotionEvent.ACTION_DOWN){
Log.d(TAG, "ontouch: DOWN");
return false;
}
else {
// This is all action events.
lastAction = -1;
return true;
}
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// We come here after onTouch event figured out that its a simple touch event and needs to be handled here.
}

Simultaneous GridView button events/determining which button has been touched

I'm having a hard time trying to figure this out. I have a gridview of 8 buttons. At the moment I'm using an onItemClickListener to trigger the buttons actions, however this produces two problems for me.
1) The buttons action happens after the button has been unpressed.
2) Two buttons cannot the pressed at the same time, you must release the first button.
As I have learnt, an onTouchListener should resolve my first issue, though I'm not sure how to determine which button has been pressed. My code for the onItemClickListener is as follows
gridview.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
Toast.makeText(Activity.this, "" + position, Toast.LENGTH_SHORT).show();
}
});
Now with the above, I know exactly which button has been pushed. I believe the code for implementing as an onTouchListener is as follows
gridview.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return false;
}
}) {
How am I supposed to determine which button has been pressed using MotionEvent? Before I was getting passed 'position' and it made this fairly easy. I also need to account for if two or more buttons have been pushed simultaneously/without letting another one go.
Does anyone know how to achieve this?
Having hit this very issue recently and coming across this post in my quest for help, I wanted to add two things from what I did which seem to have worked:
1) I added the onTouchListener to the object in the adapter rather than the activity or gridview.
2) In the OnTouchListener, I looked for MotionEvent.ACTION_DOWN (first finger touch) and MotionEvent.ACTION_POINTER_DOWN (subsequent finger touches), this way I can get multitouches and process them immediately without waiting for the user to lift their finger(s).
Note that I called it ImageAdapter, even though I've added a TextView to each as that way I can use the TextView background for the image, but add invisible text to the TextView so it works with Talkback):
public class ImageAdapter extends BaseAdapter {
private Context mContext;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return numCols * numRows;
}
public Object getItem(int position) {
return this;
}
public long getItemId(int position) {
return position;
}
// create a new TextView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView;
if (convertView == null) { // if it's not recycled, initialize some attributes
textView = new TextView(mContext);
} else {
textView = (TextView) convertView;
}
// place any other initial setup needed for the TextView here
// here's our onTouchListener
textView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
boolean returnValue;
int thePosition = v.getId();
// MotionEvent.ACTION_DOWN gets the first touch
// MotionEvent.ACTION_POINTER_DOWN gets any subsequent touches (if you place a second finger on the screen)
// Between these I can get touches as soon as they happen, including multitouch support, without needing to wait until the user lifts their finger.
if ((event.getAction() == MotionEvent.ACTION_DOWN) || (event.getAction() == MotionEvent.ACTION_POINTER_DOWN)) {
TextView textView;
if (v == null) { // if it's not recycled, initialize some attributes
textView = new TextView(mContext);
} else {
textView = (TextView) v;
}
// Do any processing based on the touch - I call a function and pass the position (number of cell, 1..n) and textview so can make changes to it as needed
ScreenTapped(thePosition, textView);
// I used a returnValue
returnValue = true;
} else returnValue = false;
return returnValue;
});
return textView;
} // getView
} //imageadapter
I am infact trying to figure the same thing out. I got as far as figuring out which gridcell has been clicked using the following code
public boolean onTouch(View v, MotionEvent me) {
float currentXPosition = me.getX();
float currentYPosition = me.getY();
int position = gridView.pointToPosition((int) currentXPosition, (int) currentYPosition);
Position gives you the number on the gridView, and you can supposedly retrieve that particular item as following
gridView.getItemAtPosition(position)
But that is where I am stuck. My gridView has Textview items in it, and I am having trouble converting the item to a textview and then performing operations on it.
Hope this helps!
When using gridView the philosophy is:
the grid view implements the onTouchLister
when touch happens onTouchLister gathers the coordinates (a lot :) )
for all ACTION_MOVE events
when the touch event is MOVE_UP, calculate the real positions under
the coordinates and return the item in the grid
So the solution would be:
In your activity where you have findViewById(some_grid_view)
//Register handler for the onTouch event of gridView in your activity
gridView.setOnTouchListener(new MyActivityOnTouchListener(this));
NOTE: my onTouch listener is implemented in another class (MyActivityOnTouchListener) instead of inside the activity
...then in the MyActivityOnTouchListener class you implement the onTouch method:
public class CalendarActivityOnTouchListener implements View.OnTouchListener {
private MyActivity myActivityContext;
private GridView mGridView;
private HashSet<Point> movementCoordinates = new HashSet<Point>;
//Constructor
public MyActivityOnTouchListener (MyActivity context){
this.myActivityContext= context;
mGridView= myActivityContext.getGridView(); //assign touched gridView into a local variable
}
#Override
public boolean onTouch(View v, MotionEvent event) {
/*
* NOTE:
* ACTION_MOVE fires events until you release it
* ACTION_UP once you release it fires it
*/
//while touching the grid a bunch of ACTION_MOVE events are dispatched
if (event.getAction() == MotionEvent.ACTION_MOVE) {
//gather all coordinates touched (in a set to avoid duplicates)
movementCoordinates.add(new Point((int)event.getX(), (int)event.getY()));
return true;
}
//Finally the finger is lifted
if (event.getAction() == MotionEvent.ACTION_UP) {
//convert all movementCoordinates gathered in the previous block into real grid positions
int position;
for(Point p : movementCoordinates){
Log.d("Luka", p.x +" / "+p.y);
position = calendarGridView.pointToPosition(p.x, p.y);
//...Do whatever with the position
}
}
}
}
Be careful about the pointToPosition() method because in some cases it can return -1 instead of the position behind the coordinates. For example, if you have a margin between items in the grid those coordinates cannot return a position, hence the -1
hope it helps...

Capturing touch events in an android list item and prevent scrolling

I'm trying a wild idea here by putting a custom control within the items of a certain list view. The control is only "activated" if the user touches down on a certain trigger point and then they can "drag around."
My question is, what can I do in onTouchEvent(...) to prevent the listview from receiving the event and scrolling. Right now I can touch and get ahold of the control, but if I move my finger too much up or down the listview takes over and starts scrolling, then my view doesn't even receive a ACTION_UP event.
Here is my current onTouchEvent code:
if (e.getAction() == MotionEvent.ACTION_DOWN) {
Log.d("SwipeView", "onTouchEvent - ACTION_DOWN" + e.getX() + " " + e.getY());
int midX = (int)(this.getWidth() / 2);
int midY = (int)(this.getHeight() / 2);
if (Math.abs(e.getX() - midX) < 100 &&
Math.abs(e.getY() - midY) < 100) {
Log.d("SwipeView", "HEY");
setDragActive(true);
}
this.invalidate();
return true;
} else if (e.getAction() == MotionEvent.ACTION_MOVE) {
_current[0] = e.getX();
_current[1] = e.getY();
this.invalidate();
return true;
} else if (e.getAction() == MotionEvent.ACTION_UP) {
_current[0] = 0;
_current[1] = 0;
setDragActive(false);
this.invalidate();
return true;
}
I'm sure it has something to do with the event hierarchy in some fashion.
This might not be exactly what you're looking for, but it's possible to implement capture capabilities in your activity. add
private View capturedView;
public void setCapturedView(View view) { this.capturedView = view); }
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
return (this.capturedView != null) ?
this.capturedView.dispatchTouchEvent(event) :
super.dispatchTouchEvent(event);
}
to your activity, then simply pass your view on ACTION_DOWNand null on ACTION_UP. it's not exactly pretty, but it works. i'm sure there's a proper way to do this though.
finally learned the correct way to do this: requestDisallowInterceptTouchEvent

Is it possible to pass an onTouchEvent to multiple views in Android?

I have read a few questions regarding this topic on SO but haven't really found a solid answer to it.
I have a framelayout that I stack multiple custom views on, however the onTouch event only works with the top view. (the custom views are all the same view with the same onTouch event, just multiple of them)
FrameLayout
customView[2] <--- this is the last view added and the only one that receives the event
customView[1]
customView[0]
I'm testing it on Android 2.2 and am wondering if there is any way for the other views below to know where the touch happened?
EDIT (Adding some code)
I'm adding some code to hopefully help explain where I'm running into issues. At first I just automatically had the onTouchEvent return true. This made it so that the last view (in my case customerView[2]) would be the only one generating a value.
However, once I added the method to set the onTouchEvent to return true or false, now the only view returning a generated value is customView[0].
I hope this clears up what I am asking. I'm rather new to this and I appreciate you taking the time to explain it (and of course I appreciate your patience).
Also, I realize that my TextView's don't update with the value on each touchEvent, I'm working on fixing that.
My Activity:
public class MyActivity extend Activity {
CustomView[] customView;
TextView[] textView;
int numViews 3;
//FrameLayout and Params created
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
for(int i = 0; i < numViews; i++) {
customView[i] = new CustomView(this, i);
//Allows the onTouch to be handled by all Views - View[0] is the bottom view
if(i == 0) {
customView[i].setTouchBool(true); //set view's onTouch to return true
} else {
customView[i].setTouchBool(false); //set view's onTouch to return false
}
//Set TextView to display the number generated by the CustomView
textView[i].setText(Double.toString(customView[i].getGeneratedNumber()));
//Add views to main layout
frame.addView(textView[i]);
frame.addView(customView[i]);
}
}
}
My View:
public class CustomView extends View {
boolean onTouchHandler = true;
int xVal = 0, yVal = 0;
int index;
double generatedNum = 0;
public CustomView(Context context) {
this(context, 0);
this.index = 0;
}
public CustomView(Context context, int index) {
super(context);
this.index = index;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch(action) {
case MotionEvent.ACTION_DOWN: {
//do logic
}
case MotionEvent.ACTION_MOVE: {
//do logic
}
case MotionEvent.ACTION_UP: {
xVal = (int) ev.getX();
yVal = (int) ev.getY();
generateNumber(xVal, yVal, index);
break;
}
}
return onTouchHandler;
}
private void generateNumber(int x, int y, int index) {
if(index == 0) {
generatedNum = (x / 2) * (y / 2) + 64;
} else {
generatedNum = (x / 2) * (y / 2) + (index * 128);
}
}
public double getGeneratedNumber() {
return generatedNum;
}
public boolean setTouchBool(boolean b) {
this.onTouchHandler = b;
}
}
Android will cascade down the views calling onTouchEvent on each one until it receives a true from one of them. If you want a touch event to be handled by all of them, then return false until it reaches the last one.
EDIT:
Ok. If I understand correctly, you have a single top view containing a bunch of child views one layer deep. My original answer was assuming that you had three custom views that were on top of each other in the ViewGroup's hierarchy (View3 is a child of View2. View2 is a child of View1. View1 is a child of ParentView). You want the user's touch event on the parent view to get sent to all of it's children.
If that's the case, AFAIK, there is no view in Android's API that allows that. So, you'll have to make a custom view that does it.
OK, I haven't tested this, so please tell me if it works and if it's what you're trying. Create a custom class that extends whatever object frame is, then override the onTouch method like so.
#Override
public boolean onTouchEvent(MotionEvent ev) {
for(int i = 0; i < this.getChildCount(); i++){
this.getChildAt(i).dispatchTouchEvent(ev);
}
return true;
}
Now, keep the same logic that your custom views have, except they should all return false because your parent view will not receive the onTouch event unless they do as stated in my previous answer
note: with this implementation, the child view that the user actually touches will fire twice because the logic will go
fire child touch event -> return false -> fire parent touch event -> fire child touch event again
I know this question is very old, but I had the same problem and solved it by creating my own Layout to determine which child is actually touched.
I therefore iterate over the children of my custom layout and check if the user actually clicked on the view. The collision detection is handled in the custom view's onTouch() method. (Collision detection is done by intersecting a Region() with the event's x,y coordinates. For me this was convennient because I drew the custom view with a Path())
Here is a kotlin code snippet from my custom layout for better understanding:
class CustomLayout(context: Context, attrs: AttributeSet) :
RelativeLayout(context, attrs){
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
if(ev.action != MotionEvent.ACTION_UP){
return true
}
//Iterate over child view and search for the right child that should handle this touch event
for (i in childCount - 1 downTo 0) {
val child = getChildAt(i)
if (!viewTouched(child, ev)) {
continue
}
//Do something
Timber.d("Touched view: ${child.id}")
}
return true
}
private fun viewTouched(child: View, ev: MotionEvent) : Boolean {
child as OnTouchListener
//onTouch() does the collision detection
return child.onTouch(child, ev)
}

Categories

Resources