ExpandableListView でドラッグして並び替え

”kurukuru-papaの日記”の以下のページを拝見させていただき、
  「ListViewの項目をドラッグして並び替え可能にしてみた」
   http://d.hatena.ne.jp/kurukuru-papa/20120519/1337428346

Cool と思い、ならば、ExpandableListView でドラッグして並び替えを作ってみたくなりました。

Expand(拡げた状態)でのドラッグは操作性が悪いので、ドラッグを開始時=長押し時に全て折りたたみ
状態にして動かすことにしました。
また、Adapter は抽象化し、ドラッグ中のリスト項目の描画も色を外からデフォルト以外で指定できるように
した他は、 kurukuru-papa さんのソースを引用させて頂きました。

kurukuru-papa さんに感謝します。

コードを全てここに載せるには、量が多くて厳しいので一部になります。

Group の行は、行のタイトル表示を Map から表現する前提で、List<Map<String,String>>
各Group の リストの表示データは、List<List<Map<String,String>>> であるとします。

ExpandableListView にセットする Adapter を抽象化します。
( kurukuru-papa さんのソースが非常に参考になり感謝です。)

public abstract class DragExpandableListAdapter extends BaseExpandableListAdapter {
   private int currentPosition = -1;
   private boolean mIsChanged = false;

   /** Group 行表示 View の取得 */
   protected abstract View getExpressView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent);

   /** Group 行表示の為の List の取得 */
   protected abstract List getGroups();

   /** Group - 入れ子 Child リストの取得 */
   protected abstract List getChilds();

   /** ドラッグ後の コールバック */
   protected abstract void doDraged(boolean isChanged);

   /** ドラッグ開始時に全て、Expandされた行を閉じる */
   protected abstract void allCollapse();

   @Override
   public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent){
      View view = getExpressView(groupPosition, isExpanded, convertView, parent);
      if (groupPosition==currentPosition){
         view.setVisibility(View.INVISIBLE);
      } else {
         view.setVisibility(View.VISIBLE);
      }
      return view;
   }
   public void startDrag(int position){
      allCollapse();
      mIsChanged = false;

      this.currentPosition = position;
   }
   public int getDragBackgroundColor(){
      return  Color.argb(128, 0xFF, 0xFF, 0xFF);
   }

   @SuppressWarnings("unchecked")
   public void doDrag(int newPosition){
      List glist = getGroups();
      List clist = getChilds();
      Object group = glist.get(currentPosition);
      Object child = clist.get(currentPosition);
      if (currentPosition < newPosition){
         // リスト項目を下に移動している場合
         for (int i = currentPosition; i < newPosition;i++){
            glist.set(i, glist.get(i+1));
            clist.set(i, clist.get(i+1));
         }
         mIsChanged = true;
      }else if(currentPosition > newPosition){
         // リスト項目を上に移動している場合
         for (int i = currentPosition; i > newPosition;i--){
            glist.set(i, glist.get(i-1));
            clist.set(i, clist.get(i-1));
         }
         mIsChanged = true;
      }
      glist.set(newPosition, group);
      clist.set(newPosition, child);
      currentPosition = newPosition;
   }
   public void stopDrag(){
      this.currentPosition = -1;
      doDraged(mIsChanged);
   }
}

public class DragExpandableListView extends ExpandableListView は、
setAdapter の実装で、DragExpandableListAdapter にキャストして使用させます。
kurukuru-papa さんに書いて頂いた PopupView に、ドラッグ中の色の指定を指定する
  protected void setDragBackgroundColor(int argb)
を追加します。

注意が必要なのは、Expandable リストであるが為に拡げた時のポジションがそのままドラッグの
メソッドが実行されてしまうので、DragExpandableListView での DragExpandableListAdapter の
doDrag メソッド実行で、List の IndexOutOfBoundsException  が発生するのを捕捉して
その場合、ドラッグさせないようにする。


→ DragExpandableListView の中で、以下のようにする。

   private boolean doDrag(MotionEvent event){
      if (!dragging){
         return false;
      }
      int x = (int)event.getX();
      int y = (int)event.getY();
      int position = pointToPosition(x, y);
      // ドラッグの移動先リスト項目が存在する場合
      if (position != AdapterView.INVALID_POSITION) {
         // アダプターのデータを並び替える
         try{
         adapter.doDrag(position);
         }catch(Exception e){
            return false;
         }

      }
      popupView.doDrag(x, y);
      invalidateViews();
      setScroll(event);
      return true;
   }

使う時は、

DragExpandableListView expandableListView;


expandableListView = (DragExpandableListView)findViewById(R.id.list);
expandableListView.setAdapter(new DragExpandableListAdapter(){
   LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   // for Group-row express
   @Override
   public View getExpressView(int groupPosition,boolean isExpanded,View convertView,ViewGroup parent){
      View view = convertView==null ? layoutInflater.inflate(R.layout.expand_group_1,null) : convertView;
      *1.setText(groupList.get(groupPosition).get("title"));
      view.setBackgroundDrawable(getResources().getDrawable(R.drawable.list_selector_group_1));
      return view;
   }
      :
   //-- 省略 --- 
      :
   @Override
   protected List getGroups(){
      return groupList;
   }
   @Override
   protected void allCollapse(){
      for(int i=0;i < groupList.size();i++){
         expandableListView.collapseGroup(i);
      }
   }
   @Override
   protected List getChilds(){
      return dataList;
   }
   // ドラッグ後の コールバック
   @SuppressWarnings("unchecked")
   @Override
   protected void doDraged(boolean isChanged){
      if (isChanged){
         StringBuffer sb = new StringBuffer();
         List<Map<String,String>> list = getGroups();
         for(Map<String,String> map : list){
            sb.append(map.get("title")+"\n");
         }
         new AlertDialog.Builder(DragExpandListSampleActivity.this)
         .setMessage(sb.toString())
         .setPositiveButton("OK",new DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialog,int which){
            }
         })
         .create().show();
      }
   }
});

*1:TextView)view.findViewById(R.id.groupTextView