Tuesday, January 13, 2015

Drag and Drop items between ListView, with OnDragListener on ListView cells

In last two post "Drag and Drop items between ListView" and "Improved Drag and Drop items between ListView", it drag a ListView item, drop on a LinearLayout, then add the item to the associated ListView, always on bottom. Not drop on ListView actually.

In this exercise, another OnDragListener (ItemOnDragListener) implemented for rowView of ListView items, such that we can actually drop on ListView items, and insert in the previous position.


MainActivity.java
package com.example.androidimageviewlist;

import java.util.ArrayList;
import java.util.List;

import android.support.v7.app.ActionBarActivity;
import android.text.method.ScrollingMovementMethod;
import android.app.Activity;
import android.content.ClipData;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.DragShadowBuilder;
import android.view.View.OnDragListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity {

 //items stored in ListView
 public class Item {
  Drawable ItemDrawable;
  String ItemString;
  Item(Drawable drawable, String t){
   ItemDrawable = drawable;
   ItemString = t;
  }
 }
 
 //objects passed in Drag and Drop operation
 class PassObject{
  View view;
  Item item;
  List<Item> srcList;
  
  PassObject(View v, Item i, List<Item> s){
   view = v;
   item = i;
   srcList = s;
  }
 }
 
 static class ViewHolder {
  ImageView icon;
  TextView text; 
 }

 public class ItemsListAdapter extends BaseAdapter {
  
  private Context context;
  private List<Item> list;

  ItemsListAdapter(Context c, List<Item> l){
   context = c;
   list = l;
  }

  @Override
  public int getCount() {
   return list.size();
  }

  @Override
  public Object getItem(int position) {
   return list.get(position);
  }

  @Override
  public long getItemId(int position) {
   return position;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   View rowView = convertView;
   
      // reuse views
      if (rowView == null) {
       LayoutInflater inflater = ((Activity) context).getLayoutInflater();
       rowView = inflater.inflate(R.layout.row, null);

       ViewHolder viewHolder = new ViewHolder();
       viewHolder.icon = (ImageView) rowView.findViewById(R.id.rowImageView);
       viewHolder.text = (TextView) rowView.findViewById(R.id.rowTextView);
       rowView.setTag(viewHolder); 
      }

      ViewHolder holder = (ViewHolder) rowView.getTag();
      holder.icon.setImageDrawable(list.get(position).ItemDrawable);
      holder.text.setText(list.get(position).ItemString);
      
      rowView.setOnDragListener(new ItemOnDragListener(list.get(position)));

      return rowView;
  }
  
  public List<Item> getList(){
   return list;
  }
 }

 List<Item> items1, items2, items3;
 ListView listView1, listView2, listView3;
 ItemsListAdapter myItemsListAdapter1, myItemsListAdapter2, myItemsListAdapter3;
 LinearLayoutListView area1, area2, area3;
 TextView prompt;
 
 //Used to resume original color in drop ended/exited
 int resumeColor;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  listView1 = (ListView)findViewById(R.id.listview1);
  listView2 = (ListView)findViewById(R.id.listview2);
  listView3 = (ListView)findViewById(R.id.listview3);
  
  area1 = (LinearLayoutListView)findViewById(R.id.pane1);
  area2 = (LinearLayoutListView)findViewById(R.id.pane2);
  area3 = (LinearLayoutListView)findViewById(R.id.pane3);
  area1.setOnDragListener(myOnDragListener);
  area2.setOnDragListener(myOnDragListener);
  area3.setOnDragListener(myOnDragListener);
  area1.setListView(listView1);
  area2.setListView(listView2);
  area3.setListView(listView3);
  
  initItems();
  myItemsListAdapter1 = new ItemsListAdapter(this, items1);
  myItemsListAdapter2 = new ItemsListAdapter(this, items2);
  myItemsListAdapter3 = new ItemsListAdapter(this, items3);
  listView1.setAdapter(myItemsListAdapter1);
  listView2.setAdapter(myItemsListAdapter2);
  listView3.setAdapter(myItemsListAdapter3);
  
  /*
  //Auto scroll to end of ListView
  listView1.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
  listView2.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
  listView3.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
  */
  
  listView1.setOnItemClickListener(listOnItemClickListener);
  listView2.setOnItemClickListener(listOnItemClickListener);
  listView3.setOnItemClickListener(listOnItemClickListener);
  
  listView1.setOnItemLongClickListener(myOnItemLongClickListener);
  listView2.setOnItemLongClickListener(myOnItemLongClickListener);
  listView3.setOnItemLongClickListener(myOnItemLongClickListener);
  
  prompt = (TextView) findViewById(R.id.prompt);
  // make TextView scrollable
  prompt.setMovementMethod(new ScrollingMovementMethod());
  //clear prompt area if LongClick
  prompt.setOnLongClickListener(new OnLongClickListener(){
   
   @Override
   public boolean onLongClick(View v) {
    prompt.setText("");
    return true; 
   }});
  
  resumeColor  = getResources().getColor(android.R.color.background_light);

 }
 
 OnItemLongClickListener myOnItemLongClickListener = new OnItemLongClickListener(){

  @Override
  public boolean onItemLongClick(AdapterView<?> parent, View view,
    int position, long id) {
   Item selectedItem = (Item)(parent.getItemAtPosition(position));
   
   ItemsListAdapter associatedAdapter = (ItemsListAdapter)(parent.getAdapter());
      List<Item> associatedList = associatedAdapter.getList();
   
   PassObject passObj = new PassObject(view, selectedItem, associatedList);

   ClipData data = ClipData.newPlainText("", "");
   DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
   view.startDrag(data, shadowBuilder, passObj, 0);
   
   return true;
  }
  
 };
 
 OnDragListener myOnDragListener = new OnDragListener() {

  @Override
  public boolean onDrag(View v, DragEvent event) {
   String area;
   if(v == area1){
    area = "area1"; 
   }else if(v == area2){
    area = "area2"; 
   }else if(v == area3){
    area = "area3"; 
   }else{
    area = "unknown"; 
   }
   
   switch (event.getAction()) {
    case DragEvent.ACTION_DRAG_STARTED:
     prompt.append("ACTION_DRAG_STARTED: " + area  + "\n");
     break; 
    case DragEvent.ACTION_DRAG_ENTERED:
     prompt.append("ACTION_DRAG_ENTERED: " + area  + "\n");
     break; 
    case DragEvent.ACTION_DRAG_EXITED:
     prompt.append("ACTION_DRAG_EXITED: " + area  + "\n");
     break; 
    case DragEvent.ACTION_DROP:
     prompt.append("ACTION_DROP: " + area  + "\n");

     PassObject passObj = (PassObject)event.getLocalState();
     View view = passObj.view;
     Item passedItem = passObj.item;
     List<Item> srcList = passObj.srcList;
     ListView oldParent = (ListView)view.getParent();
     ItemsListAdapter srcAdapter = (ItemsListAdapter)(oldParent.getAdapter());
     
     LinearLayoutListView newParent = (LinearLayoutListView)v;
     ItemsListAdapter destAdapter = (ItemsListAdapter)(newParent.listView.getAdapter());
        List<Item> destList = destAdapter.getList();
     
     if(removeItemToList(srcList, passedItem)){
      addItemToList(destList, passedItem);
     }
     
     srcAdapter.notifyDataSetChanged();
     destAdapter.notifyDataSetChanged();
     
     //smooth scroll to bottom
     newParent.listView.smoothScrollToPosition(destAdapter.getCount()-1);
     
     break;
      case DragEvent.ACTION_DRAG_ENDED:
       prompt.append("ACTION_DRAG_ENDED: " + area  + "\n");  
      default:
       break;    
   }
      
   return true;
  }
  
 };
 
 class ItemOnDragListener implements OnDragListener{
  
  Item  me;
  
  ItemOnDragListener(Item i){
   me = i;
  }

  @Override
  public boolean onDrag(View v, DragEvent event) {
   switch (event.getAction()) {
   case DragEvent.ACTION_DRAG_STARTED:
    prompt.append("Item ACTION_DRAG_STARTED: " + "\n");
    break; 
   case DragEvent.ACTION_DRAG_ENTERED:
    prompt.append("Item ACTION_DRAG_ENTERED: " + "\n");
    v.setBackgroundColor(0x30000000);
    break; 
   case DragEvent.ACTION_DRAG_EXITED:
    prompt.append("Item ACTION_DRAG_EXITED: " + "\n");
    v.setBackgroundColor(resumeColor);
    break; 
   case DragEvent.ACTION_DROP:
    prompt.append("Item ACTION_DROP: " + "\n");

    PassObject passObj = (PassObject)event.getLocalState();
    View view = passObj.view;
    Item passedItem = passObj.item;
    List<Item> srcList = passObj.srcList;
    ListView oldParent = (ListView)view.getParent();
    ItemsListAdapter srcAdapter = (ItemsListAdapter)(oldParent.getAdapter());
    
    ListView newParent = (ListView)v.getParent();
    ItemsListAdapter destAdapter = (ItemsListAdapter)(newParent.getAdapter());
    List<Item> destList = destAdapter.getList();
    
    int removeLocation = srcList.indexOf(passedItem);
    int insertLocation = destList.indexOf(me);
    /*
     * If drag and drop on the same list, same position,
     * ignore
     */
    if(srcList != destList || removeLocation != insertLocation){
     if(removeItemToList(srcList, passedItem)){
      destList.add(insertLocation, passedItem);
     }
     
     srcAdapter.notifyDataSetChanged();
     destAdapter.notifyDataSetChanged();
    }

    v.setBackgroundColor(resumeColor);
    
    break;
     case DragEvent.ACTION_DRAG_ENDED:
      prompt.append("Item ACTION_DRAG_ENDED: "  + "\n");
      v.setBackgroundColor(resumeColor);
     default:
      break;    
  }
     
  return true;
  }
  
 }
 
 OnItemClickListener listOnItemClickListener = new OnItemClickListener(){

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position,
    long id) {
   Toast.makeText(MainActivity.this, 
     ((Item)(parent.getItemAtPosition(position))).ItemString, 
     Toast.LENGTH_SHORT).show();
  }
  
 };

 private void initItems(){
  items1 = new ArrayList<Item>();
  items2 = new ArrayList<Item>();
  items3 = new ArrayList<Item>();
  
  TypedArray arrayDrawable = getResources().obtainTypedArray(R.array.resicon);
  TypedArray arrayText = getResources().obtainTypedArray(R.array.restext);
  
  for(int i=0; i<arrayDrawable.length(); i++){
   Drawable d = arrayDrawable.getDrawable(i);
   String s = arrayText.getString(i);
   Item item = new Item(d, s);
   items1.add(item);
  }
  
  arrayDrawable.recycle();
  arrayText.recycle();
 }
 
 private boolean removeItemToList(List<Item> l, Item it){
  boolean result = l.remove(it);
  return result;
 }
 
 private boolean addItemToList(List<Item> l, Item it){
  boolean result = l.add(it);
  return result;
 }

}

LinearLayoutListView.java and the layout file, /res/layout/activity_main.xml, refer to last post.

/res/layout/row.xml and /res/values/arrays.xml, refer to previous post of "Custom ListView with ImageView".

To use Drag and Drop on your app, you have to modify AndroidManifest.xml to set android:minSdkVersion="11".



download filesDownload the files.

Next:
Drag and Drop between ListView and GridView

8 comments:

manu said...

downlaod link is not working can u give another link

Anonymous said...

sites.google.com/site/androidexercise5/download/AndroidImageViewList_20150113a.zip

kurukshetran said...

Hi, how to implement this drag and drop between table rows. Can this same principle can be applied.

Anonymous said...

not scrolling automatically at bottom !

Ekta said...

this one the best tutorial on google i ever found thank you so much it reaallly works... :)

Zacharie ALES said...

Thank you for the tutorial.

To make it clear for the user that the row is added before the highlighted row I replaced
"v.setBackgroundColor(0x30000000);"
by
int[] colors = {0x30000000, 0, 0};
v.setBackground(new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors));

Zacharie ALES said...

Is it possible to drag thanks to a short click instead of a long one?

I tried to useOnItemClickListener instead of OnItemLongClickListener but in that case, nothing happened when I click.

Zacharie ALES said...

Ok I just answered my own question with a little delay. You can replace the onItemLongClickListener by onTouchListener. However, in that case, you don't know the position and the view associated with the selected row.

If "lv" is your ListView you can use the following to get them:

Rect rect = new Rect();
int childCount = lv.getChildCount();
int[] listViewCoords = new int[2];
lv.getLocationOnScreen(listViewCoords);
int x = (int) event.getRawX() - listViewCoords[0];
int y = (int) event.getRawY() - listViewCoords[1];
View child;
View myRowView = null;
int myPosition = 0;

while (myPosition < lv.getAdapter().getCount() && myRowView == null) {
child = lv.getChildAt(myPosition);
child.getHitRect(rect);
if (rect.contains(x, y))
myRowView = child;
else
myPosition++;
}

// If the selected row has been found
if (myRowView != null) {
...
}