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名を自分のプロジェクトのものに修正します。
package com.tomoima.testapp.ui; // /com/tomoima/testapp/ui配下に配置している
import java.util.ArrayList;
//(中略)
public class DynamicGridView extends GridView {
//(略)
}
使い方
BaseDynamicGridAdapterを継承したCustomAdapterを作ります。
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で呼んであげます
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 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があります。
private HashMap<Object, Integer> mIdMap = new HashMap<Object, Integer>();
このhashMapではObjectをキーに、初期ポジションが管理されています。
BaseDynamicGridAdapterでコンストラクタが作られると、AbstractDynamicGridAdapter#addStableIdが呼ばれ、object のarraylistであるmItemsのitem情報ががmIdMapに格納されます。addStableIdというメソッド名からわかるとおり、初期値(固定値)のId順序を格納します。
protected void addStableId(Object item) {
mIdMap.put(item, nextStableId++);
}
ドラッグ&ドロップによりViewを移動すると、DynamicGridView#handleCellSwitch()が呼ばれます。
内部ではgetViewForId()を介してAbstractDynamicGridAdapter#getItemID()が呼ばれ、itemをキーに最初のposition(originalPosition)が呼ばれます。
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の順番を更新します。
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の削除、追加も行うことができます。
注意点
自分が確認した限りだと、このライブラリは結構頻繁に更新があるようです。利用する際は必ず現状のソースコードがどうなっているか確認することをおすすめします。