How can I detect MotionEvent.ACTION_MOVE outside a View's ViewParent? - java

I have the following component structure:
- CustomComponent extends ConstraintLayout
- ImageView
- ImageView
- ViewPager2
- CustomFragment extends ListFragment
- ListView
- ListItem extends ConstraintLayout
- ImageView
- ListItem extends ConstraintLayout
- ImageView
- ListItem extends ConstraintLayout
- ImageView
- etc...
Visually it looks like this (I left out ListView for brevity):
The ImageView in ListItem will act as a drag button for dragging the ListItem, so that the items can be reordered. But ultimately, I also want to be able to drag a ListItem outside the ViewPager2 and drop it on one of the ImageViews in the CustomComponent. I think I will implements this by temporarily hiding the original and then "clone" and attach the ListItem as a child to CustomComponent.
However, before even getting there, the problem currently is that MotionEvent.ACTION_MOVE on the ImageView is constrained to its parent ListItem as illustrated by the red arrows. As soon as the touch gesture goes outside ListItem, I stop receiving MotionEvent.ACTION_MOVE events.
This is the listener I currently have on the drag button:
dragButton.setOnTouchListener((view, event) -> {
System.out.println("ListItem to drag is: " + view.getParent());
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
dragging = false;
break;
case MotionEvent.ACTION_MOVE:
if(!dragging) {
dragging = true;
System.out.println('drag start');
}
else {
System.out.println('drag');
}
break;
case MotionEvent.ACTION_UP:
if(dragging) {
dragging = false;
System.out.println('drag end');
}
break;
}
return true;
});
While I imagine that it will keep being detected for a bit if I were to move the ListItem along with the MotionEvent coordinates, I doubt it would be reliable, as it might lag behind and thereby slip from under the touch gesture. Moreover: with the above mentioned "cloning", this is not even relevant anymore, since its the "clone" that will be moving — not the original.
So, is there a way to detect MotionEvent.ACTION_MOVE outside ListItem when dragging the ImageView, as illustrated by the blue arrows?
I know about onInterceptTouchEvent(), which I could override in CustomComponent, but that gives me no information about which View the event is meant for, whereas in the above OnTouchListener I can easily determine which ListItem to drag by view.getParent().
I'm open to hearing completely other ideas of how to approach this as well, of course.

Related

How to add motion in Viewpager2?

original Viewpager2 can scroll left&right to change page.
And I want to add "down to up scroll" to use 'startActivity'
I tried like this...but
Viewpager2 viewpager;
viewpager.getChildAt(0).setonTouchListener(new View.onTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
return true;
}
});
It cannot work original viewpager's left&right scroll.
I want to save original's, and add just another scroll.
How can i do?
#I searched about onInterceptTouchEvent, DispatchTouchEvent...
I cannot find how to use.
you want vertical scroll to start new activity or to scroll through list items?
if you want to scroll through list then use recyclerView,see this - Simple Android RecyclerView example
and for starting new activity or any process on swiping(top to bottom or left to right) see this - Android: How to handle right to left swipe gestures

How to refresh layout/context from list adapter (I think this is what i need)

Im having a bug that i cant understand the reason and how to resolve it. I belive that is a problem of layout/view/context refresh but i dont know.
I have a cell from a listView(I prefer recyclerView but the project has years) and in the corner of the cell i have a button to show more options. Programatically it just make an element View.GONE and another element View.VISIBLE.
I will attach code in a moment
To this button i setted too a listener that when i tap on it it do the opposite of below mentioned. It shows some elements and hide an entire LinearLayout from the cell. The elements are showed BUT the LinearLayout keeps on the screen like bugged. If i tap anywhere it disappears and if i try to tap on it it disappears too. Its like the view of that linear got bugged and keep in there like a ghost view. I will shop some pictures.
The cell normally at the beginning: https://imgur.com/1BjK0KP
The cell after i press the entire view to show the LinearLayout at the bottom of the cell: https://imgur.com/eONSptW
The cell after i press the arrow of the corner to hide the LinearLayout. Here it shows the view bugged https://imgur.com/jrT0qxV
The cell after i tap anywhere else https://imgur.com/XD1jN7U
public void expandView(View view){
final View cellView = view;
final LinearLayout editLinear = (LinearLayout) view.findViewById(R.id.cart_edit);
editLinear.setVisibility(View.VISIBLE);
final TextViewFont countText = (TextViewFont) view.findViewById(R.id.itemCount);
countText.setVisibility(View.GONE);
final TextViewFont total = (TextViewFont) view.findViewById(R.id.itemTotal);
final ImageView imageViewArrow = (ImageView) view.findViewById(R.id.cart_edit_image);
imageViewArrow.setImageDrawable(context.getResources().getDrawable(R.drawable.icon_arrow_up));
//notifyDataSetChanged();
imageViewArrow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
countText.setVisibility(View.VISIBLE);
editLinear.setVisibility(View.GONE);
imageViewArrow.setImageDrawable(context.getResources().getDrawable(R.drawable.icon_arrow_down));
}
});
First of all, you don't need to store context because in View you have the method view.getContext().
I recommend this article:
https://possiblemobile.com/2013/06/context/
On the other hand, ensure that you don't have a ghost view that is overlapping your image view.

android imageview onclick behaviour

Can you create different actions for clicking on the same ImageView but different parts? Say I have an ImageView and I want for it to act differently if I click on the top of the ImageView and differently if I click on the middle part of the ImageView . Can I achieve this in android
The best way is to add an OnTouchListener to the view and handle click event by yourself related to the touch coordinate.
But if you need an easy way which I don't suggest because it can lead to performance issue you can add transparent views on top of your ImageView and add different OnClickListener for them.
if you have fixed sections in your imageview you can use a frame layout and add 3(for example) transparent views on your imageview and then set click listeners on those views.
but if you have dynamic sections you can handle it by combining onTouchListener and onClickListener, for example :
// just store the touch X,Y coordinates
View.OnTouchListener touchListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// save the X,Y coordinates
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
lastTouchDownXY[0] = event.getX();
lastTouchDownXY[1] = event.getY();
}
// let the touch event pass on to whoever needs it
return false;
}
};
View.OnClickListener clickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
// retrieve the stored coordinates
float x = lastTouchDownXY[0];
float y = lastTouchDownXY[1];
// use the coordinates for whatever or check the clickable areas
Log.i("TAG", "onLongClick: x = " + x + ", y = " + y);
}
};
I think the best way for this is that you declare a frameLayout as parent view then you can declare views on your imageView then clicking each view will perform what you want.

OnDragListener Fires Several Times?

I have several layouts; each containing a TextView. If the user drags a TextView unto another TextView, I'm going to execute the method swapFamily() but it fires several times, depending on the number of families.
FamilyItemLayout is just a custom LinearLayout with a String attribute attached to it and with add'tl getters and setter for that attribute.
However, my issue is that,
private class onDropFamily implements View.OnDragListener {
#Override
public boolean onDrag(View view, DragEvent event) {
TextView txtDragged = (TextView) event.getLocalState();
TextView txtTarget = (TextView) view;
String familyDragged = ((FamilyItemLayout) txtDragged.getParent()).getFamily();
String familyTarget = ((FamilyItemLayout) txtTarget.getParent()).getFamily();
switch (event.getAction()) {
// Signals the start of a drag and drop operation.
case DragEvent.ACTION_DRAG_STARTED:
break;
// Signals to a View that the drag point has entered the bounding box of the View.
case DragEvent.ACTION_DRAG_ENTERED:
view.setBackgroundResource(R.mipmap.pill_sun);
break;
// Signals that the user has moved the drag shadow outside the bounding box of the View.
case DragEvent.ACTION_DRAG_EXITED:
view.setBackgroundResource(R.mipmap.pill_sky);
break;
// Signals to a View that the user has released the drag shadow, and the drag point is within the bounding box of the View.
case DragEvent.ACTION_DROP:
break;
// Signals to a View that the drag and drop operation has concluded.
case DragEvent.ACTION_DRAG_ENDED:
// Check if drag event was successful
if (dropEventHandled(event)) {
fixedPlan.swapFamily(familyDragged, familyTarget);
}
txtDragged.setVisibility(View.VISIBLE);
txtTarget.setBackgroundResource(R.mipmap.pill_sky);
break;
}
return true;
}
private boolean dropEventHandled(DragEvent dragEvent) {
return dragEvent.getResult();
}
}
I believe you should put those code in ACTION_DROP instead of ACTION_DRAG_ENDED
http://developer.android.com/reference/android/view/DragEvent.html
All views that received an ACTION_DRAG_STARTED event will receive the
ACTION_DRAG_ENDED event even if they are not currently visible when
the drag ends.
Also remember to return correct value for ACTION_DROP
The View should return true from its onDragEvent(DragEvent) handler or
OnDragListener.onDrag() listener if it accepted the drop, and false if
it ignored the drop.

Custom horizontal scroll animation to track finger on view in android

So this is not a tecnical question with code but more on some thoughts. I am new to android developing and because of that I am not completely familiar with a lot of what it has to offer.
I have a list of items on the screen and you can scroll up and down to view the different items.
My issue came up when I decided to have the items be able to slide left and right to reveal a settings like panal with a few optons below them(each item has its own settings below it, not a setting screen for all items kind of deal). I looked around for different ways to do this but cant seem to find one that works. What I am thinking so far is to have a HorizonalScrollView with my item in it with the setting menu to the left or right off the screen, but when I put my text area in the HorizonalScrollView, it only takes up half the screen, so I can fit two side by side, not what I wanted and the end result wont be what I imagined. I really would like a solution that allows for the setting to be under the item, so when you push it out of the way it reveals the settings.
Is there a better way or should I just continue trying to make my HorizonalScrollView work, any guides or thought on how I could go about this would be greatly appreciated.
Went though my apps to see if I could find one that has something similar, gmail app has it. Here is a link to an image that gives you an idea, after you slide the item to the side it brings up an undo or archive buttons in the place of the item as if they were hiding under the list item you swiped away
it's quite an interesting topic this question. And be prepared to do a certain amount of custo stuff.
First of all, drop the HorizonalScrollView, I don't think it will help you. The layout of each item should be something like this (pseudo-code, you can build the XML yourself =] ):
FrameLayout
RelativeLayout id:topContent background:someSolidColor
// inside this RelativeLayout, the stuff that is visible on the ListView
RelativeLayout id:bottomContent
// inside this RelativeLayout, the stuff that is behind the content
/FrameLayout
that way you will be literally putting one thing on top of the other. Also note that the topContent have a background with a solid color. If you do not specify any background, both RelativeLayouts will be visible. Also note that I used RelativeLayout, just because I like them, and I like their flexibility, but this will depend on the content of your list view and your settings.
And now is when things get fun, you'll need a GestureDetector to detect the finger sliding and you'll use that value to generate a margin offset on the id:topContent.
You can create a TouchListener like this:
public class MySlideListener extends View.OnTouchListener{
private View v;
private GestureDetector gestureDetector;
public MySlideListener (View v){
this.v = v;
gestureDetector = new GestureDetector(v.getContext(), myGestureListener);
}
public boolean onTouch (View v, MotionEvent event){
return gestureDetector.onTouchEvent(event);
}
private SimpleOnGestureListener myGestureListener = new SimpleOnGestureListener(){
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
// now here we make the view scroll
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
lp.leftMargin = distanceX;
lp.rightMargin = -distanceX;
// You might need to call view.requestLayout();
// but first give it a try without it
// This part of the method for processing the horizontal
// offset can (and should) be further developed to add some
// 'snap-in' or transparency functionality to make the whole
// concept work better.
// But this code should give you a proof of concept on how to deal with stuff.
// The important part is that now you have a call back that have access
// to the view during onScroll.
// Also might be necessary to enable/disable the bottomContent view
// in order for it to be not clickable whilst not visible.
return true;
}
}
}
and then set a new of those listeners for each topContent of your ListView (probably inside the getView from the adapter) with topContentView.setOnTouchListener(new MySlideListener(topContentView));
Please keep in mind that I typed all this code by heart and is 100% untested!
edit:
The above code is the correct direction, but it's a 100% untested thing.
Now the code below, I've just compiled, tested, and this code works!
This class is also a bit more efficient as you can create only one and apply the same instance to all the items created on your adapter. You can see it's getting the view to scroll on the touch event.
public class MySlideListener implements View.OnTouchListener {
private View view;
private ListView listView;
private GestureDetector gestureDetector;
public MySlideListener(ListView lv) {
listView = lv;
gestureDetector = new GestureDetector(lv.getContext(), myGestureListener);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
view = v;
gestureDetector.onTouchEvent(event);
return true;
}
private SimpleOnGestureListener myGestureListener = new SimpleOnGestureListener() {
private int origLeft, origRight;
public boolean onDown(MotionEvent e) {
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
origLeft = lp.leftMargin;
origRight = lp.rightMargin;
return true;
};
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
listView.requestDisallowInterceptTouchEvent(true);
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
lp.leftMargin = (int) (origLeft + (e2.getRawX() - e1.getRawX()));
lp.rightMargin = (int) (origRight - (e2.getRawX() - e1.getRawX()));
view.requestLayout();
return true;
};
};
}

Categories

Resources