I'm not sure if this is a device issue or an issue with my code but I created a simple draggable sortable list using a relative layout and image views. I have very specific reasons for doing it this way and that is not my question.
The problem I'm having is occasionally it will totally freeze my app. The item will get stuck in the dragging state. If I lift my finger the shadow (dragging) object is still on the screen and if I touch the screen it will move to that spot. This will go on for about a minute and then I will get an error saying the app is non responsive with the option to kill it or wait. The only useful bit in the logcat is as follows:
12-09 14:23:13.157: W/WindowManager(16415): Drag is in progress but there is no drag window handle.
Then when the app times out I get this as an error
12-09 14:59:09.782: E/ActivityManager(16415): ANR in com.appname.appname (com.appname.appname/.MainActivity)
12-09 14:59:09.782: E/ActivityManager(16415): Reason: keyDispatchingTimedOut
I googled this error message and the only info was someone with no drag listener and another person saying it was the device touch sensor not keeping up.
Ideally, I'd love to fix this error and prevent it from happening in the first place. It does seem to happen mostly if I drag quickly, but I can't very well ask my users not to drag quickly... right?
Alternatively, is there a way that I could detect that dragging has frozen the app and interrupt the drag. Like set a timer on the touch listener and if there are no drag_location messages within like a second or two interrupt the drag? The timer stuff I know how to do, but I don't know how I would force the drag to stop while it's frozen. Any ideas?
Here's the code:
setup
//happens once when the app loads
RelativeLayout trackList = (RelativeLayout) findViewById(R.id.nsTrackList1);
trackList.setOnDragListener(new MyTrackDragListener(this));
//happens in a loop for each "track" (image view)
trackButton = new ImageView(this);
trackButton.setImageDrawable(nsPackages[trackId].banner[bannerSizeSelector]);
trackButton.setOnTouchListener(new MyTrackTouchListener());
On touch
public class MyTrackTouchListener implements OnTouchListener {
boolean isDragging=false;
float prevX, prevY;
public boolean onTouch(View view, MotionEvent motionEvent) {
if(motionEvent.getPointerCount() < 2 && !isDragging) return false;
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
isDragging=false;
prevX=0;
prevY=0;
return true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE) {
if(isDragging) return true;
boolean wasFirst = (prevX == 0 || prevY == 0);
float theTotalDiff = Math.abs(prevX - motionEvent.getX()) + Math.abs(prevY - motionEvent.getY());
prevX=motionEvent.getX();
prevY=motionEvent.getY();
if(wasFirst) return true;
if(theTotalDiff <3) return true;
ClipData data = ClipData.newPlainText("", "");
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(data, shadowBuilder, view, 0);
int thisViewId = view.getId();
//hide view
view.setVisibility(View.GONE);
isDragging=true;
return true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
isDragging=false;
return true;
}else {
Integer thisAction = motionEvent.getAction();
Log.d("looper","Motion action: "+thisAction.toString());
return false;
}
}
}
On Drag
class MyTrackDragListener implements OnDragListener {
public static boolean isDragging=false;
private MainActivity parent;
public MyTrackDragListener(MainActivity myAct){
parent=myAct;
}
@Override
public boolean onDrag(View v, DragEvent event) {
int action = event.getAction();
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
isDragging=true;
// do nothing
return true;
case DragEvent.ACTION_DROP:
View view = (View) event.getLocalState();
parent.doDropSort(view,(int) event.getY());
return true;
case DragEvent.ACTION_DRAG_ENDED:
if(isDragging && event.getResult()==false){
View view2 = (View) event.getLocalState();
parent.doDropSort(view2,(int) event.getY(),true);
return true;
}
isDragging=false;
break;
case DragEvent.ACTION_DRAG_LOCATION:
parent.doDragHover((int) event.getY());
return true;
default:
Log.d("looper","drag other... "+String.valueOf(event.getAction()));
}
return false;
}
}
A few things I already tried
- Removing the drag listener entirely
- Always return true from onDrag
- Always return false from onDrag
- Basically every combination of return true/false in drag and touch
- Removing the 2 finger and Action_Move part and triggering drag on Action_down instead
Same results. Drag and drop works perfectly about 70% of the time and then suddenly does the freezing behavior described above. Sometimes it's on the very first drag sometimes it's after several. I've noticed on consistent pattern except possibly drag speed. It seems to USUALLY happen when I'm dragging quickly, but drag direction or where I drag to doesn't seem to matter.
YAY! I Figured it out. Here's the answer incase it helps someone in the future.
I finally narrowed it down to only the top list item (don't know how I missed that) and then started commenting out lines until it didn't break which narrowed it down to this line:
The way my list was set up each item used the Layout Property BELOW in a relative layout (except the top item). Once I removed the top item from the view the 2nd to top item was below an item which was visibility GONE so it didn't know what to do. The solution was to set the parameter of the next item in the view. Luckily I manually set the view IDs of my track buttons (there are other views that scroll too.. labels and such) so they are sequential. So my fix was simply finding the item with id+1 and making it become the top item by setting BELOW to zero like this...