Scroll gridview while dragging item (as shadow)
Asked Answered
H

2

10

I am developing for api > 14 and I have implemented drag/drop functionality on a GridView.

However, while dragging items to the top or bottom of the screen the GridView does not scroll.

I would like to have it scroll in the direction you drag an item getting faster as you move to the edge and slow as you move the item back towards the vertical center of the screen.

Surely this sort of functionality should be standard in any drag-n-drop?

Anyway, does anyone know the best way to approach this?

UPDATE: I can't actually find a single example on the internet that even attempts to scroll with a dragged grid item (am I missing something here?) let alone solve my problem.

How do you get the draggable item (that appears as a shadow) to scroll the gridview? I am assigning the drag as follows:

int gridChildPosition = position - mGridView.getFirstVisiblePosition();                     
ViewFlipper gridItem = (ViewFlipper) mGridView.getChildAt(gridChildPosition);

ClipData data = ClipData.newPlainText("", "");
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(gridItem);

if (mGridView.startDrag(data, shadowBuilder, gridItem, 0)) {

    int lastVisiblePosition = mGridView.getLastVisiblePosition() - mGridView.getFirstVisiblePosition();
    for (int i=0; i<=lastVisiblePosition; i++) {
        if (i == gridChildPosition) {
            continue;
        }
        mGridView.getChildAt(i).setOnDragListener(new GridItemDragListener(position));
        mGridView.getChildAt(i).setAlpha(0.4f);
    }
}

Above code creates the draggable shadow and all other items currently get dimmed. But I'm unsure how I can get the shadow to scroll the gridview.

Henpeck answered 22/4, 2013 at 19:0 Comment(3)
Check out the answer here: #12236535Mongolic
@Mongolic Thanks, already seen this. No explanation for the on drag scroll.Henpeck
You can check out the DragDrop tutorial blahti.wordpress.com/2011/10/03/drag-drop-for-android-gridview which may help you out.Ikhnaton
B
7

The cool thing about Drag Events is that you can listen for them in the parent ViewGroup too. Extend GridView, and override onDragEvent to monitor DragEvent.ACTION_DRAG_LOCATION. In there, get the Y coordinates (event.getY()) of the drag. Make sure to return true from ACTION_DRAG_STARTED, or you won't receive the event in ACTION_DRAG_LOCATION.

Determine your "hit boxes", perhaps using the technique that jaibatrik suggests, or simply using a percentage of the GridView's measured height.

It would probably make most sense to use smoothScrollByOffset(int offset), this way you can implement stepped/exponential scroll. Meaning, the longer the user keeps the dragged item in the hit box, the larger the offset.

Sounds like a cool idea for an open source library/component. ;-)

EDIT:

Here's an example of how you could do this:

import android.app.Activity;
import android.content.ClipData;
import android.content.ClipData.Item;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.DragEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.GridView;

public class GridDragActivity extends Activity implements OnItemLongClickListener {

    private static final String TAG = "GridDragActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        MyGridView gridView = new MyGridView(this);
        gridView.setNumColumns(3);
        gridView.setOnItemLongClickListener(this);
        gridView.setAdapter(new ArrayAdapter<String>(
                this,
                android.R.layout.simple_list_item_1,
                COUNTRIES));

        setContentView(gridView);
    }

    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
            long id) {

        ClipData data = ClipData.newPlainText((String) view.getTag(),
                String.valueOf(position));
        View.DragShadowBuilder shadow = new View.DragShadowBuilder(view);
        view.startDrag(data, shadow, null, 0);

        return true;
    }

    class MyGridView extends GridView {

        private static final int THRESHHOLD = 200;
        private static final int OFFSET = 10;

        public MyGridView(Context context) {
            super(context);
        }

        @Override
        public boolean onDragEvent(DragEvent event) {

            int height = getMeasuredHeight();

            switch (event.getAction()) {
                case DragEvent.ACTION_DRAG_STARTED:
                    return true;

                case DragEvent.ACTION_DRAG_LOCATION:

                    float y = event.getY();
                    if (height - y < THRESHHOLD) {
                        smoothScrollByOffset(OFFSET);
                    } else if (height - y > height - THRESHHOLD) {
                        smoothScrollByOffset(-OFFSET);
                    }

                    return true;

                case DragEvent.ACTION_DROP:

                    ClipData data = event.getClipData();
                    Item item = data.getItemAt(0);
                    int start = Integer.valueOf((String) item.getText());
                    int end = pointToPosition((int) event.getX(), (int) event.getY());

                    Log.i(TAG, "DROP started at = " + start + ", ended at = " + end);

                    return true;
            }

            return super.onDragEvent(event);
        }
    }

    static final String[] COUNTRIES = new String[] {
        "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
        "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina",
        "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan",
        "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium",
        "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia",
        "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil",
        "British Indian Ocean Territory",
        "British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burundi",
        "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
        "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
        "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
        "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
        "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica",
        "Dominican Republic",
        "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
        "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
        "Former Yugoslav Republic of Macedonia", "France", "French Guiana", "French Polynesia",
        "French Southern Territories", "Gabon", "Georgia", "Germany", "Ghana", "Gibraltar",
        "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea",
        "Guinea-Bissau",
        "Guyana", "Haiti", "Heard Island and McDonald Islands", "Honduras", "Hong Kong",
        "Hungary"
    };
}
Brochure answered 2/5, 2013 at 23:6 Comment(2)
I will have play around with what you suggest.Henpeck
Thanks you, I will need some time to look at this as I have been diverted. However, I will still honour the bounty if this solution is helpful.Henpeck
E
1

I think you need to use the methods smoothScrollToPosition(int position) or smoothScrollByOffset(int offset) of GridView. Take two rectangles at the top and bottom of the screen. If the touch event is received on thos rectangles while dragging then scroll the GridView. You can get the last visible position at any given time using the getLastVisiblePosition() method.

Egotism answered 22/4, 2013 at 20:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.