I want to implement a Gallery that allows the user to drag items out of it. This shouldn't get in the way of scrolling/flinging.
Given the interface layout, the user can only drag items out of the Gallery in a vertic开发者_高级运维al path, and scroll the Gallery horizontally.
Is this feasible? Is there an easy way of detecting horizontal movements, and defer them to the Gallery's event handlers, and intercept vertical movements? Or do I have to override onInterceptTouchEvent()
and do the math myself?
(edit: I'm giving a try to a GestureListener, overriding onFling and onScroll, and passing the events to the Gallery when the vertical scroll distance is below a threshold)
I inherited Gallery, and overrode the onScroll method. I haven't implemented the drop logic yet, but the dragging and scrolling work.
When I can spare the time, I'll write a full post in my blog with more details, and the drop mechanism. For now, a simple copy-paste in case somebody reaches this page in the future.
To keep the behavior where it belongs, I created this DraggableView interface:
public interface DraggableView {
public void beforeDrag();
public DragView createDragView();
public Object getDraggedInfo();
public void afterDrop();
}
Views in the Gallery can be dragged out of the Gallery area if they implement this view. They are notified before and after, and must implement two methods:
createDragView()
returns a DragView object. Basically, a transparent hovering bitmap to accompany the user's movement.getDraggedInfo()
returns the information that should reach the drop target.
Here's the DragView class:
public class DragView extends ImageView {
private final LayoutParams mLayoutParams;
public DragView(Context context, Bitmap bitmap) {
super(context);
mLayoutParams = new LayoutParams();
mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
mLayoutParams.height = LayoutParams.WRAP_CONTENT;
mLayoutParams.width = LayoutParams.WRAP_CONTENT;
mLayoutParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_NOT_TOUCHABLE;
mLayoutParams.format = PixelFormat.TRANSLUCENT;
mLayoutParams.windowAnimations = 0;
mLayoutParams.alpha = 0.5f;
setImageBitmap(bitmap);
setLayoutParams(mLayoutParams);
}
public void move(int x, int y) {
mLayoutParams.x = x;
mLayoutParams.y = y;
}
}
As you can see, it takes a Bitmap
in construction, and creates a hovering ImageView
. Finally, here is the (just implemented and not very clean) Gallery code to make it all happen:
public class DraggableItemGallery extends Gallery {
private boolean mDragging;
private DragView mDragView;
private DraggableView mDragViewOwner;
private WindowManager mWindowManager;
private boolean mScrollStarted;
public DraggableItemGallery(Context context) {
super(context);
initialize();
}
public DraggableItemGallery(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public DraggableItemGallery(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
public void initialize() {
mWindowManager = (WindowManager)
getContext().getSystemService("window");
}
private void startDraggingItem(DraggableView view, int x, int y) {
mDragging = true;
mDragViewOwner = view;
mDragView = view.createDragView();
mDragView.move(x, y);
mWindowManager.addView(mDragView, mDragView.getLayoutParams());
}
private void continueDraggingItem(int x, int y) {
DragView dragView = getDragView();
dragView.move(x, y);
mWindowManager.updateViewLayout(dragView, dragView.getLayoutParams());
}
private void stopDraggingItem() {
mDragging = false;
mWindowManager.removeView(mDragView);
mDragViewOwner.afterDrop();
mDragView = null;
mDragViewOwner = null;
}
private DraggableView getDraggedItem() {
return mDragViewOwner;
}
private DragView getDragView() {
return mDragView;
}
private boolean isDraggingItem() {
return (mDragging);
}
private void setScrolling(boolean scrolling) {
mScrollStarted = scrolling;
System.out.println("Scrolling " + scrolling);
}
private boolean isScrolling() {
return mScrollStarted;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if ((event.getAction() & ACTION_MASK) == ACTION_UP) {
setScrolling(false);
if (isDraggingItem())
stopDraggingItem();
}
return super.onTouchEvent(event);
}
final Rect onScroll_tempRect = new Rect();
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (isScrolling()) {
if (isDraggingItem()) {
int x = (int) e2.getX(),
y = (int) e2.getY();
System.out.println("Moving to " + x + " " + y);
continueDraggingItem(x, y);
return true;
} else {
/* Not dragging, let the Gallery handle the event */
return super.onScroll(e1, e2, distanceX, distanceY);
}
} else {
setScrolling(true);
boolean isVertical = (Math.abs(distanceY) > Math.abs(distanceX));
if (isVertical) {
int x = (int) e1.getX(),
y = (int) e1.getY();
View hitChild = null;
// A tiny optimization, declared above this method
final Rect hitRect = onScroll_tempRect;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.getHitRect(hitRect);
if (hitRect.contains(x, y)) {
hitChild = child;
break;
}
}
if (hitChild instanceof DraggableView) {
startDraggingItem((DraggableView) hitChild, x, y);
return true;
}
}
/* Either the scroll is not vertical, or the point
* of origin is not above a DraggableView. Again,
* we let the Gallery handle the event.
*/
return super.onScroll(e1, e2, distanceX, distanceY);
}
}
}
Hope it helps.
Here is something I did to do exactly that. That's only the code for the activity... there is some layout and other res files you'll need...
Every list item has an icon and name matched randomly.
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.FrameLayout.LayoutParams;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import java.util.ArrayList;
import java.util.Arrays;
public class DragActivity extends Activity implements View.OnTouchListener, AdapterView.OnItemLongClickListener
{
private static final String TAG="DragActivity";
private static final int NOT_DRAGGING = 0;
private static final int DRAGGING = 1;
private int state=NOT_DRAGGING;
private ImageView draggable =null;
private int dragged_position;
float current_x, current_y;
int current_icon = R.drawable.notepad;
private ArrayList<String> names = new ArrayList<String>(Arrays.asList("John", "Mark", "Mathew", "Luke", "Bob", "Will", "Brian", "Mike"));
private ArrayList<Integer> icons = new ArrayList<Integer>(Arrays.asList( R.drawable.glasses, R.drawable.monkey, R.drawable.normal, R.drawable.smile, R.drawable.wink));
private ArrayList<Integer> matching;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setupListContent();
ListView list = (ListView) findViewById(R.id.main_list);
list.setAdapter(new DragListAdapter());
list.setOnItemLongClickListener(this);
list.setOnTouchListener(this);
// need to use the same view for the both listeners, as described in Android documentation :
// http://developer.android.com/guide/topics/ui/ui-events.html
// onTouch() - This returns a boolean to indicate whether your listener consumes this event. The important thing
// is that this event can have multiple actions that follow each other. So, if you return false when the down action
// event is received, you indicate that you have not consumed the event and are also not interested in subsequent
// actions from this event. Thus, you will not be called for any other actions within the event, such as a finger
// gesture, or the eventual up action event.
ImageView image = (ImageView) findViewById(R.id.main_image);
image.setImageResource(current_icon);
}
private void setupListContent() {
matching = new ArrayList<Integer>();
for (int i=0; i<names.size(); i++) {
matching.add((int) (icons.size() * Math.random()));
}
}
@SuppressWarnings("unchecked")
private class DragListAdapter extends ArrayAdapter {
public DragListAdapter() {
super(DragActivity.this, R.layout.list_item, names);
}
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.list_item, parent, false);
}
row.setDrawingCacheEnabled(true);
TextView name = (TextView) row.findViewById(R.id.item_text);
ImageView icon = (ImageView) row.findViewById(R.id.item_icon);
name.setText(names.get(position));
icon.setImageResource(icons.get(matching.get(position)));
return row;
}
}
private boolean checkOnDropIcon(MotionEvent me) {
ImageView drop_icon = (ImageView) findViewById(R.id.main_image);
Rect icon_rect = new Rect();
drop_icon.getGlobalVisibleRect(icon_rect);
Log.d(TAG, "icon at " + icon_rect.left + "<- ->" + icon_rect.right + ", " +
icon_rect.top + " ^ v" + icon_rect.bottom);
if ((me.getRawX()<icon_rect.left) || (me.getRawX()>icon_rect.right) ||
(me.getRawY()<icon_rect.top) || (me.getRawY()>icon_rect.bottom)) {
return false;
}
else {
return true;
}
}
private void checkOnDrop(MotionEvent me) {
boolean onDropIcon = checkOnDropIcon(me);
ImageView image = (ImageView) findViewById(R.id.main_image);
if ((onDropIcon) && (current_icon==R.drawable.notepad)) {
current_icon = R.drawable.exit;
image.setImageResource(current_icon);
image.invalidate();
return;
}
if ((!onDropIcon) && (current_icon==R.drawable.exit)) {
current_icon = R.drawable.notepad;
image.setImageResource(current_icon);
image.invalidate();
return;
}
}
public boolean onTouch(View view, MotionEvent me) {
if (state == NOT_DRAGGING) {
// get the position of the touch so we know where to place the dragging item if it is a long press
current_x = me.getRawX();
current_y = me.getRawY();
return false;
}
else {
FrameLayout frame = (FrameLayout) findViewById(R.id.drag_space);
if (me.getAction()==MotionEvent.ACTION_UP) {
frame.removeAllViews();
draggable=null;
frame.setVisibility(View.GONE);
state=NOT_DRAGGING;
// check if we dropped a name
if (checkOnDropIcon(me)) {
names.remove(dragged_position);
matching.remove(dragged_position);
ListView list = (ListView) findViewById(R.id.main_list);
DragListAdapter adapter = (DragListAdapter) list.getAdapter();
adapter.notifyDataSetChanged();
}
// restore the icon
ImageView image = (ImageView) findViewById(R.id.main_image);
current_icon = R.drawable.notepad;
image.setImageResource(current_icon);
image.invalidate();
}
if (me.getAction()==MotionEvent.ACTION_MOVE) {
int frame_position[] = new int[2];
frame.getLocationOnScreen(frame_position);
draggable.setPadding(
(int) me.getRawX()-frame_position[0]-(draggable.getDrawable().getIntrinsicWidth()/2),
(int) me.getRawY()-frame_position[1]-(draggable.getDrawable().getIntrinsicHeight()/2),
0, 0);
draggable.invalidate();
checkOnDrop(me);
}
return true;
}
}
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
if (state == DRAGGING) {
Log.d(TAG, "already have an object moving... ?");
return false;
}
FrameLayout frame = (FrameLayout) findViewById(R.id.drag_space);
int frame_position[] = new int[2];
frame.getLocationOnScreen(frame_position);
// setup everything for dragging
state = DRAGGING;
dragged_position = i;
draggable = new ImageView(this);
Bitmap bm = view.getDrawingCache();
draggable.setImageBitmap(bm);
draggable.setAlpha(150);
draggable.setScaleType(ImageView.ScaleType.CENTER);
draggable.setDrawingCacheEnabled(true);
draggable.setPadding((int) current_x-frame_position[0]-(bm.getWidth()/2), (int) current_y-frame_position[1]-(bm.getHeight()/2), 0, 0);
frame.setVisibility(View.VISIBLE);
frame.addView(draggable, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
return true;
}
}
精彩评论