kurukuru-papaのブログ

主に、ソフトウェア開発に関連したメモを書き溜めたいと思います。

アプリポケット Ver.0.4.1公開

Android用ランチャーアプリ「アプリポケット」のVersion 0.4.1を公開しました。変更内容は下記です。

概要

次の機能を改善しました。Google Play ストアのコメント欄で頂いていたご要望になります。

  1. メイン画面のアイコンタイトルについて、文字サイズを選択できるようにしました。設定画面から選択して下さい。
  2. メイン画面で指定アプリ起動後に、当アプリを終了するオプションを用意しました。設定画面からオプションを有効にして下さい。

その他、細かい改善・不具合修正を行いました。

  1. メイン画面の実行中アプリタグにGmailを表示する際、アイコンタイトルが表示されない場合があったのを修正。

動作環境

  • 対象OS:Android 1.6以降
  • 動作確認端末:Xperia SO-01B

アプリポケット Ver.0.4.0公開

Android用ランチャーアプリ「アプリポケット」のVersion 0.4.0を公開しました。変更内容は下記です。

概要

次の機能を追加しました。

  1. 各画面の配色を選択できるようにしました。
  2. メイン画面を半透明に表示できるようにしました。
  3. メイン画面の見た目をダイアログのように表示できるようにしました。

その他、いくつか細かい改善・不具合修正を行いました。

  1. メイン画面を再表示時、初期状態で表示するようにしました。
  2. 実行中アプリの起動で失敗するアプリがあったのを改善しました。

配色(テーマ)

元々の配色を含めて、8種類の配色を用意しました。素人丸出しの拙い配色かもしれませんが、皆さんに、少しでも気に入る配色があることを願っています。配色は、設定画面の「テーマ」から選択して下さい。配色の名前とイメージサンプルは次になります。

配色の名前

  1. 青(前バージョンで使っていた配色です。デフォルトではこの配色としています。)
  2. ナチュラル・グリーン
  3. 灼熱の赤
  4. やさしいピンク
  5. 明るいグレー
  6. 濃い紺色
  7. 濃い茶色
  8. 濃い紫色

配色イメージサンプル(上記配色名と同じ順序で配置)

半透明

メイン画面の透明度を5段階で選択できるようにしました。デフォルトでは透過しない設定にしています。透過したい場合は、設定画面の「透明度」から選択して下さい。透明度は、配色(テーマ)と合わせて設定することが出来ます。

透明度

  1. 透過しない(デフォルトではこの設定です)
  2. 透明(小)
  3. 透明(中)
  4. 透明(大)
  5. 透明

透明度イメージサンプル(上記の順で配置。ただし、「透過しない」は含めていません。配色(テーマ)は「青」を使っています。)

ダイアログ表示

メイン画面をダイアログのようにポップアップ表示できるようにしました。配色(テーマ)と合わせて設定することが出来ます。ただし、ダイアログ表示にすると、半透明の設定は無効化されます。

ダイアログ表示イメージサンプル

動作環境

  • 対象OS:Android 1.6以降
  • 動作確認端末:Xperia SO-01B

ユーザ操作に応じて全画面表示/通常表示を切り替える方法

前の記事に引き続き、Androidアプリで全画面表示する方法を書きます。今回は、ユーザの操作に従って、全画面表示と通常表示を切り替えてみました。

画面イメージ

作成した画面のキャプチャを以下に貼付けました。画面では、ラジオボタンを用意して、ユーザが自由に、全画面表示(タイトルバーなし)や通常表示(タイトルバーあり)を切り替えられるようにしています。

「何もしない」を選ぶと、次回の画面表示時に、タイトルバーなし&ステータスバーありの表示を行います。「タイトルバーなし」を選ぶと、タイトルバーなし&ステータスバーなしを表示します。

「テーマでタイトルバーあり」を選ぶと、Androidのテーマ機能を利用してタイトルバーを表示します。「テーマでタイトルバーなし」ではテーマ機能でタイトルバーを非表示にします。「テーマ+カスタムタイトル」ではテーマ機能とJava側処理でアイコン付きのタイトルバーを表示します。

AndroidManifest.xml

AndroidManifest.xmlのactivityタグの定義を抜粋しました。ポイントは、android:theme属性で、タイトルバーなし(@android:style/Theme.NoTitleBar)を設定している事です。これがないと、アクティビティ表示時に一瞬タイトルバーが見えてしまいました。

        <activity
            android:name=".TryAndroidUI002Activity"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.NoTitleBar" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

アクティビティクラス

ユーザのラジオボタン指定に従って、タイトルバーやステータスバーの表示/非表示を切り替えたり、カスタムタイトルバーの設定をしたりしています。各処理は、各々決まったタイミングで処理しなければならないので、煩雑な実装になっています。ユーザのラジオボタン操作は、プリファレンスに登録し、次回画面表示時に読み込んでいます。スリープ処理は、「一瞬タイトルバーが表示される」現象が起きない事を確認するための処理です。もし間違った実装であれば、スリープ時の間、タイトルバーが見えてしまうはずです。

public class TryAndroidUI002Activity extends Activity implements
		OnCheckedChangeListener {
	private static final String KEY_VIEW_TYPE = "view_type";

	private RadioGroup radioGroup;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		// 設定を取得する
		SharedPreferences sp = getPreferences(MODE_PRIVATE);
		int viewType = sp.getInt(KEY_VIEW_TYPE, 0);

		// テーマを設定する
		// 親クラスのonCreate()呼び出し前に設定すること
		switch (viewType) {
		case R.id.themeTitleRadioButton:
			// テーマを使ってタイトルバーあり
			setTheme(R.style.CustomTheme_Theme001);
			break;
		case R.id.themeNoTitleRadioButton:
			// テーマを使ってタイトルバーなし
			setTheme(R.style.CustomTheme_Theme001_NoTitle);
			break;
		case R.id.customTitleRadioButton:
			// タイトルバーありテーマ&カスタムタイトルバー
			setTheme(R.style.CustomTheme_Theme001);
			break;
		}

		// テーマ設定後に親クラスのonCreate()を呼ぶ
		// テーマ設定前に呼ぶと一部テーマが設定されなかったりする
		sleep(1000);
		super.onCreate(savedInstanceState);

		// タイトルバーの形式を指示する
		// setContentView()呼び出し前に実施すること
		switch (viewType) {
		case R.id.noTitleRadioButton:
			// タイトルバーなし
			getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
			requestWindowFeature(Window.FEATURE_NO_TITLE);
			break;
		case R.id.customTitleRadioButton:
			// カスタムタイトルバー
			requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
			break;
		}

		// レイアウト設定
		setContentView(R.layout.main);

		// カスタムタイトルバーのレイアウトを設定する
		switch (viewType) {
		case R.id.customTitleRadioButton:
			getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,
					R.layout.custom_title);
			break;
		}

		// ラジオボタン設定
		radioGroup = (RadioGroup) findViewById(R.id.radioGroup);
		radioGroup.check(viewType);
		radioGroup.setOnCheckedChangeListener(this);

		// コンテンツ設定
		TextView textView = (TextView) findViewById(R.id.textView1);
		textView.setText(getResources().getString(R.string.hello));
	}

	@Override
	public void onCheckedChanged(RadioGroup group, int checkedId) {
		SharedPreferences sp = getPreferences(MODE_PRIVATE);
		SharedPreferences.Editor editor = sp.edit();
		editor.putInt(KEY_VIEW_TYPE, checkedId);
		editor.commit();
	}

	private void sleep(long time) {
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			Log.d(getClass().getName(), getClass().getSimpleName()
					+ "#onCreate()," + e.getMessage());
		}
	}
}

レイアウトXML

画面のレイアウトでは、ユーザに操作してもらうラジオボタンを記述しています。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="次回起動時の表示設定" />

    <RadioGroup
        android:id="@+id/radioGroup"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <RadioButton
            android:id="@+id/nothingRadioButton"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="何もしない" />

        <RadioButton
            android:id="@+id/noTitleRadioButton"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="タイトルバーなし" />

        <RadioButton
            android:id="@+id/themeTitleRadioButton"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="テーマでタイトルバーあり" />

        <RadioButton
            android:id="@+id/themeNoTitleRadioButton"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="テーマでタイトルバーなし" />

        <RadioButton
            android:id="@+id/customTitleRadioButton"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="テーマ+カスタムタイトル" />
    </RadioGroup>

</LinearLayout>

custom_title.xml

カスタムタイトルバーの内容を定義しています。何となくアイコンを追加してみました。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical|left"
        android:src="@drawable/ic_launcher" >
    </ImageView>

    <TextView
        style="?android:attr/windowTitleStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="3dp"
        android:layout_weight="1"
        android:singleLine="true"
        android:text="@string/app_name" >
    </TextView>

</LinearLayout>

以上のようにして、タイトルバーやステータスバー、カスタムタイトルバーを、ユーザ操作によって、動的に切り替えるAndroidサンプルアプリを作る事ができました。

動作環境

Android OS: Version 2.1
端末: Xperia SO-01B

アクティビティを全画面表示する方法

Androidアプリで画面表示周りを作っていました。アクティビティを全画面表示すること自体は簡単に出来たのですが、全画面表示する際、一瞬だけタイトルバーが表示されていました。タイトルバーをまったく表示しないで、全画面表示する方法をメモしておきます。

概要

ここで言う「全画面表示」とは、アクティビティのタイトルバーを非表示にし、ステータスバー(アンテナ強度や時刻が表示されているエリア)を隠すことを指しています。実現方法は、大きく次の二通りがありました。

  1. AndroidManifest.xmlでテーマを設定する。
  2. アクティビティのonCreateメソッドで設定する。

AndroidManifest.xmlでテーマを設定する方法では、上述した一瞬だけタイトルバーが表示される現象は起きませんでした。アクティビティのonCreateメソッドで制御を行う場合だけ、一瞬タイトルバーが表示されました。

onCreateメソッドでタイトルバーの制御を行う場合、制御を行なっている途中でも、端末上では画面が表示されていました。このときの画面は、AndroidManifest.xmlで設定されている内容です。AndroidManifest.xmlでタイトルバーの設定を何も書いていなければ、onCreateメソッドの処理中、タイトルバー付きの画面が表示されました。onCreateメソッドでタイトルバーの制御を行う場合でも、AndroidManifest.xmlにタイトルバーの設定を行ったほうが良いようです。

AndroidManifest.xmlのテーマ設定で全画面表示する方法

画面イメージは次のようになります。さっぱりしすぎて分かりにくいのですが、タイトルバーもステータスバーもありません。

アクティビティに対するテーマの適用は、AndroidManifest.xmlで、activityタグのandroid:theme属性で行います。以下に抜粋しました。ここではAndroidで用意されている「Theme.NoTitleBar.Fullscreen」というテーマ(タイトルバーなし&全画面表示)を指定しています。

        <activity
            android:name=".ThemeNoTitleActivity"
            android:label="TryAndroidUI002 ThemeNoTitle"
            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

上記のAndroidManifest.xmlの指定だけで、アクティビティクラスで何もしなくても、全画面表示が出来ます。ですが、ここでは、アクティビティ表示時に、タイトルバーがまったく表示されないのを確認するため、アクティビティクラスにスリープ処理を追加しています。

public class ThemeNoTitleActivity extends Activity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		// 親クラスのonCreate()を呼ぶ前にスリープを入れて、表示状態を確認する。
		sleep(1000);
		super.onCreate(savedInstanceState);

		// レイアウト設定
		setContentView(R.layout.activity);

		// コンテンツ設定
		TextView textView = (TextView) findViewById(R.id.textView1);
		textView.setText(getResources().getString(R.string.hello));
	}

	private void sleep(long time) {
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			Log.d(getClass().getName(), getClass().getSimpleName()
					+ "#onCreate()," + e.getMessage());
		}
	}
}

AndroidManifest.xmlでテーマを利用した全画面表示の手順は、以上で終わりです。

アクティビティのonCreateメソッドで全画面表示する方法

画面イメージは次のようになります。こちらも、さっぱりしすぎて分かりにくいのですが、タイトルバーもステータスバーもありません。AndroidManifest.xmlでテーマを利用した方法と、同じ画面表示になっています。

アクティビティクラスでは、onCreateメソッドで、getWindow().addFlags()とrequestWindowFeature()の記述しています。この2行をsetContentView()の前に記述する事がポイントらしいです。ここでも、アクティビティ表示時にタイトルバーがまったく表示されないのを確認するため、スリープ処理を追加しています。

public class NoTitleActivity extends Activity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		// 親クラスのonCreate()を呼ぶ前にスリープを入れて、表示状態を確認する。
		sleep(1000);
		super.onCreate(savedInstanceState);

		// setContentView()呼び出し前に、タイトルバーなし設定を行う
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		// レイアウト設定
		setContentView(R.layout.activity);

		// コンテンツ設定
		TextView textView = (TextView) findViewById(R.id.textView1);
		textView.setText(getResources().getString(R.string.hello));
	}

	private void sleep(long time) {
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			Log.d(getClass().getName(), getClass().getSimpleName()
					+ "#onCreate()," + e.getMessage());
		}
	}
}

上述のアクティビティに対するAndroidManifest.xmlは、次のように定義します。android:theme属性で、Android標準の@android:style/Theme.NoTitleBarを設定し、初期表示の状態でタイトルバーを非表示にしています。もし、android:theme属性を設定しないと、デフォルト設定でタイトルバーありとして動作することになるため、画面表示時に一瞬だけタイトルバーが見えてしまいます。

        <activity
            android:name=".NoTitleActivity"
            android:label="TryUI002 NoTitle"
            android:theme="@android:style/Theme.NoTitleBar" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

以上で、アクティビティクラス側での全画面表示が出来ました。

動作環境

Android OS:Version 2.1
端末:Xperia SO-01B

参照

次のページを参考にさせて頂きました。

アプリポケット Ver.0.3.3公開

Android用ランチャーアプリ「アプリポケット」のVersion 0.3.3を公開しました。変更内容は下記です。

概要

次の機能改善を行いました。主に、Google Playのコメント欄に頂いていました要望です。

  1. アプリ操作ダイアログにGoogle Playページ表示メニューを追加。
  2. トップ画面のアイコンタイトル表示で1行のみ表示する設定を追加。
  3. バックアップ/リストアデータに除外設定を追加。

次の不具合を修正しました。こちらも、主に、Google Playのコメント欄で指摘されていた件を含みます。

  1. トップ画面を全画面表示する際、一瞬タイトルが表示されるのを修正。
  2. タグの並び替え指定が反映されない場合があるのを修正。

動作確認環境

アプリポケット Ver.0.3.2公開

Android用ランチャーアプリ「アプリポケット」のVersion 0.3.2を公開しました。変更内容は、タグ操作の機能改善と、不具合対応です。

概要

主なアップデート内容です。

  1. トップ画面に表示するタグの並び順を変更できるようにしました。トップ画面でメニューボタンを押して、タグ管理画面を表示し、並び順を変更して下さい。
  2. アプリ検索機能やバックアップ機能を実行した際に、エラーが発生していたのを修正しました。

動作確認環境

ListViewの項目をドラッグして並び替え可能にしてみた

先日の記事(こんなに簡単だとは思わなかった!Viewのドラッグ方法)で、Viewをドラッグ・アンド・ドロップしてみました。今回は、発展させて、ListViewの項目をドラッグ・アンド・ドロップして並び替えられるようにしてみました。少し長いですが、作成したプログラムを紹介します。

画面イメージ

作成したプログラムの画面イメージは、次のようになります。画面中央の背景色が白っぽくなっている矩形部分がドラッグ中の項目です。

プログラム構成

画面とJavaクラスの構成を図にしました。DragListViewは、AndroidのListViewを継承し、ドラッグ・アンド・ドロップの主な制御を行なっています。DragListAdapterは、ListViewに表示する項目とその並び順を管理しています。PopupViewは、ドラッグ時に選択項目をポップアップ表示するビューです。

プログラム詳細(レイアウトXML

レイアウトXML(drag_list_activity.xml)は、次のようになります。今回作成したDragListViewを配置しています。ドラッグ時に表示する選択項目のポップアップは、実行時に作成するため、レイアウトXMLには出て来ません。レイアウトやビューに余白や背景色を付けているのは、画面上でビューの配置をわかりやすくするためです。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/FrameLayout1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#555555"
    android:padding="20dip" >
    <com.kurukurupapa.tryandroidui.draglist.DragListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#000000" >
    </com.kurukurupapa.tryandroidui.draglist.DragListView>
</FrameLayout>

プログラム詳細(アクティビティ - DragListActivity)

アクティビティのクラス(DragListActivity.java)です。上記のレイアウトXMLを呼び出し、DragListViewとDragListAdapterを紐付けています。

public class DragListActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.drag_list_activity);

		DragListAdapter adapter = new DragListAdapter(this);
		DragListView listView = (DragListView) findViewById(R.id.list);
		listView.setAdapter(adapter);
	}
}

プログラム詳細(リストビュー - DragListView)

ドラッグ・アンド・ドロップの制御を行うクラス(DragListView.java)です。AndroidのListViewクラスを継承しています。タッチイベントが発生すると、DragListAdapterとPopupViewの、ドラッグ開始、ドラッグ中、ドラッグ終了メソッドを各々呼び出し、画面再描画しています。リスト項目の長押しでドラッグ開始するようにしています。

public class DragListView extends ListView implements
		AdapterView.OnItemLongClickListener {

	private static final int SCROLL_SPEED_FAST = 25;
	private static final int SCROLL_SPEED_SLOW = 8;

	private DragListAdapter adapter;
	private PopupView popupView;
	private MotionEvent downEvent;
	private boolean dragging = false;

	public DragListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		popupView = new PopupView(context);
		setOnItemLongClickListener(this);
	}

	@Override
	public void setAdapter(ListAdapter adapter) {
		if (adapter instanceof DragListAdapter == false) {
			throw new RuntimeException("引数adapterがDragListAdapterクラスではありません。");
		}
		super.setAdapter(adapter);
		this.adapter = (DragListAdapter) adapter;
	}

	/**
	 * 長押しイベント<br>
	 * ドラッグを開始する。当イベントの前に、タッチイベント(ACTION_DOWN)が呼ばれている前提。
	 */
	@Override
	public boolean onItemLongClick(AdapterView<?> parent, View view,
			int position, long id) {
		return startDrag(downEvent);
	}

	/**
	 * タッチイベント<br>
	 * ドラッグしている項目の移動や、ドラッグ終了の制御を行う。
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		boolean result = false;
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			storeMotionEvent(event);
			break;
		case MotionEvent.ACTION_MOVE:
			result = doDrag(event);
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_OUTSIDE:
			result = stopDrag(event);
			break;
		}

		// イベントを処理していなければ、親のイベント処理を呼ぶ。
		// 長押しイベントを発生させるため、ACTION_DOWNイベント時は、親のイベント処理を呼ぶ。
		if (result == false) {
			result = super.onTouchEvent(event);
		}
		return result;
	}

	/**
	 * 長押しイベント時に、タッチ位置を取得するため、ACTION_DOWN時のMotionEventを保持する。
	 */
	private void storeMotionEvent(MotionEvent event) {
		downEvent = event;
	}

	/**
	 * ドラッグ開始
	 */
	private boolean startDrag(MotionEvent event) {
		dragging = false;
		int x = (int) event.getX();
		int y = (int) event.getY();

		// イベントから position を取得
		// 取得した position が 0未満=範囲外の場合はドラッグを開始しない
		int position = eventToPosition(event);
		if (position < 0) {
			return false;
		}

		// アダプターにドラッグ対象項目位置を渡す
		adapter.startDrag(position);

		// ドラッグ中のリスト項目の描画を開始する
		popupView.startDrag(x, y, getChildByIndex(position));

		// リストビューを再描画する
		invalidateViews();
		dragging = true;
		return true;
	}

	/**
	 * ドラッグ処理
	 */
	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) {
			// アダプターのデータを並び替える
			adapter.doDrag(position);
		}

		// ドラッグ中のリスト項目の描画を更新する
		popupView.doDrag(x, y);

		// リストビューを再描画する
		invalidateViews();

		// 必要あればスクロールさせる
		// 注意:invalidateViews()後に処理しないとスクロールしなかった
		setScroll(event);
		return true;
	}

	/**
	 * ドラッグ終了
	 */
	private boolean stopDrag(MotionEvent event) {
		if (!dragging) {
			return false;
		}

		// アダプターにドラッグ対象なしを渡す
		adapter.stopDrag();

		// ドラッグ中のリスト項目の描画を終了する
		popupView.stopDrag();

		// リストビューを再描画する
		invalidateViews();
		dragging = false;
		return true;
	}

	/**
	 * 必要あればスクロールさせる。<br>
	 * 座標の計算が煩雑になるので当Viewのマージンとパディングはゼロの前提とする。
	 */
	private void setScroll(MotionEvent event) {
		int y = (int) event.getY();
		int height = getHeight();
		int harfHeight = height / 2;
		int harfWidth = getWidth() / 2;

		// スクロール速度の決定
		int speed;
		int fastBound = height / 9;
		int slowBound = height / 4;
		if (event.getEventTime() - event.getDownTime() < 500) {
			// ドラッグの開始から500ミリ秒の間はスクロールしない
			speed = 0;
		} else if (y < slowBound) {
			speed = y < fastBound ? -SCROLL_SPEED_FAST : -SCROLL_SPEED_SLOW;
		} else if (y > height - slowBound) {
			speed = y > height - fastBound ? SCROLL_SPEED_FAST
					: SCROLL_SPEED_SLOW;
		} else {
			// スクロールなしのため処理終了
			return;
		}

		// 画面の中央にあるリスト項目位置を求める
		// 横方向はとりあえず考えない
		// 中央がちょうどリスト項目間の境界の場合は、位置が取得できないので、
		// 境界からずらして再取得する。
		int middlePosition = pointToPosition(harfWidth, harfHeight);
		if (middlePosition == AdapterView.INVALID_POSITION) {
			middlePosition = pointToPosition(harfWidth, harfHeight
					+ getDividerHeight());
		}

		// スクロール実施
		final View middleView = getChildByIndex(middlePosition);
		if (middleView != null) {
			setSelectionFromTop(middlePosition, middleView.getTop() - speed);
		}
	}

	/**
	 * MotionEvent から position を取得する
	 */
	private int eventToPosition(MotionEvent event) {
		return pointToPosition((int) event.getX(), (int) event.getY());
	}

	/**
	 * 指定インデックスのView要素を取得する
	 */
	private View getChildByIndex(int index) {
		return getChildAt(index - getFirstVisiblePosition());
	}
}

プログラム詳細(リストデータ管理 - DragListAdapter)

リスト項目の並び替え行うクラス(DragListAdapter.java)です。AndroidのBaseAdapterクラスを継承しています。データの並び替えを行う処理を実装しています。ドラッグ中の選択項目は、ポップアップ表示する方針ですので、当クラスでは非表示にしています。

public class DragListAdapter extends BaseAdapter {
	private static final String[] items = { "Android 1.0(APIレベル1)",
			"Android 1.1(APIレベル2)", "Android 1.5(APIレベル3)",
			"Android 1.6(APIレベル4)", "Android 2.0(APIレベル5)",
			"Android 2.0.1(APIレベル6)", "Android 2.1(APIレベル7)",
			"Android 2.2(APIレベル8)", "Android 2.3(APIレベル9)",
			"Android 2.3.3(APIレベル10)", "Android 3.0(APIレベル11)",
			"Android 3.1(APIレベル12)", "Android 3.2(APIレベル13)",
			"Android 4.0(APIレベル14)", };

	private Context context;
	private int currentPosition = -1;

	public DragListAdapter(Context context) {
		this.context = context;
	}

	@Override
	public int getCount() {
		return items.length;
	}

	@Override
	public Object getItem(int position) {
		return items[position];
	}

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

	/**
	 * リスト項目のViewを取得する
	 */
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// View作成
		if (convertView == null) {
			convertView = new TextView(context);
		}
		TextView textView = (TextView) convertView;
		textView.setTextSize(30);

		// データ設定
		textView.setText((String) getItem(position));

		// ドラッグ対象項目は、ListView側で別途描画するため、非表示にする。
		if (position == currentPosition) {
			textView.setVisibility(View.INVISIBLE);
		} else {
			textView.setVisibility(View.VISIBLE);
		}
		return textView;
	}

	/**
	 * ドラッグ開始
	 *
	 * @param position
	 */
	public void startDrag(int position) {
		this.currentPosition = position;
	}

	/**
	 * ドラッグに従ってデータを並び替える
	 *
	 * @param newPosition
	 */
	public void doDrag(int newPosition) {
		String item = items[currentPosition];
		if (currentPosition < newPosition) {
			// リスト項目を下に移動している場合
			for (int i = currentPosition; i < newPosition; i++) {
				items[i] = items[i + 1];
			}
		} else if (currentPosition > newPosition) {
			// リスト項目を上に移動している場合
			for (int i = currentPosition; i > newPosition; i--) {
				items[i] = items[i - 1];
			}
		}
		items[newPosition] = item;

		currentPosition = newPosition;
	}

	/**
	 * ドラッグ終了
	 */
	public void stopDrag() {
		this.currentPosition = -1;
	}
}

プログラム詳細(ポップアップ表示ビュー - PopupView)

ドラッグ中の選択項目をポップアップ表示するクラス(PopupView.java)です。AndroidのImageViewクラスを継承しています。このビューはWindowManager上に表示しています。WindowManager上では座標系が変わることに注意しなければならないため、getLocationInWindow()メソッドを使用しています。

public class PopupView extends ImageView {
	private static final Bitmap.Config DRAG_BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
	private static final int BACKGROUND_COLOR = Color.argb(128, 0xFF, 0xFF,
			0xFF);
	private static final int Y_GAP = 20;

	private WindowManager windowManager;
	private WindowManager.LayoutParams layoutParams;
	private boolean dragging = false;
	private int baseX;
	private int baseY;
	private int[] itemLocation = new int[2];

	public PopupView(Context context) {
		super(context);
		windowManager = (WindowManager) context
				.getSystemService(Context.WINDOW_SERVICE);

		// レイアウトを初期化する
		initLayoutParams();
	}

	/**
	 * ドラッグ開始
	 *
	 * @param itemView
	 */
	public void startDrag(int x, int y, View itemView) {
		// ドラッグ終了処理が未完了の場合、前回のドラッグ処理が不正に終了しているかもしれない。
		// 念のため、ドラッグ終了処理を行う。
		if (dragging) {
			stopDrag();
		}

		// ドラッグ開始座標を保持する
		baseX = x;
		baseY = y;

		// ドラッグする項目の初期位置を保持する
		itemView.getLocationInWindow(itemLocation);

		// ドラッグ中の画像イメージを設定する
		setBitmap(itemView);

		// WindowManagerに登録する
		updateLayoutParams(x, y);
		windowManager.addView(this, layoutParams);
		dragging = true;
	}

	/**
	 * ドラッグ中処理
	 *
	 * @param x
	 * @param y
	 */
	public void doDrag(int x, int y) {
		// ドラッグ開始していなければ中止
		if (dragging == false) {
			return;
		}

		// ImageViewの位置を更新
		updateLayoutParams(x, y);
		windowManager.updateViewLayout(this, layoutParams);
	}

	/**
	 * ドラッグ項目の描画を終了する
	 */
	public void stopDrag() {
		// ドラッグ開始していなければ中止
		if (dragging == false) {
			return;
		}

		// WindowManagerから除去する
		windowManager.removeView(this);
		dragging = false;
	}

	/**
	 * ドラッグ中の項目を表す画像を作成する
	 */
	private void setBitmap(View itemView) {
		Bitmap bitmap = Bitmap.createBitmap(itemView.getWidth(),
				itemView.getHeight(), DRAG_BITMAP_CONFIG);
		Canvas canvas = new Canvas();
		canvas.setBitmap(bitmap);
		itemView.draw(canvas);
		setImageBitmap(bitmap);
		setBackgroundColor(BACKGROUND_COLOR);
	}

	/**
	 * ImageView 用 LayoutParams の初期化
	 */
	private void initLayoutParams() {
		// getLocationInWindow()と座標系を合わせるためFLAG_LAYOUT_IN_SCREENを設定する。
		// FLAG_LAYOUT_IN_SCREENを設定すると、端末ディスプレイ全体の左上を原点とする座標系となる。
		// 設定しない場合、ステータスバーを含まない左上を原点とする。
		layoutParams = new WindowManager.LayoutParams();
		layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
		layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
		layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
		layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
				| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
				| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
				| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
				| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
		layoutParams.format = PixelFormat.TRANSLUCENT;
		layoutParams.windowAnimations = 0;
	}

	/**
	 * ImageView 用 LayoutParams の座標情報を更新
	 */
	private void updateLayoutParams(int x, int y) {
		// ドラッグ中であることが分かるように少し上にずらす
		layoutParams.x = itemLocation[0] + x - baseX;
		layoutParams.y = itemLocation[1] + y - baseY - Y_GAP;
	}
}

環境

  • 動作可能な最小OSバージョン:Android 2.1
  • 動作確認端末:Xperia SO-01B

参考サイト

次のサイトを参考にして作成しました。感謝です。