Problem Description
In my application I have GridView with 12 items in it. Every item in the GridView has its own icon like it is shown in the image below. For every icon I have two images one pressed and one normal.
Question
I try to find some standard mechanisms to change images from Normal state to Presses state when user press on the item and then release the button, but can't find. Instead of that I use public boolean onTouch(View v, MotionEvent event) method but it brings to some side effects for example when user scroll down or slide screen to change tab, items are clicked.
Source Code
#Override
public boolean onTouch(View v, MotionEvent event) {
/* Get Action on Touch. */
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
float currentXPosition = event.getX();
float currentYPosition = event.getY();
lastPressedPosition = gvCategories.pointToPosition((int) currentXPosition, (int) currentYPosition);
if(lastPressedPosition < 0 || lastPressedPosition > sDefaultArray.length)
return false;
/* Set Pressed Image. */
Drawable drawable = getActivity().getResources().getDrawable(sPressedArray[lastPressedPosition]);
Category category = (Category) gvCategories.getItemAtPosition(lastPressedPosition);
category.setImage(((BitmapDrawable) drawable).getBitmap());
CategoriesGridAdapter adapter = (CategoriesGridAdapter)gvCategories.getAdapter();
adapter.notifyDataSetChanged();
}
else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
/* Get current possition to compare with the last one. */
float currentXPosition = event.getX();
float currentYPosition = event.getY();
int currentPosition = gvCategories.pointToPosition((int) currentXPosition, (int) currentYPosition);
if(currentPosition == -1 && lastPressedPosition == -1)
return false;
if (currentPosition == lastPressedPosition && action == MotionEvent.ACTION_UP) {
Category category = (Category) gvCategories.getItemAtPosition(currentPosition);
Log.i(TAG, String.format("Category Title is: %s", category.getTitle()));
if (category == Category.More) {
//tracker.trackEvent("Category Clicked", "More", "", 0L);
/* Get current selected language. */
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
final String lang = preferences.getString(Preferences.LANGUAGE, "en");
/* Load All categories available in the application. */
updateCategoriesAdapter(lang, true);
}
else {
//tracker.trackEvent("Category Clicked", category.getTitle(), "", 0L);
CategoryDetailsActivity.sFragmentManager = getFragmentManager();
/* Start categories detail activity. */
Intent intent = new Intent(getActivity().getApplicationContext(), CategoryDetailsActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(CategoryDetailsActivity.CATEGORIES_IDS, category.getIDs());
intent.putExtra(CategoryDetailsActivity.CATEGORY_NAME, category.getTitle());
getActivity().getApplicationContext().startActivity(intent);
}
}
if(lastPressedPosition == -1)
return false;
/* Set Pressed Image. */
Drawable drawable = getActivity().getResources().getDrawable(sDefaultArray[lastPressedPosition]);
Category category = (Category) gvCategories.getItemAtPosition(lastPressedPosition);
category.setImage(((BitmapDrawable) drawable).getBitmap());
CategoriesGridAdapter adapter = (CategoriesGridAdapter)gvCategories.getAdapter();
adapter.notifyDataSetChanged();
}
return false;
}
If you have the two images already made, use a Button element in your GridView and set the Background element of your button to a state selector drawable. refer to this link for XML declarations on making your selector drawable example here .
For your scenario you will have to create different selector XML files for each button.
Related
I have a 2 dimensional recyclerview that contains buttons.
when the user clicks on the button it changes the layout color to green and vice versa.
I'm trying to add a feature that allows the user to slide the finger on the buttons row, and make the buttons get pressed.
couldn't find documentation about it.
I don't want to use slider widget because I would like to keep the buttons formation.
any ideas?
thanks
This is my setOnClickListener for the button ( the button is a circular layout):
holder.linearLayout.setOnClickListener(new View.OnClickListener() {
Intent intent = new Intent(mContext,WorkoutInterface.class);
#Override
public void onClick(View v) {
if(isPressed[0] ==false){//check if the button is already pressed
isPressed[0] =true;
actualReps+=1;// add rep count to the actual reps variable
if(workout.pressedButtons==null){//save the number of presses in list
workout.pressedButtons="";
}else {
workout.pressedButtons+=","+String.valueOf(verticalIndex) + String.valueOf(position);
buttonColorCheck(holder, position, verticalIndex);//if button is pressed in the past, keep it green
}
}else{
holder.linearLayout.setBackgroundColor(Color.argb(255,166, 172, 175) );
isPressed[0] =false;
actualReps-=1;
changeButtonStatus(String.valueOf(verticalIndex)+String.valueOf(position));
}
}
});
you have to create it manually
public Rect btn1Rect= new Rect((int) btn1.getX(), (int) btn1.getY(), (int) btn1.getX() + btn1.getWidth(), (int) btn1.getY() + btn1.getHeight()); //bound of button 1
View.OnTouchListener buttonTouch = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getActionMasked();
int x= (int) event.getRawX(),y= (int) event.getRawY();
switch (action) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
if(btn1Rect.contains(x,y)){
Log.d(TAG, "onTouch: your touch contains btn1 bound");
}
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
};
make sure that set touch listener to your buttons and remove click listener from it.
I am making a single screen application of a simple logic circuit. I am using onTouchEvent to handle user interactions. I am using ACTION_UP, ACTION_MOVE, & ACTION_DOWN right now, but this only allows me to use one gesture. I want to be able to selection an option in my UI, such as "AND" gate. I want to use one touch to select what I want to do, then the next touch to place the gate on the screen. Instead, my onTouchEvent only allows me to touch the component I want and I have to keep my finger on the screen to drag it to the location I want to place it. This is not want I want.
I've tried researching how to implement some sort of state variable to allow onTouchEvent to wait for the next touch, but I don't think I correctly understand how to implement it.
#Override
public boolean onTouchEvent(MotionEvent motionEvent) {
Log.d("Debugging", "In onTouchEvent");
if((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
Touch.horizontalTouched = (int)motionEvent.getX()/ grid.getBlockSize();
Touch.verticalTouched = (int)motionEvent.getY()/ grid.getBlockSize();
whatWasTouched = whatWasTouched(Touch.horizontalTouched, Touch.verticalTouched);
}else if((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_MOVE){
//do nothing, finger is moving on screen
}
else if((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP){
Touch.secondHorizontalTouch = (int)motionEvent.getX()/ grid.getBlockSize();
Touch.secondVerticalTouch = (int)motionEvent.getY()/ grid.getBlockSize();
placeComponent();
draw();
}
return true;
}
I expect my first touch on the screen to be able to select an option, ex: "AND", "OR", "NOT", "SWITCH", "EDIT", etc. and then my second touch completes the desired action. I also want to be able to touch a component I placed on the screen and then touch another component so I can wire them together.
By default, event listeners in Android are for waiting - you don't have to provide any delay.
Simply set the onTouchEvent(...) listener on the ImageView and show the first bitmap. When the ImageView is touched, show the next bitmap and so on. All you have to do is keep a count of how many touches there have been in order to know which image to show (image 1, 2, 3, 4 etc).
Example...
public class LoadImage extends Activity {
int imageNumber = 1;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_load_image);
//get an image and create a bitmap from it
ImageView imageView = (ImageView) findViewById(R.id.imageView);
imageView.setImageBitmap(bitmap);
}
#Override
public boolean onTouchEvent(MotionEvent evt) {
if (evt.getAction() == MotionEvent.ACTION_DOWN) {
imageNumber++;
switch (imageNumber) {
case 2:
// show image 2
break;
case 3:
// show image 3
break;
...
}
return true;
}
return false;
}
}
I was able to figure this out by using a static class that will handle the state of my touches.
#Override
public boolean onTouchEvent(MotionEvent motionEvent) {
Log.d("Debugging", "In onTouchEvent");
if (placeState.getState() == false) {
if ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
Touch.horizontalTouched = (int) motionEvent.getX() / grid.getBlockSize();
Touch.verticalTouched = (int) motionEvent.getY() / grid.getBlockSize();
whatWasTouched = whatWasTouched(Touch.horizontalTouched, Touch.verticalTouched);
if(whatWasTouched.equals("DELETE")){
visualComponents.clear();
logicalComponents.clear();
}
placeState.toggleState();//sets to true
draw();
}
}else if (placeState.getState() == true) {
Touch.secondHorizontalTouch = (int) motionEvent.getX() / grid.getBlockSize();
Touch.secondVerticalTouch = (int) motionEvent.getY() / grid.getBlockSize();
placeComponent();
placeState.toggleState();//sets to false
draw();
}
return true;
}
Solved it by using emandt's suggestion. My personal Solution added below.
I'm using Android Studio for this.
I searched for solutions but couldn't find anything resembling this.
I want to know on which ImageView an UP action occurs while starting the DOWN action on a different ImageView (to eventually be able to drag one image over the other and make it snap to the same position by getting the position of the image I dragged over).
My example has two ImageViews with the id imageView (left) and imageView2(right).
In my example I'm not dragging anything yet, I just want to touch the left image, see "Action was down" in the log and lift the finger over the right image showing "Action was up2".
I don't know if this is easily possible.
As far as I can tell from testing, the MotionEvent.ACTION_UP only fires for an ImageView when you also pressed down on it beforehand. So when I release on top of imageView2 it only shows "Action was up" from the left image.
I wondered if it was possible by playing with return false, since the return value tells if an ActionEvent is consumed so I thought if the UP event of imageView returns false, maybe it does trigger the UP event of imageView2 but no. (Either complete misunderstanding on my part or it doesn't recognise UP on the second because it didn't start with a DOWN and MotionEvents probably always have to start with a DOWN).
public class MainActivity extends Activity {
ImageView imageView;
ImageView imageView2;
String DEBUG_TAG = "action";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
imageView2 = findViewById(R.id.imageView2);
imageView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
//int action = MotionEventCompat.getActionMasked(event);
int action = event.getActionMasked();
switch(action) {
case (MotionEvent.ACTION_DOWN) :
Log.d(DEBUG_TAG,"Action was DOWN"+v.toString());
return true;
case (MotionEvent.ACTION_MOVE) :
//Log.d(DEBUG_TAG,"Action was MOVE");
return true;
case (MotionEvent.ACTION_UP) :
Log.d(DEBUG_TAG,"Action was UP"+v.toString());
return false;
default :
//return true;
}
return true;
}
});
imageView2.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
//int action = MotionEventCompat.getActionMasked(event);
int action = event.getActionMasked();
switch(action) {
case (MotionEvent.ACTION_DOWN) :
Log.d(DEBUG_TAG,"Action was DOWN2"+v.toString());
return true;
case (MotionEvent.ACTION_MOVE) :
//Log.d(DEBUG_TAG,"Action was MOVE");
return true;
case (MotionEvent.ACTION_UP) :
Log.d(DEBUG_TAG,"Action was UP2"+v.toString());
return true;
default :
//return true;
}
return true;
}
});
}
}
If there is no simple way to do this, I'm thinking about solving this mathematically, but maybe some of you can help.
So my question is, is there a way to recognise an UP action on a second ImageView while currently being in a MotionEvent of another ImageView?
SOLUTION (see emandt's answer)
I ditched the second OnClickListener because I realised that the 2nd image doesn't need any, I just need its position.
Added this method:
#Nullable
private View getDroppedView(View droppedView, int x, int y, List<View> arrayOfPossibilities) {
Rect cVisibleBoundsRect = new Rect();
for (View cView : arrayOfPossibilities) {
//if currently iterated view doesn't have values for getGlobalVisibleRect, skip the .contains part
//ignore the item which is your current active item (which would potentially be dropped)
//getGlobalVisibleRect sets cVisibleBoundsRect immediately to the Rect given as parameter
if (!cView.getGlobalVisibleRect(cVisibleBoundsRect)||(cView.equals(droppedView))) continue;
if (cVisibleBoundsRect.contains(x, y)) {
Log.d(DEBUG_TAG,"Found something");
//THIS "cView" IS THE VIEW WHERE YOU RELEASED THE FINGER
return cView;
}
}
Log.d(DEBUG_TAG,"Found nothing");
return null;
}
And added this in onUP:
case (MotionEvent.ACTION_UP) :
View dropTarget;
Log.d(DEBUG_TAG,"Action was UP"+v.toString());
dropTarget = getDroppedView(v, (int)event.getRawX(), (int)event.getRawY(), listOfViews);
if (dropTarget != null){
v.setX(dropTarget.getX());
v.setY(dropTarget.getY());
}
I think you want to know which is the View where you release the finger from the screen, am I right?
To do this you can use the same "View.OnTouchListener()" for all of your Views and in the ACTION_UP you have to call a new method similar to this (pseudo-code):
....
case (MotionEvent.ACTION_UP) :
View[] cArrayOfPossibileViews = new View[]{ findViewById(IMAGE_1), findViewById(IMAGE2) }
getDroppedView(v, event.getRawX(), event.getRawY(), cArrayOfPossibileViews);
break;
}
....
#Nullable
private View getDroppedView(View view, int x, int y, View[] arrayOfPossibilities) {
Rect cVisibleBoundsRect = new Rect();
for (View cView : arrayOfPossibilities) {
if (!cView.getGlobalVisibleRect(cVisibleBoundsRect)) continue;
if (cVisibleBoundsRect.contains(x, y)) {
//THIS "cView" IS THE VIEW WHERE YOU RELEASED THE FINGER
return cView;
}
}
return null;
}
This method get View bounds and compare them avains X and Y of your Touch Event. If X and Y are contained inside a View bounds it means that View is the one you need.
I have a GridView and attached adapter to it. Adapter populates images in the grid. I have set setOnTouchLister to GridView in my Activity and implemented in the adapter only. In adapter I have an Integer[] imageIDs that contains resource ids of all images that are added & an ArrayList<ImageSourceObject> imgObjsArr that extends ImageView & has other properties set.
Now onTouch(), I want to change the image of the selected image to other one. Here's my code :
SEtting adapter of grid in Activity in onCreate :
// Set Objects in Game View
gameView = (GridView) this.findViewById(R.id.game_gridView);
gameObjAdapter = new GameObjectsAdapter(this, R.id.game_gridView, null);
gameView.setAdapter(gameObjAdapter);
ADAPTER :
public class GameObjectsAdapter extends BaseAdapter implements OnTouchListener {
super();
mContext = c;
this.layoutResourceId = layoutResourceId;
objects = data;
gridViewResAdap = (GridView) mContext.findViewById(this.layoutResourceId);
createObjectsArray();
Log.d("GOA", "Created Objects Array");
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageSourceObject imgView;
if (convertView == null) {
imgView = new ImageSourceObject(mContext);
imgView.setLayoutParams(new GridView.LayoutParams(20, 20));
imgView.getScaleType();
imgView.setScaleType(ScaleType.FIT_XY);
imgView.setPadding(0, 0, 0, 0);
} else {
imgView = (ImageSourceObject) convertView;
}
// Chk status of touched of imgView & set image accordingly
if (imgView.isTouched())
imgView.setImage(R.drawable.droid_touched2);
else
imgView.setImage(imageIds[position]);
return imgView;
}
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
int action = event.getActionMasked();
float currentXPos = event.getX();
float currentYPos = event.getY();
int position = gridViewResAdap.pointToPosition((int)currentXPos, (int) currentYPos);
// Key was Pressed Here
if (action == MotionEvent.ACTION_UP) {
if (position > 0) {
// Get the object which is clicked
ImageSourceObject isb = this.imgObjsArr.get(position);
Log.d("GA", " Postion ID " + isb.getId() + " [] ID : " + imageIds[position]);
// Change the status of touched & set image
isb.setTouched(true);
isb.setImage(R.drawable.droid_touched2);
// Update the ArrayList & Integer[] with this updated obj
this.imgObjsArr.set(position, isb);
imageIds[position] = R.drawable.droid_touched2;
Log.d("ISB", "++++ Object ID : " + isb.getId() + " [] ID : " + imageIds[position] + " ISB Touched :" + isb.isTouched());
Toast.makeText(mContext, "Postion Pressed : " + (position+1),
Toast.LENGTH_SHORT).show();
//this.gridViewResAdap.invalidate();
}
return true;
}
return false;
}
Logs :
02-19 15:19:11.615: D/GA(2046): Postion ID 2130837556 [] ID : 2130837556
02-19 15:19:11.617: D/ISB(2046): ++++ Object ID : 2130837559 [] ID : 2130837559 ISB Touched :true
The logs say that the object in Integer[] & ArrayList both are updated & have right values. After all this also the image is not updated on the screen. The gridViewResAdap is also the object of grid that is passed from the activity. I tried calling invalidate() on it also, but yet no results. As in my getView(), I am using imageIDs, so I have kept that also updated.
ImageSourceObject :
public class ImageSourceObject extends ImageView {
public void setImage(int resourceId) {
super.setImageResource(resourceId);
this.setId(resourceId);
}
Also, onTouch() gives error to call onPerformClick(), I am not sure where to call & why to call. I have alos not implemented onClickListener in the adapter. What can be done in this case & what to write in onClickListener when things are manged in onTouch().
Can you help me know why the image object is not updating and where am I going wrong ? I thought I have to refresh the grid, so have also called invalidate(), but no results.
Any help is highly appreciated.
Thanks
After your ImageView begins handling the TouchEvent, it is the only view allowed to do anything with that touch gesture. You want your GridView to intercept the TouchEvent, check the X and Y coordinates of the TouchEvent (event.getX(); event.getY()) and see if those coordinates fall within the bounds of one of your ImageView objects. If yes, set a flag on the ImageView or something that can trigger your setImageDrawable() method. I was able to achieve a similar effect in one of my projects but I had to create a custom GridView class (public class yourGridView extends GridView), then override the following:
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
switch(action) {
case MotionEvent.ACTION_DOWN:
Log.i(AGL, "InterceptAction = DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(AGL, "InterceptAction = MOVE");
break;
case MotionEvent.ACTION_CANCEL:
Log.i(AGL, "InterceptAction = CANCEL");
return false;
}
return true; //returning true tells your main Activity that you want the custom GridView to handle this TouchEvent; It will then send the TouchEvent to your GridView's onTouchEvent() method for handling.
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch(action) {
case MotionEvent.ACTION_MOVE:
int xCoord = (int) event.getX();
int yCoord = (int) event.getY();
Log.i(AGL, "MOVE EVENT;" + "\n" + "Touch X = " + Integer.toString(xCoord) + "\n" +
"Touch Y = " + Integer.toString(yCoord));
for(int i = 0; i < this.getChildCount(); i++) {
ImageView suspect = (ImageView) this.getChildAt(i);
if(suspect.getBounds().contains(xCoord, yCoord)) {
suspect.CHANGE_YOUR_IMAGE_HERE();
suspect.invalidate();
}
}
break;
}
return true;
}
PLEASE NOTE: getBounds() is a method I wrote in my custom ImageView class. This method returns a Rect object that represents the bounding box of the ImageView object. You will have to use your own logic for fetching the bounds of your ImageView.
ALSO NOTE: When I run this logic in my project, my ImageViews change images rapidly during the touch gesture. I haven't figured out yet how to limit it to one image change per MotionEvent.ACTION_MOVE.
I hope this helps.
You can simply do:
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
int action = event.getActionMasked();
float currentXPos = event.getX();
float currentYPos = event.getY();
int position = gridViewResAdap.pointToPosition((int)currentXPos, (int) currentYPos);
// Key was Pressed Here
if (action == MotionEvent.ACTION_UP) {
if (position > 0) {
((ImageView)v).setImageDrawable(ctx.getResources().getDrawable(R.drawable.droid_touched2));
}
return true;
}
return false;
}
I have created multiple views dynamically , in my case textViews. i have given them ids. what iwant is to select all the textViews ids over which i slide my finger. E.g if there are textviews saying A And B and when i slide finger from A to b i should get ids save in my array. Currently I am learning touch. I don't know how to implement it.
Best Regards
OnTouchListener MyOnTouchListener = new OnTouchListener(){
public boolean onTouch(View v, MotionEvent event)
{
Drawable d = getResources().getDrawable(R.drawable.small_corners);
switch(event.getAction() & MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN:
vibe.vibrate(20);
id.add(v.getId());
TextView vv = (TextView) v;
val.add(vv.getText().toString());
vv.setBackgroundDrawable(d);
String str = "";
//A pressed gesture has started, the motion contains the initial starting location.
return false;
case MotionEvent.ACTION_POINTER_DOWN:
//A non-primary pointer has gone down.
break;
case MotionEvent.ACTION_MOVE:
//A change has happened during a press gesture (between ACTION_DOWN and ACTION_UP).
int check = 0;
for (int i = 0; i < id.size(); i ++)
{
if (id.get(i) == v.getId())
check = 1;
}
if (check != 1)
{
id.add(v.getId());
vv = (TextView) v;
val.add(vv.getText().toString());
vv.setBackgroundDrawable(d);
}
return false;
case MotionEvent.ACTION_MASK:
//A change has happened during a press gesture (between ACTION_DOWN and ACTION_UP).
break;
case MotionEvent.ACTION_OUTSIDE:
//A change has happened during a press gesture (between ACTION_DOWN and ACTION_UP).
break;
case MotionEvent.ACTION_UP:
//A pressed gesture has finished.
break;
case MotionEvent.ACTION_POINTER_UP:
//A non-primary pointer has gone up.
break;
}
return false;
}
};
Did you tried ACTION_HOVER_ENTER and ACTION_HOVER_EXIT?
It might do the trick. See example in documentation of onGenericMotionEvent.
From what I have understood, try this; Implement onTouchlistner and set this listener for all of your TextViews, in onTouch() do this:
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction() == MotionEvent.ACTION_MOVE){
//SAVE YOUR VIEWS ID//
someArray[index] = ((TextView)v).getId())
return true;
}
return false;
}
Try different ACTIONS of MotionEvent