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...
Related
I have an Activity, inside is ViewPager and inside is ListFragment with ListView items. I want define touch gesture for the items (drag, fling etc.).
I'm able to attach onTouchListener() to each ListView item View by overriding getView() of adapter.
Adapter adapter = new Adapter(blah, blah, blah) {
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View itemView = super.getView(position, convertView, parent);
itemView.setOnTouchListener(new View.OnTouchListener() {
blah, blah, blah
});
return itemView;
}
};
But I only receive MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE and MotionEvent.ACTION_UP if it occurs inside triggering item View boundary. For example I'm not able to catch MotionEvent.ACTION_OUTSIDE.
My guess is, that my custom onTouchListener compete with listener of ViewPager and of Activity. I want to trigger these other listeners in some occasions, i.e. if user move to side, I want to slide the whole View inside ViewPager but if he push on the ListView item and move vertically, I want startDrag() of the item.
How to implement that?
Edit/ Currently, my custom listener on ListView items works along with ViewPager, which is great. I'm able to catch ListView item events as long as I don't move outside its View and I'm able to slide the whole View in ViewPager as well.
Edit/ I've rewrote my ListView into RecycledView and realized that if I attach listener to item View, the ViewPager catches almost all move events, while allowing only the click events to go through. I've also realized, that if I attach OnTouchListener to the RecycledList it is able to catch move events along with the ViewPager so it depends on which level I attach the Listener. Problem is, that in item View level I have available full reference to item, while when working with the list, I need to guess item position from event coordinates and then gather data from Adapter and RecycledView, which is extra work.
Edit/ it wasn't that hard after all. Luckily, RecycledView has findChildViewUnder(int xPos, int yPos) method, so getting the View was piece of cake.
Here's my implementation, if someone is interested
mRecycledView.setOnTouchListener(new View.OnTouchListener() {
int LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
float mItemViewHeight, mInitialY;
boolean mIsResolved;
final Handler mHandler = new Handler(Looper.getMainLooper());
final Runnable mLongPress = new Runnable() {
#Override
public void run() {
mIsResolved = true;
onLongClick();
}
};
#Override
public boolean onTouch(View v, MotionEvent me) {
switch (MotionEventCompat.getActionMasked(me)) {
case MotionEvent.ACTION_DOWN:
mInitialY = me.getY();
mActiveUserView = mRecycledView.findChildViewUnder(me.getX(), mInitialY);
if (mActiveUserView == null) { // clicked to RecycledView where's no item
mIsResolved = true;
} else {
mIsResolved = false;
mItemViewHeight = (float) mActiveUserView.getHeight();
mHandler.postDelayed(mLongPress, LONG_PRESS_TIMEOUT);
}
break;
case MotionEvent.ACTION_MOVE:
if (!mIsResolved) {
stopLongClickHandler();
// check for vertical upward move
if (mInitialY - me.getY() > mItemViewHeight) {
mIsResolved = true;
onVerticalDrag();
}
}
break;
case MotionEvent.ACTION_UP:
if (!mIsResolved) {
stopLongClickHandler();
mIsResolved = true;
onClick();
}
}
return !mIsResolved;
}
void stopLongClickHandler() {
mHandler.removeCallbacksAndMessages(null);
}
void onVerticalDrag() {
Log.e("DEBUG", "start drag");
}
void onLongClick() {
Log.e("DEBUG", "long click");
}
void onClick() {
Log.e("DEBUG", "short click");
}
});
I can suggest you replacing ListView with RecyclerView. Then read this tutorial: it contains almost every drag/swipe/touch implementations on RecyclerView clearly explained.
When i use an onTouchListener in the getView of my adapter the line
android:listSelector="#drawable/circle"
immediately stops working, if I set onTouch to return false it works again, however then the ACTION_DOWN ACTION_UP dosent work properly.
Heres what i have in onTouch
image.setOnTouchListener(new View.OnTouchListener() {
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Assets.playMusic(songID, false);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
Assets.mediaPlayer.stop();
Assets.mediaPlayer = null;
}
return true;
}
});
Its suppose to play music for as long as you have a finger held on the item and when you release it should stop the music. And it works well when returning true. However for some reason the circle stops appearing behind the tapped items. If it is set to false the circle appears, but then action_up dosent stop the music
ive tried using .setSelected .setActivated .setEnabled and none of them work
please help
Also i want it to work kinda like snapchats camera button, tap it and it does one thing, hold it and it does something for duration of your hold. I was going to use time variables in the Action up and down. but if anyone knows another way to do this id appreciate info about that too
In this situation is not encouraged to attach an OnTouchListener to the image, but to the items of the GridView instead.
You should have something like this:
GridView gridview = (GridView) findViewById(R.id.the_gridview_id);
gridview.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, final View v, int position, long id) {
doSomething();
}
});
EDIT:
In order to know how much time a view is pressed, you can do something like this:
// this goes somewhere in your class:
long lastDown;
long lastDuration;
...
// this goes wherever you setup your button listener:
gridview.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
lastDown = System.currentTimeMillis();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
lastDuration = System.currentTimeMillis() - lastDown;
}
}
};
I want to remove a gridView item's shadow when the user presses it, and then restore it once the user releases. (cant use selector.xml because items have a user chosen color)
this code removes shadow when first pressed but upon release it stays stuck down with no shadow.
gridItemView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN) {
image.removeShadow();
image.invalidate();
}
else if(event.getAction()==MotionEvent.ACTION_UP){
image.setShadow();
image.invalidate();
}
return false;
}
});
I cant set it to true, because then .OnItemClickListener in the fragment dosent work. Also I kinda fixed it by setting the shadow to turn on in onItemClickListener, but if the user slides their thumb off the item instead of just clicking it will stay pressed
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
MainActivity.selectedItem = position;
if (lastView[0] != null) {
lastView[0].setBackgroundResource(R.drawable.nullr);
}
picker.setOldCenterColor(MainActivity.items.get(MainActivity.selectedItem).getColor());
picker.setColor(MainActivity.items.get(MainActivity.selectedItem).getColor());
View imageContainer = view.findViewById(R.id.imageContainer);
CircularImageView circleImage = (CircularImageView) view.findViewById(R.id.circleView);
artistText.setText(MainActivity.items.get(position).getArtist());
songText.setText(MainActivity.items.get(position).getSong());
int backgroundColor = Color.parseColor("#FFEECC");
GradientDrawable drawable = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[]{backgroundColor, backgroundColor});
drawable.setShape(GradientDrawable.OVAL);
drawable.setStroke(25, MainActivity.items.get(MainActivity.selectedItem).getColor());
imageContainer.setBackgroundDrawable(drawable);
circleImage.setShadow();
circleImage.invalidate();
lastView[0] = imageContainer;
MainActivity.anItemIsSelected = true;
}
});
you forgot
else if(event.getAction()==MotionEvent.ACTION_UP){
image.setShadow();
image.invalidate();
}
what you can do here is try to create a custom Gridview and override onTouchEvent, something like this, (it is not precisely accurate though)
public class MyGridv extends GridView{
//implement all constructors;
//the override onTouchEvent(MotionEvent event);
#override
protected boolean onTouchEvent(MotionEvent event){
// put your ontouch code here and return true, you might need to do
//some changes because you can not get access to your methods or
//you can make this class a private class to your mainActivity
// now delete your ontouch for your gridview
}
}
the logic here is onTouchEvent() is initially called before your onTouchListenerand its the first to be called, returning true there will then pass the event to onTouch() and on onClick, there everything will work fine
hope its helpful
Try to add below on your View.
android:clickable="true"
I have come up with an idea such that on the touch of the two TextViews both become invisible. This idea works when I touch a single finger on the TextView and only one TextView becomes invisible. But when I test it with two finger, only one Textview becomes invisible. It does not make both the textviews invisble.
I have written the following code.
public class MatchMeaning1 extends Activity implements OnTouchListener{
private static final String TAG = MatchMeaning1.class.getSimpleName();
TextView[] txtWord, txtMeaning;
int [] wordID = {R.id.txtWord1, R.id.txtWord2, R.id.txtWord3, R.id.txtWord4, R.id.txtWord5};
int[] meaningID = {R.id.txtMeaning1,R.id.txtMeaning2, R.id.txtMeaning3, R.id.txtMeaning4, R.id.txtMeaning5 };
String[] word = {"1.abidcation","2.abhor","3.abide","4.abyssmal","5.award"};
String[] meaning = {"a.deep","b.stay","c.cede","d.accolade","5.hate"};
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.match_meaning);
txtWord = new TextView[5];
txtMeaning = new TextView[5];
for(int i = 0; i < txtWord.length; i++){
txtWord[i] = (TextView)findViewById(wordID[i]);
txtMeaning[i] = (TextView)findViewById(meaningID[i]);
txtWord[i].setText(word[i]);
txtMeaning[i].setText(meaning[i]);
txtWord[i].setOnTouchListener(this);
txtMeaning[i].setOnTouchListener(this);
}
}
#Override
public boolean onTouch(View v, MotionEvent event) {
int pointerCount = event.getPointerCount();
if(pointerCount == 2){
TextView [] tv = new TextView[2];
for(int i = 0; i < tv.length ;i++){
tv[i] = (TextView)v;
tv[i].setVisibility(TextView.INVISIBLE);
}
}
return true;
}
}
The above code doesn't work for pointerCount = 2, but it works for pointerCount = 1.
Can anyone tell me the solution for this?
The onTouch event is processed on a per view basis so it would be firing for both textviews except you're returning true which swallows the event. What you want is probably closer to:
#Override
public boolean onTouch(View v, MotionEvent event) {
if(v != YOUR_TEXT_VIEW1 && v != YOUR_TEXT_VIEW2) return false;
((TextView)v).setVisibility(TextView.INVISIBLE);
return false;
}
Replace YOUR_TEXT_VIEW 1 & 2 with objects you declare as the current two text views you want to disappear.
EDIT:
If you only want the views to disappear if the user is touching both of them you're going to have to devise a more involved approach. I see two possibilities:
The onTouch event needs to call another function which appends all views triggered by each unique MotionEvent. After appending the view to the list the function checks to see if both expected views are present. If so, trigger the invisibility calls.
Instead of using onTouch use the onTouchEvent method and determine if the getX(index) getY(index) coordinates fall inside the two views you are expecting to be touched.
Your phone does not support multitouchEvent.That's why if you are touching two text view at a time, its taking only one event at a time and one textviewbecame invisible.
so first check whether your phone support more then one touch at time.
I have a listview with clickable buttons in the row views, and a custom SimpleCursorAdapter to implement this list. Despite the onitemclicklistener not being fired when the row is clicked (see here), I have implemented a listener that works when touching the row item:
public View getView(int position, View convertView, ViewGroup parent) {
.................................
convertView.setOnClickListener(new OnItemClickListener(position));
}
public class OnItemClickListener implements OnClickListener{
private int mPosition;
OnItemClickListener(int position){
mPosition = position;
}
public void onClick(View view) {
}
}
There are two problems with this - it takes two touches to fire the onitemclick listener, presumably one to focus and one to fire, and it's impossible to select the row using the trackball.
I have tried some of the workaround listed on SO, inlcuding making the button not focusable, and some other methods here, but didn't get anywhere. As that link points out, Google accomplish it themselves with the call log application. That seems to be achieved with ListActivities though - I am using a Tab Activity with several lists in the same tab.
I managed to sort out both issues in the end with a TouchDelegate. The relevant code I used in my Custom Adapter is below. I used the TouchableDelegate on an ImageView, so I'm pretty sure most other objects would also work. TOUCH_RECT_EXPANSION is just a constant parameter for how much you want the bounding box to be expanded by. Also note that your Custom Adapter must implement View.OnTouchListener.
public View getView(int position, View convertView, ViewGroup parent) {
star = (ImageView) convertView.findViewById(R.id.liststar);
final View parentview = (View) star.getParent();
parentview.post( new Runnable() {
// Post in the parent's message queue to make sure the parent
// lays out its children before we call getHitRect()
public void run() {
final Rect r = new Rect();
star.getHitRect(r);
r.top -= TOUCH_RECT_EXPANSION;
r.bottom += TOUCH_RECT_EXPANSION;
r.left -= TOUCH_RECT_EXPANSION;
r.right += TOUCH_RECT_EXPANSION;
parentview.setTouchDelegate( new TouchDelegate(r,star) {
public boolean onTouchEvent(MotionEvent event) {
return true;
}
});
}
});
star.setOnTouchListener(this);
}
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// do something here
}
return true;
}
}
I also had some issues with the onItemClickListener. In the end these were solved by using a separate custom class that implemented the interface OnItemClickListener, so try that if you are having problems, but it's probably more likely that I was doing something wrong with the in-class onItemClickListener, because I can't see any reason why that should work differently.