GridViewをグリグリ動かせるライブラリDynamicGridViewの使い方

GridViewでView同士をグリグリ動かせるDynamicGridViewというオープンソースライブラリがあります。
使い方や実装を紹介します。

作者によるデモ：
https://www.youtube.com/watch?v=zlzNvxksIfY

##使い方
ソースコードは以下にあります。
<https://github.com/askerov/DynamicGrid>

### プロジェクトへの導入方法
#### Gradleの場合


DynamicGridのプロジェクトを利用したいプロジェクト内の適当なところにおいて、メインプロジェクトのbuild.gradle内dependenciesに以下を追記します。


```
    dependencies {
        compile project(':dynamicgrid')  //プロジェクト直下にdynamicGridプロジェクトを置いた場合
    }
```

#### Eclipseの場合

  1.DynamicGridのソースコード(src/org/askerov/dynamicgrid/配下, res/values/配下)を自分のプロジェクト内srcやres内に配置します。  
  2.各javaファイルのpackage名を自分のプロジェクトのものに修正します。
    
```java

package com.tomoima.testapp.ui;  // /com/tomoima/testapp/ui配下に配置している

    import java.util.ArrayList;
    //(中略)
public class DynamicGridView extends GridView {
　　//(略)
}
```


### 使い方
BaseDynamicGridAdapterを継承したCustomAdapterを作ります。

```java

public class myAdapter extends BaseDynamicGridAdapter {

    public myAdapter(Context context, List<?> items, int columnCount) {
        super(context, items, columnCount);
       
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // ViewHolderの定義
        myViewHolder holder;
        
        //viewは使いまわすようにする
        if (convertView == null){
            convertView = LayoutInflater.from(getContext())
                    .inflate(R.layout.item_grid, null);
            holder = new myViewHolder();
            holder.imageView = (ImageView) convertView.findViewById(R.id.item_img);
            holder.textView = (TextView) convertView.findViewById(R.id.item_text);
            convertView.setTag(holder);
        } else {
            holder = (myViewHolder) convertView.getTag();
        }
        //ViewHolderについたimageView, textViewへの処理
        int resId = (Integer) getItem(position);
        holder.imageView.setImageResource(resId);
        holder.textView.setText(resId);
        return convertView;
    }
    
    private class myViewHolder {
        private ImageView imageView;
        private TextView textView;
    }
    

}
```

作ったCustomAdapterをActivityで呼んであげます

```java

public class MyActivity extends Activity {


    private DynamicGridView gridView;
    
    private int[] myList = {
            R.id.hoge,
            R.id.moge,
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_grid);
        
        ArrayList<Integer> myArrayList = new ArrayList<Integer>(); 
        
        
        gridView = (DynamicGridView) findViewById(R.id.dynamic_grid);
        gridView.setAdapter(new myAdapter( this, myArrayList, 4)); //gridViewのカラム数定義
        
        //Viewから指を離すとドラッグモードが停止します
        gridView.setOnDropListener(new DynamicGridView.OnDropListener()
        {
            @Override
            public void onActionDrop()
            {
                gridView.stopEditMode();
            }
        });
        
        
        gridView.setOnDragListener(new DynamicGridView.OnDragListener() {
            @Override
            public void onDragStarted(int position) {
            //ドラッグ開始位置の取得ができます
                Log.d(TAG, "drag started at position " + position);
            }

            @Override
            public void onDragPositionsChanged(int oldPosition, int newPosition) {
            //ドラッグした前後の位置情報を取得できます
                Log.d(TAG, String.format("drag item position changed from %d to %d", oldPosition, newPosition));
            }

            @Override
            public boolean onDrag(View v, DragEvent event) {
                // TODO Auto-generated method stub
                return false;
            }
        });
        
        //Viewの長押しでドラッグモードになります
        gridView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                gridView.startEditMode(position);
                return true;
            }
        });

        //Viewクリックでposition情報が取得できます
        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(MyActivity.this, parent.getAdapter().getItem(position).toString(),
                        Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onBackPressed() {
        if (gridView.isEditMode()) {
            gridView.stopEditMode();
        } else {
            super.onBackPressed();
        }
    }
}
    
```

GridViewのカラム数は
DynamicGridView#setAdapterとR.layout.activity_grid内で定義します。

activiy_grid.xml

```xml


<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <com.tomoima.testapp.DynamicGridView
            android:id="@+id/dynamic_grid"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:numColumns="4"  <-これ
            />

</LinearLayout>

```

### 仕組み
DynamicGridのキモはViewポジションの管理にあります。ソースコードから流れを追いたいと思います。

AbstractDynamicGridAdapterを見ると、mIdMapというhashMapがあります。

```java

private HashMap<Object, Integer> mIdMap = new HashMap<Object, Integer>();

```

このhashMapではObjectをキーに、初期ポジションが管理されています。

BaseDynamicGridAdapterでコンストラクタが作られると、AbstractDynamicGridAdapter#addStableIdが呼ばれ、object のarraylistであるmItemsのitem情報ががmIdMapに格納されます。addStableIdというメソッド名からわかるとおり、初期値(固定値)のId順序を格納します。

```java

protected void addStableId(Object item) {
        mIdMap.put(item, nextStableId++);
}
```


ドラッグ＆ドロップによりViewを移動すると、DynamicGridView#handleCellSwitch()が呼ばれます。
内部ではgetViewForId()を介してAbstractDynamicGridAdapter#getItemID()が呼ばれ、itemをキーに最初のposition(originalPosition)が呼ばれます。

```java

public final long getItemId(int position) {
        if (position < 0 || position >= mIdMap.size()) {
            return INVALID_ID;
        }
        Object item = getItem(position);
        return mIdMap.get(item); //item情報をキーとして最初のpositionを取得
}
```

これと移動後のposition(targetPosition)を引数としてDynamicGridView#reorderElementから呼び出されるDynamicGridUtils#reorder を実行することで、BaseDynamicGridで管理されているmItemsの順番を更新します。

```java

public static void reorder(ArrayList list, int indexFrom, int indexTwo) {
        Object obj = list.remove(indexFrom);
        list.add(indexTwo, obj); //targetの位置にoriginalのpositionを移動
}
```

このように、mMapIdの中で初期のpositionを保持することでViewで実際に見えるpositionの整合性が保たれるようになります。

工夫次第で特定のViewだけ動かさないようにしたり、Viewの削除、追加も行うことができます。

###注意点
自分が確認した限りだと、このライブラリは結構頻繁に更新があるようです。利用する際は必ず現状のソースコードがどうなっているか確認することをおすすめします。