Given an arbitrary ViewGroup G with an arbitrary collection of child views, how can I detect when the user clicks on any of the child views? In this case, I want to draw a highlight for G.
I could add an onClick listener for each child, but I'm trying to avoid that so that the code doesn't have to be changed when the layouts change.
Alternatively, I could add onTouch handlers to G and set the highlight during ACTION_DOWN. However, this would trigger for actions that don't actually result in clicks, such as a swipe (the swipe could be handled by ViewPager, for example, and ultimately be irrelevant to G).
My layout for G has the focusable attributes:
android:focusable="true"
android:focusableInTouchMode="true"
Thanks.
Here is how I do it:
//in onTouch method of parent, I get the coordinates of click
int x = ((int) motionEvent.getX());
int y = ((int) motionEvent.getY());
//obtain the clickable arrea of the child as HitRect
Rect clickRect = new Rect();
Rect rect = new Rect();
imageView.getHitRect(rect);
//ask if the area contains the coordinates of the click
if(rect.contains(x, y)){
//do some work like if onClickListener on the child was called.
return false; //you clicked here, don't need to handle other Childs
}
//ask for other childs like before...
Now, you can target the parent as the delegate of all clicks done inside it, even if it is done in a child.
EDIT:
To ignore other touch event that are not click, you can ask for how much user moved the finger:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_CANCEL:
if (Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
return true; // user mover finger too much, ignore touch
}
return false; // finger still there waiting for click
I give a square of 10 pixels to permit a confortable click, and if you exit it, I ignore it.
EXTRA:
Here is the complete code for click and long click with onTouchListener.
You could use the View.getChildCount() to loop through all child views and see if the touch intersects with the child view.
This involves getting x and y positions and calculating if it fits within the child view, use View.getChildAt(position) to get the reference to the child view .
So it would be something like this:
int childNr = theView.getChildCount();
for (int i = 0; i < childNr; i++){
YourView tmp = (YourView) theView.getChildAt(i);
if(tmp.intersects(x, y)){
do some work
}
}
here you would have to put your view variable instead of theView and the class name which handles the views instead of (YourView) and x, y are the coordinates of the pressed spot.
In your XML, you could add point all the children to the same onClick method. Inside that method you could draw the highlight to G and then do something (or nothing) for the individual child view.
Related
I'm trying to create a One-armed Bandit app.
I have created an animation xml file to go through multiple images. When a button is clicked, the animation stops.
My question is how to compare the picture that one animation stopped on with that of another? So far I've tried something like this:
if(wheel1.getBackground().getConstantState().equals(wheel2.getBackground().getConstantState())) matches++;
Any help is appreciated.
you must be starting the animation with .animationStart()
just use .onAnimationStop() and it will trigger the event automatically.
A View should not maintain application logic, the controller (your hosting Activity or Fragment) should.
That said, to achieve what you want use View.setTag() to apply a logical description of each View to it.
Then when stopping animation, loop through all Views you have and get their position on screen, get the Views mostly visible in each column of your bandit machine and compare their tags (View.getTag())
for example, if the items animate vertically use below method to determine where the bandit stopped.
//the area where to compare views
int BOUND_TOP, BOUNT_DOWN;
//your content view
ViewGroup rootLayout;
//method to get information about what is visible
public List<Object> getVisibleViewTags() {
List<Object> list = new LinkedList<>();
int count = rootLayout.getChildCount();
for (int pos = 0; pos < count; pos++) {
View child = rootLayout.getChildAt(pos);
float translationY = child.getTranslationY();
if (translationY > BOUND_TOP && translationY < BOUND_DOWN) {
list.add(child.getTag());
}
}
return list;
}
Now you just need to attach information about a view as tag to it.
example:
view.setTag("view_apples");
or
view.setTag("view_bananas");
Anybody knwos, how I can animate my GridView after deleting 1 entry? I want something like this:
The first is my normal gridview with 8 items. If I'm deleting one item, I want, that a animate starts and the coloumn of the deleted item will slide up and fill the free place.
Anybody an idea?
Thanks for help :)
One of possible ways to achieve that can be the following set of steps in given order:
1) Save left/top positions of all grid children before deletion. One thing to note here is that the left/top positions should be mapped not to the child position in gridview (which will change after deletion), but to the itemId in adapter which is going to be same for each child even after deletion. To do so, adapter should support stableIds;
2) Delete grid child from adapter and call notifyDataSetChanged();
3) Right after previous step add OnPreDrawListener to the gridview. That is good place to initialize animations, as the layout is ready but not yet drawn to screen. Now we can access all children's coordinates after deletion. On the first onPreDraw() callback, remove listener and calculate diffX and diffY values of each child. And set translationX, translationY values to diffX and diffY and start animating each child to 0 translation value.
Pseudo code might look like this:
// Step 1
saveTopLeftPositions(gridView);
// Step 2
adapter.removeAt(deletedPosition);
adapter.notifyDataSetChanged();
// Step 3
gridView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
gridView.getViewTreeObserver().removeOnPreDrawListener(this);
int firstVisiblePos = grid.getFirstVisiblePosition();
int childCount = grid.getChildCount();
for (int i=0; i<childCount; i++) {
View child = grid.getChildAt(i);
long itemId = adapter.getItemId(i + firstVisiblePos);
float diffX = child.getX() - getOldXPos(itemId);
float diffY = child.getY() - getOldYPos(itemId);
child.setTranslationX(diffX);
child.setTranslationY(diffY);
child.animate().translationX(0).translationY(0);
}
return false;
}
});
I have a problem scrolling my childscene. I have created a CameraScene which i am trying to scroll with a touch event. My childscene is not scrolling, however, if i scroll on the camera attached to the engine the parent scene scrolls fine.
So how do i get my child scene to scroll without the objects attached to myparents scene scrolls along?
public StatsScene(Context context, VertexBufferObjectManager vbo) {
super(new SmoothCamera(0, 0, WITDH, HEIGHT, 0, SPEEDY, 0));
this.setOnSceneTouchListener(new IOnSceneTouchListener() {
#Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if(pSceneTouchEvent.getAction() == MotionEvent.ACTION_DOWN) {
mTouchY = pSceneTouchEvent.getMotionEvent().getY();
}
else if(pSceneTouchEvent.getAction() == MotionEvent.ACTION_MOVE) {
float newY = pSceneTouchEvent.getMotionEvent().getY();
mTouchOffsetY = (newY - mTouchY);
float newScrollX = getCamera().getCenterX();
float newScrollY = getCamera().getCenterY() - mTouchOffsetY;
getCamera().setCenter(newScrollX, newScrollY);
mTouchY = newY;
}
return true;
}
});
}
I'm not really into AndEngine and I'm not sure if I get your problem right (in your code is nothing about "myparents" or "childscene"), but when something is attached to your scene, then this implies it will move with it. You could scroll your children in the other direction to maintain their position, but that could get you into trouble in the longterm. If it is possible, try to seperate your scrolling scene and your objects, meaning, that they shouldn't be children of each other. Instead, if you want them to keep them related, give them a common parent. If you move one object now, the siblings won't. Hope that helps.
From your description I would think that your parent scene is the one getting your the input so I'm guessing, please correct me if I'm wrong, that you are setting your child scene something like this:
mMainScene.attachChild(mChildScene);
In this case you will have to deal with deviating the input to the child instead of the parent. However, you have a few options here:
If your child scene occupies full screen and you don't need to worry about updating/drawing your parent scene, simply swap scenes with
mEngine.setScene(mChildScene);
If you do need to keep drawing and updating your parent scene check the MenuScene pre-made class and Scene.setChildScene() method, there is one example on how using this in the AndengineExamples project I think. Using this class will let you take the input on the child scene but still drawing and updating your main scene, it even let's you set your child in a modal way.
I have an activity that allows users to swipe between 3 different views. Each view displays a list of images. The images have onClick events that call a new activity and makes the images full screen.
This all works ok however if I try to swipe between the 3 different views and my finger swipes over an image it will trigger the onClick event and open the image fullscreen. So I am wondering how can I put priority on onTouch (for the page/view swapping) ?
(the onTouch event uses a gestureListener and i'm using onFling and flipper methods..)
(the onClick is simply a imageView.setOnClickListener(new OnClickListener() )
If you return true from your onFling(…) it should consume the event and stop propagation.
You can find here solution for delegating a touch from parent to child view, if you need more info.
mDrawerLayout.post(() -> {
Rect delegateArea = new Rect();
mCalendarView.getHitRect(delegateArea);
delegateArea.right += 100;
delegateArea.bottom += 100;
TouchDelegate touchDelegate = new TouchDelegate(delegateArea, mCalendarView);
if (View.class.isInstance(mCalendarView.getParent())) {
((View) mCalendarView.getParent()).setTouchDelegate(touchDelegate);
}
});
I am trying to use a ViewFlipper and make it act like the home screen(The layout will move with your finger). Check out this for an example. I want to do this with a ViewFlipper that only contains two children so the opposite view should be shown on either side of the current view depending on which way the user moves their finger. This code works but only for 1 direction at a time. This is in onTouchEvent.
case MotionEvent.ACTION_MOVE:
leftView.setVisibility(View.VISIBLE);
rightView.setVisibility(View.VISIBLE);
// move the current view to the left or right.
currentView.layout((int) (touchEvent.getX() - oldTouchValue),
currentView.getTop(),
(int) (touchEvent.getX() - oldTouchValue) + 320,
currentView.getBottom());
// place this view just left of the currentView
leftView.layout(currentView.getLeft() - 320, leftView.getTop(),
currentView.getLeft(), leftView.getBottom());
// place this view just right of the currentView
rightView.layout(currentView.getRight(), rightView.getTop(),
currentView.getRight() + 320, rightView.getBottom());
Which ever of the bottom two lines I put last that direction will work correctly but the other will not.
Here is how I set the leftView and rightView:
final View currentView = myFlipper.getCurrentView();
final View leftView, rightView;
if (currentView == meView) {
Log.d("current layout: ", "me");
leftView = youView;
rightView = youView;
} else if (currentView == youView) {
Log.d("current layout: ", "you");
leftView = meView;
rightView = meView;
} else {
leftView = null;
rightView = null;
}
Is it going to be possible to set it up so that the same view is shown on both sides of the current view?
Thanks stealthcopter
That worked here is the new code if anyone is interested.
if (touchEvent.getX() < oldTouchValue){
// place this view just right of the currentView
rightView.layout(currentView.getRight(), rightView.getTop(),
currentView.getRight() + 320, rightView.getBottom());
}else if (touchEvent.getX() > oldTouchValue) {
// place this view just left of the currentView
leftView.layout(currentView.getLeft() - 320, leftView.getTop(),
currentView.getLeft(), leftView.getBottom());
}
I also moved the setVisibility() calls to the MotionEvent.ACTION_DOWN in an attempt to get rid of some flickering of the views. This helped but I still get a bit.
I have possibly not very constructive suggestion, but if you want it to behave like a home screen, why you don't want to look at the src of that, and modify it to your needs ?
To get rid of the flickering, set setVisibility(View.INVISIBLE) in MotionEvent.ACTION_DOWN. The default value is "GONE" on a view. That means that you can not set Layout() on a view until it's either "VISIBLE" or "INVISIBLE". So in three steps:
Set Visibility to INVISIBLE on the View
Set Layout() on the View
Set Visibility to Visible on the View
If I understand your request, this effect should now be implemented using a View Pager
Looks like this: