问题
I am in the process of coding a drag and drop library for GridViews. I am almost complete...however, I am getting an annoying NullPointerException upon ACTION_DROP every now and then. It points to the ViewGroup source code, lines 1147 and 1153, saying it is getting a null pointer when trying to recycle the drag event.
Some background on my process: I am basically embedding an OnDragListener into a custom adapter for a GridView. I have the user (programmer) implement an OnGetViewListener, when they set the GridView adapter so they can choose what Views to inflate, what images to include, etc. Then, in the DragDropAdapter (which is the GridView's adapter), when getView is called, it calles the designated OnGetViewListener to get the View the user wants. It creates an empty LinearLayout to stand as a container for the drag and drop. The user's View is added to the container, a drag listener is set to the container, the container's tag is set to the corresponding data collection for the GridView, and then the container is the View returned for getView().
The whole process of the drag and dropping GridView items is based off the container and its tags. When two GridView items are swapped (as a user is dragging an item), it basically removes the view from the cell being hovered over and adds it to the cell that the dragged view just left. This provides a visual "swapping" of items.
public class DragDropAdapter extends BaseAdapter {
...
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout containerView = new LinearLayout(mContext);
containerView.setLayoutParams(new GridView.LayoutParams(
GridView.LayoutParams.MATCH_PARENT,
GridView.LayoutParams.MATCH_PARENT));
View itemView = mGetViewListener.getView(position, convertView, parent);
itemView.setTag(mItems.get(position));
containerView.setTag(mItems.get(position));
containerView.addView(itemView);
containerView.setOnDragListener(new ItemDragListener());
return containerView;
}
...
View mExitedView = null;
public class ItemDragListener implements OnDragListener {
public ItemDragListener() {
}
private void swapCards(int startPosition, View viewToSwap) {
if (mExitedView == null) {
mExitedView = mGridView.getChildAt(startPosition);
}
ViewGroup viewToSwapContainer = (ViewGroup) viewToSwap;
ViewGroup exitedViewContainer = (ViewGroup) mExitedView;
View childViewToSwap = viewToSwapContainer.getChildAt(0);
View childViewExited = exitedViewContainer.getChildAt(0);
int enteredPosition = ItemCoordinatesHelper
.getGridPosition(viewToSwap);
int exitedPosition = ItemCoordinatesHelper
.getGridPosition(mExitedView);
Object itemToSwap = viewToSwap.getTag();
Object exitedItem = mExitedView.getTag();
viewToSwapContainer.setVisibility(View.INVISIBLE);
viewToSwapContainer.setTag(exitedItem);
viewToSwapContainer.removeAllViews();
if (childViewExited.getParent() != null)
((ViewGroup) childViewExited.getParent()).removeAllViews();
viewToSwapContainer.addView(childViewExited);
exitedViewContainer.setTag(itemToSwap);
exitedViewContainer.removeAllViews();
if (childViewToSwap.getParent() != null)
((ViewGroup) childViewToSwap.getParent()).removeAllViews();
exitedViewContainer.addView(childViewToSwap);
exitedViewContainer.setVisibility(View.VISIBLE);
}
...
@Override
public boolean onDrag(View v, DragEvent event) {
// TODO Auto-generated method stub
View heldView = (View) event.getLocalState();
int startPosition = ItemCoordinatesHelper.getGridPosition(heldView);
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
break;
case DragEvent.ACTION_DRAG_ENTERED:
swapCards(startPosition, v);
break;
case DragEvent.ACTION_DRAG_EXITED:
mExitedView = v;
break;
case DragEvent.ACTION_DROP:
View view = (View) event.getLocalState();
v.setVisibility(View.VISIBLE);
view.setVisibility(View.VISIBLE);
break;
case DragEvent.ACTION_DRAG_ENDED:
commitChangesToAdapter();
mExitedView = null;
break;
default:
break;
}
return true;
}
}
}
Now, on ACTION_DRAG_ENDED, the NullPointerException is being thrown because for some reason it cannot recycle the drag event, if I'm understanding the source code correctly.
Update: Here is the source where it throws the exception:
@Override
1100 public boolean dispatchDragEvent(DragEvent event) {
1101 boolean retval = false;
1102 final float tx = event.mX;
1103 final float ty = event.mY;
1104
1105 ViewRootImpl root = getViewRootImpl();
1106
1107 // Dispatch down the view hierarchy
1108 switch (event.mAction) {
1109 case DragEvent.ACTION_DRAG_STARTED: {
1110 // clear state to recalculate which views we drag over
1111 mCurrentDragView = null;
1112
1113 // Set up our tracking of drag-started notifications
1114 mCurrentDrag = DragEvent.obtain(event);
1115 if (mDragNotifiedChildren == null) {
1116 mDragNotifiedChildren = new HashSet<View>();
1117 } else {
1118 mDragNotifiedChildren.clear();
1119 }
1120
1121 // Now dispatch down to our children, caching the responses
1122 mChildAcceptsDrag = false;
1123 final int count = mChildrenCount;
1124 final View[] children = mChildren;
1125 for (int i = 0; i < count; i++) {
1126 final View child = children[i];
1127 child.mPrivateFlags2 &= ~View.DRAG_MASK;
1128 if (child.getVisibility() == VISIBLE) {
1129 final boolean handled = notifyChildOfDrag(children[i]);
1130 if (handled) {
1131 mChildAcceptsDrag = true;
1132 }
1133 }
1134 }
1135
1136 // Return HANDLED if one of our children can accept the drag
1137 if (mChildAcceptsDrag) {
1138 retval = true;
1139 }
1140 } break;
1141
1142 case DragEvent.ACTION_DRAG_ENDED: {
1143 // Release the bookkeeping now that the drag lifecycle has ended
1144 if (mDragNotifiedChildren != null) {
1145 for (View child : mDragNotifiedChildren) {
1146 // If a child was notified about an ongoing drag, it's told that it's over
1147 child.dispatchDragEvent(event); //<-- NULL POINTER HERE
1148 child.mPrivateFlags2 &= ~View.DRAG_MASK;
1149 child.refreshDrawableState();
1150 }
1151
1152 mDragNotifiedChildren.clear();
1153 mCurrentDrag.recycle(); //<-- NULL POINTER HERE
1154 mCurrentDrag = null;
1155 }
Any experts out there have any ideas?
回答1:
I have the same exception, because I'm removing a ViewGroup
which contains draggable views when I handle case DragEvent.ACTION_DRAG_ENDED
.
My solution: delay the execution of removeView
method by using post(runnable)
method.
来源:https://stackoverflow.com/questions/12714833/viewgroup-throwing-nullpointerexception-in-dispatchdragevent