2012年12月27日木曜日

Git GitExtensionsでwindowsに環境を作成する

少人数のチームでネットワークで繋がったWindowsマシンに
GitとGit Extensionsを使用してバージョン管理ができる環境を作ります。

環境


サーバー(windows)
開発者A用のローカルマシン(windows)
開発者B用のローカルマシン(windows)
それぞれのマシンはネットワークで繋がっているものとします。

インストール


Git for Windows
http://msysgit.github.com/
2012/12/26現在の最新版:Git-1.8.0-preview20121022.exe
上記のサイトよりGit for Windowsをダウンロードします。

Git Extensions
Gitを操作するためのGUIツールです。
http://code.google.com/p/gitextensions/
2012/12/26現在の最新版:GitExtensions243Setup.msi
上記のサイトよりGit Extensionsをダウンロードします。

ダウンロードしたそれぞれのファイルを3つのマシンにインストールします。
インストールはデフォルトのままです。

サーバーにリモートリポジトリを作成する


リポジトリを配置するフォルダを作成し、共有設定をしておきます。
私はDドライブ直下にGitRepoフォルダを作成し、共有設定をしました。
フォルダ名に日本語が含まれると操作に失敗します。

次にGitExtensionsを起動します。
設定画面が起動します。
「チェックリスト」タブの「起動時に設定を確認」がチェックONだとGitExtensionsを起動するたびに表示されます。
特に設定する項目はないので「OK」ボタンでダイアログを閉じます。

GitExtensionsの「リポジトリの作成」を選択します。
「リポジトリの作成」ダイアログで
ディレクトリに「D:¥GitRepo¥プロジェクト名など」を入力し
「Working dirを持たないCentralリポジトリ」を選択し
作成ボタンでリポジトリを作成します。
「Initialized empty shared Git repository in D:/GitRepo/MyProjectName/」とメッセージが表示れるのでOKボタンで進みます。
「D:/GitRepo/MyProjectName」フォルダにいくつかファイルができます。
これで共有リポジトリの作成は終了です。

1台目のローカルマシン


1台目のローカルマシーンでGitExtensionsを起動します。
こちらも設定画面が表示されますが、特に設定はないので終了します。

GitExtensionsの「リポジトリのclone」を選択します。
「クローン」ダイアログで
クローンするリポジトリ:先ほど作成した共有リポジトリのパス
クローン先:ローカルマシンのリポジトリを作成するフォルダ
サブディレクトリの作成:プロジェクト名など
リポジトリの種類:個人リポジトリ を選択し
「クローン」ボタンでリポジトリをクローンします。
フォルダ名に日本語が含まれると操作に失敗します。
進捗ダイアログが表示されるのでOkボタンで進みます。
リポジトリを開くかメッセージが表示されるので「はい」を選択します。
「.gitignoreの編集」ボタンをクリックし、開いたダイアログでバージョン管理に含めないファイルの拡張子を登録します。
VisualStudioであれば「デフォルトの無視ファイル」で追加されるパターンで大丈夫そうです。
保存ボタンでダイアログを終了します。
「コミット」ボタンで変更した「.gitignore」ファイルをコミットします。
「作業ディレクトリの変更点」に表示されている「.gitignore」を「Stage」ボタンでステージエリアに移動させます。
コミットメッセージを入力し、「コミット」をクリックします。
進捗ダイアログが表示されるので「OK」ボタンで進みます。
コミット内容を表示する画面が表示されます。
次にプログラムをコミットします。
ファイルエクスプローラからE:/GitRepo/MyProjectNameにプログラムをドラッグします。
そうするとGitExtensionsのコミットの色が青から赤に変わります。
ファイルを変更し、コミットされていないファイルがあれば知らせてくれます。
「Gitコマンド」 > 「コミット」で追加したプログラムをコミットします。
「作業ディレクトリの変更点」に表示されているプログラムファイルを「StageAll」ボタンでステージエリアに移動します。
コミットメッセージを入力し「コミット」ボタンをクリックします。
進捗ダイアログが表示されるので「OK」ボタンで進みます。
現在のコミット状態
コミットしたプログラムをリモートリポジトリに送ります。
「Gitコマンド」 > 「Push(リモートへ反映)」で開いたダイアログより
リモート「origin」でブランチ「master」から「master」で「Push」します。
これでローカルブランチの内容をリモートブランチへ送信します。
「はい」を選択。
以上で1台目のローカルマシンからの設定は終了です。

2台目のローカルマシーン

2台目のローカルマシーンでGitExtensionsを起動します。
こちらも設定画面が表示されますが、特に設定はないので終了します。

GitExtensionsの「リポジトリのclone」を選択します。
「クローン」ダイアログで
クローンするリポジトリ:先ほど作成した共有リポジトリのパス
クローン先:ローカルマシンのリポジトリを作成するフォルダ
サブディレクトリの作成:プロジェクト名など
1台目と違ってブランチは「master」を選択します。
リポジトリの種類:個人リポジトリ を選択し
「クローン」ボタンでリポジトリをクローンします。
フォルダ名に日本語が含まれると操作に失敗します。
進捗ダイアログが表示されるので「OK」ボタンで先に進みます。
リポジトリを開くかメッセージが表示されるので「はい」を選択します。
1台目のローカルマシーンでコミットした内容が表示れます。

これで2台目のマシーンの設定は終了です。

2012年12月26日水曜日

Git GitExtensionsをインストールするとVisualStudioのセットアップが作成できない

GitとGitExtensionsをインストールしたら、VisualStudioのSetUpプロジェクトがビルドできなくなってしまいました。
SetUpでユーザのデスクトップにショートカットを作成しているのですが、
ショートカットにアイコンをセットした状態でビルドすると
「'[DesktopFolder]' にあるショートカット 'ProjectName (アクティブ) の プライマリ出力 へのショートカット' をビルドできません。」
とエラーになってしまいます。


解決方法は
VisualStudioの「ツール」メニュー「アドインマネージャ」で「Git Extemsions」のチェックをOFFにします。
VisualStudioのメニューバーに「Git」が表示されていますが、チェックOFFにすると「Git」が表示されなくなります。
「Git」メニューが表示されている場合はVisualStudioを再起動してください。
「Git」メニューがない状態でビルドするとエラーはでなくなりました。

2012年12月13日木曜日

Android CheckBoxの画像を変更する(BackGround) その2

前回「Android CheckBoxの画像を変更する その1」でコチラ「Android GridView CheckBox付きの画像を表示」で作成したチェックボックスの画像を変更してみました。
CheckBoxを画像の右上に配置したいのですが、これ以上動きません。

実はCheckBox画像の右横にもう一つ画像があるのです。
CheckBoxのbackgroundに設定されている@android:drawable/btn_check_label_background は透明の画像で
チェック画像とテキストの間隔を調整しているようです。
ということでbackgroundに@nullを設定してみました。
一つ目のCheckBoxはデフォルトの状態です。
二つ目のCheckBoxはtextに値を設定し、backgroundに@nullを設定しています。
三つ目のCheckBoxはtextに値を設定せず、backgroundに@nullを設定していますが、表示されません。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <CheckBox
        android:id="@+id/checkBox1"
        style="@style/CustomCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox" />
    
    <CheckBox
        android:id="@+id/CheckBox02"
        style="@style/CustomCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox" 
        android:background="@null" />   

    <CheckBox
        android:id="@+id/CheckBox03"
        style="@style/CustomCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
         android:text=""
        android:background="@null" />

</LinearLayout>
btn_check_label_backgroundに替わる画像を作ってあげればよさそうですが、
検索すると「Y.A.M の 雑記帳」さんの Android RadioButton の画像とテキストの間隔を広げる でshapeを使った方法が紹介されていました。

バックグランドに設定するShapeを定義する

res/drawableに「custom_checkbox_background.xml」を作成します。
<?xml version="1.0" encoding="utf-8"?>
<shape  xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
     <solid android:color="#A9A9A9" />
     <padding        
         android:left="22dip"        
         android:top="0dip"        
         android:right="0dip"        
         android:bottom="0dip" />
</shape>

styleを定義する

res/values/styles.xmlを編集し、CheckBoxに設定するスタイルを追記します。
<resources>

 <style name="CustomCheckBox" parent="android:Widget.CompoundButton.CheckBox">
  <item name="android:button">@drawable/custom_checkbox</item>
  <item name="android:background">@drawable/custom_checkbox_background</item>
 </style>
    
</resources>

CheckBoxにStyleを設定する

res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <CheckBox
        android:id="@+id/checkBox1"
        style="@style/CustomCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox" />
    
    <CheckBox
        android:id="@+id/CheckBox02"
        style="@style/CustomCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="" />   

</LinearLayout>

実行してみた



GridViewのCheckBoxにも適用してみました。
右上に配置できています。
shapeの角を丸くして背景色をグラデーションにすればいい感じじゃないでしょうか。

Android CheckBoxの画像を変更する その1

ここ「Android Wiki ボタンの画像変更(CheckBox,RadioButton)」を見れば一発だけど、一応メモ。

画像を用意する

まずチェックボックスの4つの状態を表す画像を用意します。
画像はres/drawable-nodpiに配置します。
  • チェックONかつ選択されていない状態の画像 [checkbox_checked]


  • チェックONかつ選択されている状態の画像 [checkbox_checked_selected]


  • チェックOFFかつ選択されていない状態の画像 [checkbox_unchecked]


  • チェックOFFかつ選択されている状態の画像 [checkbox_unchecked_selected]

画像を定義する

res/drawableフォルダに「custom_checkbox.xml」を作成します。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- チェックON かつ 選択されている状態 -->
    <item
        android:state_checked="true"
        android:state_pressed="true"
        android:drawable="@drawable/checkbox_checked_selected" />
    <!-- チェックON かつ 選択されていない状態-->
    <item
        android:state_checked="true"
        android:drawable="@drawable/checkbox_checked" />
    <!-- チェックOFF かつ 選択されている状態-->
    <item
        android:state_checked="false"
        android:state_pressed="true"
        android:drawable="@drawable/checkbox_unchecked_selected" />
    <!-- チェックOFF かつ 選択されていない状態-->
    <item
        android:state_checked="false"
        android:drawable="@drawable/checkbox_unchecked" />
</selector>

styleを定義する

res/values/styles.xmlを編集し、CheckBoxに設定するスタイルを追記します。
<resources>
    <style name="CustomCheckBox" parent="android:Widget.CompoundButton.CheckBox">
        <item name="android:button">@drawable/custom_checkbox</item>
    </style>
</resources>

CheckBoxにStyleを設定する

res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <CheckBox
        android:id="@+id/CheckBox03"
        style="@style/CustomCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox" />

    <CheckBox
        android:id="@+id/CheckBox02"
        style="@style/CustomCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checked="true"
        android:text="CheckBox" />

</LinearLayout>

実行してみた

ハイ。ちゃんとできました。

そしてハマった。

で、以前コチラ「Android GridView CheckBox付きの画像を表示」で作成したチェックボックスに適用してみたのですよ。
私はCheckBoxを画像の右上に配置したいのに、これ以上ガンとして動きません。
レイアウトをいじり倒しても動かないし・・・結構ハマりました・・・

実はCheckBox画像の右横にもう一つ画像があるのですよ。
次回はこの画像をどうにかしたいと思います。

2012年12月11日火曜日

Android ActionBarとFragmentを利用したTab画面でデータを(GridView)表示する

以前作成したTab画面に、以前作成したSDカード内の画像をチェックボックス付きで表示するGridViewを乗せてみました。
Android ActionBarとFragmentを使用してTab画面を表示する(Android 2.x)
Android GridView CheckBox付きの画像を表示

Fragment再作成時にデータを保存・復元する


まずグリッドの個々のアイテムであるチェックボックスと画像を管理するクラスCheckedImageに
Parcelableインターフェースを実装しBundleに追加できるようにします。
boolean型をParcelableで保存するには  dest.writeByte((byte) (mBoolean ? 1 : 0));  とします。
boolean型をParcelableで復元するには  mBoolean = (in.readByte() == 1);  とします。
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.MediaStore;

public class CheckedImage implements Parcelable{
    private boolean mChecked = false;
    private long mBitmapId;
    private Bitmap mBitmap = null;

    public boolean getChecked() {
        return mChecked;
    }
    public void setChecked(boolean checked) {
        this.mChecked = checked;
    }
    public long getBitmapId() {
        return mBitmapId;
    }
    public void setBitmapId(long bitmapId) {
        this.mBitmapId = bitmapId;
    }
    public Bitmap getBitmap(Context context) {
        if (mBitmap == null){
            mBitmap = MediaStore.Images.Thumbnails.getThumbnail(context.getContentResolver(), mBitmapId, MediaStore.Images.Thumbnails.MICRO_KIND, null);
        }
        return mBitmap;
    }
    /**
     * コンストラクタ
     * @param checked
     * @param bitmapId
     */
    public CheckedImage(boolean checked, long bitmapId){
        mChecked = checked;
        mBitmapId = bitmapId;
    }

    /*****************************
     * Parcelable実装のために必要
     *****************************/
    public CheckedImage(Parcel in) {
        // 必ず read と write は同じ順番で入れること。  
        mChecked = (in.readByte() == 1);
        mBitmapId = in.readLong();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // 必ず read と write は同じ順番で入れること。  
        dest.writeByte((byte) (mChecked ? 1 : 0));
        dest.writeLong(mBitmapId);    
    }

    @Override
    public int describeContents() {
        // ファイルディスクリプターを使用しないのであれば常に0
        return 0;
    }

    // これは定型文なのでそのまま使える。
    public static final Creator CREATOR = new Creator() {
        public CheckedImage createFromParcel(Parcel in) {
            return new CheckedImage(in);
        }
        public CheckedImage[] newArray(int size) {
            return new CheckedImage[size];
        }
    };


}

次にデータを保存・復元する際に便利なようにArrayAdapterを改良し
adapterに設定されているデータを取得するメソッドgetItemList()を追加します。
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.CompoundButton.OnCheckedChangeListener;


public class CheckedImageArrayAdapter extends ArrayAdapter<CheckedImage> {
    private static class ItemView {
        ImageView imageview = null;
        CheckBox checkbox = null;
    }
    
    private final static int LAYOUT_ID = R.layout.checked_image; 
    
    private Context mContext;
    private LayoutInflater mInflater;   
    private List<CheckedImage> mItemList;

    /**
     * コンストラクタ
     * @param context
     * @param objects
     */
    public CheckedImageArrayAdapter(Context context, List<CheckedImage> itemlist) {
        super(context, LAYOUT_ID, itemlist);
        mContext = context;
        mItemList = itemlist;
        mInflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    }
    
    /**
     * チェックボックスのチェックONリスナー
     */
    private OnCheckedChangeListener CheckBox1_OnCheckedChangeListener = new OnCheckedChangeListener(){
        public void onCheckedChanged(CompoundButton buttonView,    boolean isChecked) {
            int position =  (Integer)buttonView.getTag();
            CheckedImage item = CheckedImageArrayAdapter.this.getItem(position);
            item.setChecked(isChecked);    
        }};

    /**
     * adapterに設定されているデータを取得する
     * @return
     */
    public ArrayList<CheckedImage> getItemList() {
        return (ArrayList<CheckedImage>) mItemList;
    }
 
   /**
     * adapterに設定されているデータのうちチェックONのデータを取得する
     * @return
     */   
    public List<CheckedImage> getCheckedItem(){
        List<CheckedImage> lstItem = new ArrayList<CheckedImage>();
        for ( int i = 0; i < getCount(); i++) {
            if (getItem(i).getChecked()){
                lstItem.add(getItem(i));
            }
        }
        return lstItem;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ItemView itemview;
        if (convertView == null) {
            convertView = mInflater.inflate(LAYOUT_ID, null);
            itemview = new ItemView();
            itemview.imageview = (ImageView) convertView.findViewById(R.id.imageView1);
            itemview.checkbox = (CheckBox) convertView.findViewById(R.id.checkBox1);
            itemview.checkbox.setOnCheckedChangeListener(CheckBox1_OnCheckedChangeListener);
            convertView.setTag(itemview);
        } else {
            itemview = (ItemView)convertView.getTag();
        }

        CheckedImage item = getItem(position);
        itemview.checkbox.setTag(position);
        itemview.imageview.setImageBitmap(item.getBitmap(mContext));
        itemview.checkbox.setChecked(item.getChecked());

        return convertView;
    }   
}
次にFragmentです。
縦横切り替えしたときにFragmentの再作成をしたくないので、コンストラクタで  setRetainInstance(true); を設定します。
Fragmentを再作成したときデータを復元できるように、onSaveInstanceStateでadapterに設定してるデータを保存します。
タブページを変更したとき、データを再取得したくないのとチェック状態を保持しておきたいので、onCreateでグリッドに設定するadapterを作成します。 このときonCreateの引数savedInstanceStateに復元データがあれば、そのデータを使用するようにします。
import java.util.ArrayList;
import java.util.List;

import android.content.ContentResolver;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.GridView;



public class MainTab1Fragment extends Fragment {
   
    private static final String CHECKED_IMAGE_LIST = "CheckedImageList";

    private GridView mGridView = null;
    private Button mButtonSelect = null;
    private CheckedImageArrayAdapter mAdapter = null;

    public MainTab1Fragment(){
        setRetainInstance(true);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {    
        super.onCreate(savedInstanceState);

        if (mAdapter == null) {
            if (savedInstanceState != null) {
                @SuppressWarnings("unchecked")
                List<CheckedImage> lstItem = (List<CheckedImage>) savedInstanceState.get(CHECKED_IMAGE_LIST);
                mAdapter = new CheckedImageArrayAdapter(getActivity(), lstItem);
            } else {
                //SDカードより画像データのIDを取得
                ContentResolver cr = getActivity().getContentResolver();
                Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; //SDカード       
                Cursor cursor = cr.query(uri, null, null, null, null);        
                List<CheckedImage> lstItem = new ArrayList<CheckedImage>();
                cursor.moveToFirst();   
                for (int i = 0; i < cursor.getCount(); i++){
                    long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
                    lstItem.add(new CheckedImage(false,id));
                    cursor.moveToNext();
                }    
                //グリッド用のアダプターを作成
                mAdapter = new CheckedImageArrayAdapter(getActivity(), lstItem);
            };
        };
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 第3引数のbooleanは"container"にreturnするViewを追加するかどうか
        //trueにすると最終的なlayoutに再度、同じView groupが表示されてしまうのでfalseでOKらしい
        View view = inflater.inflate(R.layout.fragment_main_tab1, container, false);
        mGridView = (GridView)view.findViewById(R.id.gridView);
        mButtonSelect = (Button)view.findViewById(R.id.select);
        mButtonSelect.setOnClickListener(ButtonSelect_OnClickListener);    
        //グリッドにアダプターをセット
        mGridView.setAdapter(mAdapter);
        return view;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelableArrayList(CHECKED_IMAGE_LIST, mAdapter.getItemList());        
    }


}

Android ActionBarとFragmentを利用したTab画面でFragment#OnCreateが2回走る

結構ハマった・・・。

以前作ったActionBarを使ったTab画面で
Android ActionBarとFragmentを使用してTab画面を表示する(Android 2.x)
Android ActionBarとFragmentを使用してTab画面を表示する(Android 4.0以上)
エミュレータの縦横を切り替えると、描画が2重になっているような現象になってました。
FragmentのOnCreateが2回呼ばれていて、Fragmentが2つ重なっているような感じ。

原因はTabLisnerでした。
エミュレータの縦横を切り替えると、Activityが再作成される。
このときFragmentは自動で復元されており1回目のonCreateが走る。
で、Activityが再作成されたので、TabLisnerも再作成されてonTabSelectedが走る。
この時インスタンス変数mFragmentはnullなのでFragmentManagerに再度addされ
2つ目のFragmentがインスタンス化され2回目のonCreateが走る。
たぶん・・・こんな感じ・・・?

なのでコンストラクタでFragmentを探すように変更し
onTabSelectedでもdetachされていないときだけattachするよう変更しました

コードは Android ActionBarとFragmentを使用してTab画面を表示する(Android 2.x)
のTabListenerです。
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.SherlockFragmentActivity;

public class TabListener implements ActionBar.TabListener {

    private Fragment mFragment;
    private final SherlockFragmentActivity mActivity;
    private final String mTag;
    private final Class mClass;
    
    /**
     * @brief コンストラクタ
     * @param activity
     * @param tag
     * @param clz
     */
    public TabListener(SherlockFragmentActivity activity, String tag, Class clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        //FragmentManagerからFragmentを探す。
        mFragment = mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
    }

    /**
     * @brief  タブが選択されたときの処理
     */
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        //ftはnullではないが使用するとNullPointExceptionで落ちる
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            FragmentManager fm = mActivity.getSupportFragmentManager();
            fm.beginTransaction().add(R.id.container, mFragment, mTag).commit();
        } else {
         //detachされていないときだけattachするよう変更
            if (mFragment.isDetached()) {
                FragmentManager fm = mActivity.getSupportFragmentManager();
                fm.beginTransaction().attach(mFragment).commit();
             }
        }
    }
    /**
     * @brief  タブの選択が解除されたときの処理
     */
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        //ftはnullではないが使用するとNullPointExceptionで落ちる
        if (mFragment != null) {
            FragmentManager fm = mActivity.getSupportFragmentManager();
            fm.beginTransaction().detach(mFragment).commit();
       }    
    }
    /**
     * @brief タブが2度目以降に選択されたときの処理
     */
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}

2012年11月20日火曜日

Android ActionBarとFragmentを使用してTab画面を表示する(Android 2.x)

Android 3.0(API level 11)から導入されたActionBarとFragmentを使用して、Android 2.x系でもタブ画面を表示する方法です。

タブ画面関係の過去記事
Android TabActivityとTabHostを使用してTab画面を表示する
Android ActionBarとFragmentを使用してTab画面を表示する(Android 4.0以上)


対象:
Build SDK:Android 4.0 (API 14) / Min SDK:Android 2.1 (API 7)

Android 2.xでFragmentを使用できるようにする

eclipseメニューの「ウインドウ」→「Android SDK マネージャー」を起動します。
「Extras」の「Android Support Library」をインストールします。

Android 2.xでActionBarを使用できるようにする

Android Android2.xでActionBarを使用する を参考にして、ActionBarSherlockライブラリを参照に追加します。

タブ画面を表示するためのコード

まずメインとなるアクティビティを作成します。
Android 3.0以降はandroid.app.Activityを継承して作成しますが、Android3.0以前はcom.actionbarsherlock.app.SherlockFragmentActivityを継承して作成します。
自動で生成されるonCreateOptionMenuメソッドはSherlockFragmentActivityではfinalメソッドでOverrideできないのでコメントアウトします。
またonCreateメソッドではをsetCpmtemtView setContentViewメソッドより前にテーマを設定する必要があります。
テーマを設定しないと予期せぬエラーで落ちます。
import com.actionbarsherlock.app.SherlockFragmentActivity;

import android.os.Bundle;

public class MainActivity extends SherlockFragmentActivity {   
  
     @Override  
     public void onCreate(Bundle savedInstanceState) {   
          super.onCreate(savedInstanceState);
          //setCpmtemtViewメソッドより前にテーマを設定する。   
          setTheme(R.style.Theme_Sherlock_Light); 
          setContentView(R.layout.activity_main);   
     }   
  
//     @Override  
//     public boolean onCreateOptionsMenu(Menu menu) {   
//          getMenuInflater().inflate(R.menu.activity_main, menu);   
//          return true;   
//     }   
  
}  
MainActivityのレイアウトです。
res/layout/activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

</FrameLayout>

次に各タブのコンテンツとなるFragmentを作成します。
Android 3.0以降は android.app.Fragmentを継承して作成しますが、Android3.0以前はandroid.support.v4.app.Fragmentを継承して作成します。
レイアウトはそれぞれの違いがわかるように適当に変更しておいてください。
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Tab1Fragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 第3引数のbooleanは"container"にreturnするViewを追加するかどうか
        //trueにすると最終的なlayoutに再度、同じView groupが表示されてしまうのでfalseでOKらしい
        return inflater.inflate(R.layout.fragment_tab1, container, false);
    }
}
今回は面倒なので2タブにします。
同じように2タブめに表示するFragmentを作成します。
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Tab2Fragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 第3引数のbooleanは"container"にreturnするViewを追加するかどうか
        //trueにすると最終的なlayoutに再度、同じView groupが表示されてしまうのでfalseでOKらしい
        return inflater.inflate(R.layout.fragment_tab2, container, false);
    }
}

次にタブを変更したときのリスナーを作成します。
com.actionbarsherlock.app.ActionBar.TabListenerを実装します。
Android3.0以前でFragment関係のクラスを使用する場合は、android.support.v4.appパッケージのものを使用します。
Android3.0以前でActionBar関係のクラスを使用する場合は、 com.actionbarsherlock.appパッケージのものを使用します。
このクラスの各メソッドの引数「FragmentTransaction」はバグっており使用できません。
SherlockFragmentActivityからFragmentManagerオブジェクトを取得するメソッドはgetFragmentManagerメソッドではなくgetSupportFragmentManagerメソッドになります。
2012/12/11追記
縦横を切り替えたときFragment#OnCreateが2回走る不具合があり、
コンストラクタでFragmentを探すように変更し
onTabSelectedでもdetachされていないときだけattachするよう変更しました。
詳しくは・・・
Android ActionBarとFragmentを利用したTab画面でFragment#OnCreateが2回走る
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.SherlockFragmentActivity;

public class TabListener<T extends Fragment> implements ActionBar.TabListener {

 private Fragment mFragment;
    private final SherlockFragmentActivity mActivity;
    private final String mTag;
    private final Class mClass;
    
    /**
     * コンストラクタ
     * @param activity
     * @param tag
     * @param clz
     */
    public TabListener(SherlockFragmentActivity activity, String tag, Class clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        //FragmentManagerからFragmentを探す。  2012/12/11 追記 
    mFragment = mActivity.getSupportFragmentManager().findFragmentByTag(mTag);   
    }

    /**
     * @brief  タブが選択されたときの処理
     */
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        //ftはnullではないが使用するとNullPointExceptionで落ちる
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            FragmentManager fm = mActivity.getSupportFragmentManager();
            fm.beginTransaction().add(R.id.container, mFragment, mTag).commit();
        } else {
            //detachされていないときだけattachするよう変更   2012/12/11 変更
            //FragmentManager fm = mActivity.getSupportFragmentManager();
            //fm.beginTransaction().attach(mFragment).commit();
            if (mFragment.isDetached()) {   
                FragmentManager fm = mActivity.getSupportFragmentManager();   
                fm.beginTransaction().attach(mFragment).commit();   
             } 
        }
    }
    /**
     * @brief  タブの選択が解除されたときの処理
     */
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        //ftはnullではないが使用するとNullPointExceptionで落ちる
        if (mFragment != null) {
            FragmentManager fm = mActivity.getSupportFragmentManager();
            fm.beginTransaction().detach(mFragment).commit();
       }    
    }
    /**
     * @brief タブが2度目以降に選択されたときの処理
     */
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}

それではメインアクティビティに戻ります。
メインアクティビティではonCreateメソッドでActionBarを取得し、NavigationModeをタブにします。
ActionBarを取得するメソッドはgetActionBarメソッドではなくgetSupportActionBarメソッドになります。
後はActionBarにタブのコンテンツとなるFragmentを追加していくだけです。
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;

import android.os.Bundle;

public class MainActivity extends SherlockFragmentActivity {   
   
    @Override  
    public void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);   
        //setCpmtemtViewメソッドより前にテーマを設定する。   
        setTheme(R.style.Theme_Sherlock_Light);   
        setContentView(R.layout.activity_main);   
        
        // Set up the action bar.
        ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        actionBar.addTab(actionBar.newTab() 
                   .setText("ページ1") 
                   .setTabListener(new TabListener<Tab1Fragment>( 
                              this, "tag1", Tab1Fragment.class))); 
        actionBar.addTab(actionBar.newTab() 
                   .setText("ページ2") 
                   .setTabListener(new TabListener<Tab2Fragment>( 
                              this, "tag2", Tab2Fragment.class))); 

    }   
  
//    SherlockActivityではOverrideできない。   
//    @Override   
//    public boolean onCreateOptionsMenu(Menu menu) {   
//        getMenuInflater().inflate(R.menu.activity_main, menu);   
//        return true;   
//    }   
}  

Android 2.1 (API 7) のエミュレータで実行した結果です。

2012年11月19日月曜日

Android Android2.xでActionBarを使用する

Android 3.0(API level 11)から導入されたAction Barを Android 2.x系でも使用できるようにするには、 ActionBarSherlockライブラリを使用します。

導入方法はコチラが参考になります。
Android 2.xでAction Barが使えるActionBarSherlockの使い方

使い方はコチラが参考になります。
ActionBarSherlockライブラリを使って、2.xでもActionBarを使用する

ActionBarSherlockライブラリを作成する

ActionBarSherlockを下記サイトよりダウンロードし解凍します。
http://actionbarsherlock.com/

eclipseメニューの「ファイル」→「新規」より「Android」ノードの「既存コードからのAndroidプロジェクト」を選択します。

「RootDirectory」に解凍したActionBarSherlockフォルダ内のlibraryを指定します。
「Projects」に表示されているlibraryをチェックONします。
「プロジェクトをワークスペースにコピー」をチェックONします。

「library」プロジェクトが作成されるので、名前はなんでもいいですが「ActionBarSherlockLib」にリネームします。

Javaコンパイラーのバージョンが1.6以前だとエラーになるので変更します。
「ActionBarSherlockLib」プロジェクトを右クリックし「プロパティー」を選択します。
「Java コンパイラー」ノードを選択し「コンパイラー準拠レベル」を1.6に変更します。


ActionBarSherlockライブラリを参照する

ActionBarSherlockLibライブラリを使用する側のプロジェクトを作成します。
Build SDKはAndroid 4.0 (API 14)、Min SDKをAndroid 2.1 (API 7)にしました。

作成したプロジェクトを右クリックし「Android」ノードを選択します。
ライブラリーに「ActionBarSherlockLib」を追加します。

ActionBarを表示させたいActivityはSherlockActivityを継承するよう変更します。
自動で生成されるonCreateOptionMenuメソッドはSherlockActivityではfinalメソッドなのでOverrideできないのでコメントアウトします。
またonCreateメソッドではをテーマを設定する必要があります。
setContentViewメソッドより前にテーマを設定するsetThemeメソッドを呼び出します。
テーマを設定せずにエミュレータで実行しようとすると、実行時エラーになり実行できません。
import com.actionbarsherlock.app.SherlockActivity;

import android.os.Bundle;

public class MainActivity extends SherlockActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setCpmtemtViewメソッドより前にテーマを設定する。
        setTheme(R.style.Theme_Sherlock);
        setContentView(R.layout.activity_main);
    }

//    SherlockActivityではOverrideできない。
//    @Override
//    public boolean onCreateOptionsMenu(Menu menu) {
//        getMenuInflater().inflate(R.menu.activity_main, menu);
//        return true;
//    }
}

Android 2.1
Activityを継承

Android 2.1
SherlockActivityを継承
テーマ「Theme_Sherlock」
Android 2.1
SherlockActivityを継承
テーマ「Theme_Sherlock_Light」
Android 4.0
Activityを継承

Android 4.0
SherlockActivityを継承

2012年11月13日火曜日

Android データがなければInsert、あればUpdate

データがなければInsert、あればUpdateを行いたい時があると思います。
UPDATE と INSERT をもじって、UPSERT と呼ぶそうです。へぇ~

SELECTの結果が0件ならINSERTそれ以外はUPDATEとしてもよいのかもしれませんが
これを1つのSQLで実行することができます。

Oracleでは 「MERGE」

主キーに該当するデータがあればUPDATE、なければINSERTを行います。

    MERGE INTO 表名1 [エイリアス名]
    USING 表名2 | 副問い合わせ [エイリアス名] 
    ON ( 結合条件 )
    WHEN MATCHED THEN
     UPDATE SET カラム名 = 値, …
    WHEN NOT MATCHED THEN
     INSERT (カラム名, … )
     VALUES ( 値, … );


MySQLでは 「REPLACE」 または 「INSERT ON DUPLICATE KEY UPDAT」

INSERT ON DUPLICATE KEY UPDATでは主キーに該当するデータがあればUPDATE、なければINSERTを行います。
REPLACEでは主キーに該当するデータがあればDELTE後INSERTを行い、なければUPDATEを行います。
ただし、検索条件が主キーではないとき、該当データを削除しUPDATEを行います。

   
    INSERT INTO 表名 (カラム名1, カラム名2, ...)
    VALUES (値1, 値2, ...)
    ON DUPLICATE KEY UPDATE カラム名x=値x,カラム名y=値y, ...;


SQLiteでは 「REPLACE」 または 「INSERT OR REPLACE」

こちらはMySQLと違ってINSERT OR REPLACEの別名がREPLACEなので機能は同じで、
主キーに該当するデータがあればUPDATE、なければINSERTを行います。

   
    REPLACE INTO 表名(カラム名) VALUES (値);

2012年11月8日木曜日

Android GridView CheckBox付きの画像を表示

前回「Android GridViewにSDカードの画像を表示する 」の応用です。

今回はCheckBox付きの画像を表示したいと思います。
「SELECT」ボタンでチェックONの画像数を表示します。


Android 4.0 / API 14

MainActivityのLayoutです。
LinearLayoutにButtonとGridViewを配置します。
res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="@string/ButtonSelect"
        android:width="120dip" />

    <GridView
        android:id="@+id/gridView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:columnWidth="70dp"
        android:gravity="center"
        android:numColumns="auto_fit"
        android:stretchMode="columnWidth" >
    </GridView>

</LinearLayout>

GridViewn内の個々のアイテムのレイアウトです。
RelativeLayoutにImageViewとCheckBoxを配置します。
res/layout/grid_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/RelativeLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:paddingBottom="10dp"
        android:paddingLeft="5dp"
        android:paddingRight="5dp"
        android:paddingTop="10dp" />

    <CheckBox
        android:id="@+id/checkBox1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true" />

</RelativeLayout>

次はチェック状態と画像を管理するクラスです。
画像IDをインスタンス変数で保持しておき、サムネイル画像は必要になった時に取得するようにしています。
CheckedImage.java
import android.content.Context;
import android.graphics.Bitmap;
import android.provider.MediaStore;

public class CheckedImage {
    private boolean mChecked = false;
    private long mBitmapId;
    private Bitmap mBitmap = null;
    
    public boolean getChecked() {
        return mChecked;
    }
    public void setChecked(boolean checked) {
        this.mChecked = checked;
    }
    public long getBitmapId() {
        return mBitmapId;
    }
    public void setBitmapId(long bitmapId) {
        this.mBitmapId = bitmapId;
    }
    public Bitmap getBitmap(Context context) {
        if (mBitmap == null){
            mBitmap = MediaStore.Images.Thumbnails.getThumbnail(
                context.getContentResolver(), mBitmapId, MediaStore.Images.Thumbnails.MICRO_KIND, null);
        }
        return mBitmap;
    }
    
    public CheckedImage(boolean checked, long bitmapId){
        mChecked = checked;
        mBitmapId = bitmapId;
    }
    
}

チェックと画像を表示するためのAdapterクラスです。
ArrayAdapterを継承して作成しています。
getView()メソッドでは一度作成したViewは使い回すようにします。
getCheckedItem()メソッドではチェックされているアイテムを取得します。
CheckBoxのOnCheckedChangeListenerでは、CheckedImageオブジェクトのチェック状態を変更します。
CheckedImageArrayAdapter.java
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.CompoundButton.OnCheckedChangeListener;


public class CheckedImageArrayAdapter extends ArrayAdapter<CheckedImage> {
    private static class ViewHolder {
        int position;
        ImageView imageview = null;
        CheckBox checkbox = null;
    }

    private final static int LAYOUT_ID = R.layout.grid_item;


    private Context mContext;
    private LayoutInflater mInflater;   


    public CheckedImageArrayAdapter(Context context, List<CheckedImage> objects) {
        super(context, LAYOUT_ID, objects);
        mContext = context;
        mInflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = mInflater.inflate(LAYOUT_ID, null);
            holder = new ViewHolder();
            holder.imageview = (ImageView) convertView.findViewById(R.id.imageView1);
            holder.checkbox = (CheckBox) convertView.findViewById(R.id.checkBox1);
            holder.checkbox.setOnCheckedChangeListener(CheckBox1_OnCheckedChangeListener);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder)convertView.getTag();
        }

        CheckedImage item = getItem(position);
        holder.position = position;
        holder.imageview.setImageBitmap(item.getBitmap(mContext));
        holder.checkbox.setChecked(item.getChecked());

        return convertView;
    }

    public List<CheckedImage> getCheckedItem(){
        List<CheckedImage> lstItem = new ArrayList<CheckedImage>();
        for ( int i = 0; i < getCount(); i++) {
            if (getItem(i).getChecked()){
                lstItem.add(getItem(i));
            }
        }
        return lstItem;
    }

    private OnCheckedChangeListener CheckBox1_OnCheckedChangeListener = new OnCheckedChangeListener(){
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            View view = (View)buttonView.getParent();
            ViewHolder holder = (ViewHolder)view.getTag();
            CheckedImage item = CheckedImageArrayAdapter.this.getItem(holder.position);
            item.setChecked(isChecked);        
        }};
}

MainActivityのコードです。
SDカード内の画像IDを取得し、CheckedImageオブジェクトを作成します。
「Select」ボタンのクリックで、チェックONのアイテム数を表示します。
MainActivity.java
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.GridView;
import android.widget.Toast;

public class MainActivity extends Activity {

    private GridView mGridView = null;
    private Button mButton = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mGridView = (GridView)findViewById(R.id.gridView1);
        mButton = (Button)findViewById(R.id.button1);
        mButton.setOnClickListener(ButtonSelect_OnClickListener);

        //SDカードより画像データのIDを取得
        Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; //SDカード       
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);        
        ArrayList<CheckedImage> lstItem = new ArrayList<CheckedImage>();
        cursor.moveToFirst();   
        for (int i = 0; i < cursor.getCount(); i++){
            long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
            lstItem.add(new CheckedImage(false,id));
            cursor.moveToNext();
        }

        //グリッド用のアダプターを作成
        CheckedImageArrayAdapter adapter = new CheckedImageArrayAdapter(getApplicationContext(),lstItem);
        //グリッドにアダプターをセット
        mGridView.setAdapter(adapter);

    }


    private OnClickListener ButtonSelect_OnClickListener = new OnClickListener(){
        public void onClick(View view) {
            CheckedImageArrayAdapter adapter = (CheckedImageArrayAdapter)mGridView.getAdapter();
            List<CheckedImage> lstCheckedItem = adapter.getCheckedItem();
            Toast.makeText(MainActivity.this, String.valueOf(lstCheckedItem.size()), Toast.LENGTH_LONG).show();
        }
    };

}

2012年11月7日水曜日

.NET ブラウザからXML Webサービスを呼び出す

いつも忘れてしまうのでメモ。

HTTP-GET を使用してブラウザーで Web サービスをテストするには
http://<servername>/webservicename.asmx/Methodname?parameter=value

servername :Web サービスが配置されているサーバー名。

webservicename.asmx :Web サービスの .asmx ファイルの名前。

Methodname :Web サービスが公開するパブリック メソッドの名前。

parameter : メソッドが要求するパラメーター名と値。

2012年11月6日火曜日

Android 警告「Use a layout_height of 0dip instead of wrap_content for better performance」

警告「Use a layout_height of 0dip instead of wrap_content for better performance」が出たら
layout_weightの設定によるコンポーネントのサイズ計算が走った後、無意味なwrap_contentのサイズ計算が走ってしまうので
layout_weightを指定しているときはlayout_widthまたはlayout_height(LinearLayoutのorientationにより異なる)を[wrap_content」ではなく「0dip」に設定すると、パフォーマンスが良くなるよということ。

Android 警告「Missing contentDescription attribute on image」

警告「Missing contentDescription attribute on image」が出たら
contentDescriptionプロパティに画像が表示されなかった時の説明文を設定します。
<ImageView
    android:id="@+id/imageView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:contentDescription="@string/imageview_description" />

2012年11月1日木曜日

Android GridViewにSDカードの画像を表示する

前回「Android GridView 基本的な使い方」に続いてGridViewにSDカードの画像を表示します。

  1. GridView内で画像を表示するViewを作成する。
  2. GridViewに画像を設定するためのアダプターを作成する
  3. データとなるSDカード内の画像を配列に取得する
  4. グリッドにアダプターを設定する

Android 4.0 / API 14

まずMainActivityのレイアウトです。
GridViewを配置します。
res/layout/activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <GridView
        android:id="@+id/gridView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:horizontalSpacing="10dp"
        android:numColumns="3"
        android:verticalSpacing="10dp" >
    </GridView>

</FrameLayout>

次にGridView内で画像を表示するためのレイアウトを作成します。
ImageViewを配置します。
res/layout/grid_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_action_search" />

</LinearLayout>

画像を設定するためのアダプターを作成します。
ArrayAdapterを継承して作成します。
getView()メソッドでは先ほど作成したレイアウト「grid_item」よりImageViewを取得し画像を設定しています。
public class BitmapAdapter extends ArrayAdapter<Bitmap> {
 
 //GridView内で画像を表示するために作成したレイアウト
    private static final int RESOURCE_ID = R.layout.grid_item;
    
    private LayoutInflater mInflater;
    
    public BitmapAdapter(Context context, List<Bitmap> objects) {
        super(context, RESOURCE_ID, objects);
        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = mInflater.inflate(RESOURCE_ID, null);
        }
        ImageView imageView = (ImageView) convertView.findViewById(R.id.imageView1);
        imageView.setImageBitmap(getItem(position));
        return convertView;
    }
    
}

MainActivityのコードです。
Androidではすべての画像はデータベースで管理されており、以下のコードでCursorオブジェクトを取得します。
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
SDカードではなく本体に保存された画像を取得するには、「INTERNAL_CONTENT_URI」を指定します。
Uri uri = MediaStore.Images.Media.INTERNAL_CONTENT_URI;
次にCursorオブジェクトをループし、 MediaStore.Images.Thumbnails.getThumbnail()メソッドでサムネイル画像を取得し配列に格納していきます。
※getThumbnail()メソッドはサムネイル画像がなければ生成して返してくれます。
public class MainActivity extends Activity {

    private GridView mGridView = null;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mGridView = (GridView)findViewById(R.id.gridView1);
        
        //グリッド4列表示
        mGridView.setNumColumns(4);
        //表示する画像を取得
        Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; //SDカード       
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        ContentResolver cr = getContentResolver();
        ArrayList<Bitmap> lstBitmap = new ArrayList<Bitmap>();
        cursor.moveToFirst();   
        for (int i = 0; i < cursor.getCount(); i++){
            long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
            Bitmap bmp = MediaStore.Images.Thumbnails.getThumbnail(cr, id, MediaStore.Images.Thumbnails.MICRO_KIND, null);
            lstBitmap.add(bmp);
            cursor.moveToNext();
        }
        //アダプター作成
        BitmapAdapter adapter = new BitmapAdapter(getApplicationContext(), lstBitmap);
        //グリッドにアダプタを設定
        mGridView.setAdapter(adapter);
        
       
    }
}

Android GridView 基本的な使い方

GridViewのもっとも基本的な使い方です。
  1. データとなるString配列を作成する
  2. String配列用のアダプターを作成する
  3. グリッドにアダプターを設定する
public class MainActivity extends Activity {

    private GridView mGridView = null;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mGridView = (GridView)findViewById(R.id.gridView1);
        //グリッド4列表示
        mGridView.setNumColumns(4);
        //表示する文字列データ作成
        List<String> lstStr = new ArrayList<String>();
        for (int i = 0; i < 100; i++) {
            lstStr.add("item " + String.valueOf(i));
        }
        //グリッド用のアダプターを作成
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                this, android.R.layout.simple_list_item_1, lstStr);
        //グリッドにアダプターをセット
        mGridView.setAdapter(adapter); 
       
    }
}

2012年10月31日水曜日

Android ADB Shellを使えるようにする

Androidをインストールしたフォルダのandroid-sdk\platform-tools\adb.exeがあります。
ここへ環境変数を設定しパスを通します。

コンピュータのプロパティより「システムの詳細設定」を選択します。
「システムのプロパティ」ダイアログより「詳細設定」タブを選択し、「環境変数」ボタンをクリックします。

システム変数にANDROID_HOMEを追加します。
変数名:「ANDROID_HOME」
変数値:Androidをインストールしたパス¥android-sdk

環境変数のPathを編集し末尾に「;」がなければ追加し
「%ANDROID_HOME%¥platform-tools;」を追加します。

コマンドプロンプトを起動し「adb」を入力してコマンド一覧がでればOKです。

Android エミュレータで起動はキャンセルされました!の対処法

エミュレータで「起動はキャンセルされました!」とエラーになり実行できないときの対処法です。

eclipseのメニューより「ウィンドウ」→「設定」を選択し、表示されたダイアログより「Android」の「DDMS」を選択します

「ADB接続タイム・アウト(ミリ秒)」をデフォルトの5000から、もう少し長めに変更します。

2012年10月26日金曜日

.NET シリアライズしたファイルをデシリアライズした際、改行が読み込まれない

シリアライズしたファイルをデシリアライズで読み込んだ際に改行が読み込まれない問題

あほxのまとめwiki XmlSerializerの改行の扱いについてによると
改行がCRLFのStringをXmlSerializer.Serializeでシリアライズして保存すると、ファイルには改行がCRLFのまま保存されるが、
デシリアライズする際に
ファイルをStreamReaderでXmlSerializer.Deserializeに渡すと改行がLFになる。
ファイルをXmlTextReaderでXmlSerializer.Deserializeに渡すと改行がCRLFになる。

ほぉ~そ~なのか!

シリアライズ
Dim hoge As New Hoge
Using writer As New System.Xml.XmlTextWriter(path, System.Text.Encoding.GetEncoding("UTF-8"))
    Try
        Dim serializer As New System.Xml.Serialization.XmlSerializer(GetType(Hoge))
        serializer.Serialize(writer, hoge)
    Finally
        writer.Close()
    End Try
End Using
デシリアライズ
Dim hoge As Hoge
Using reader As New System.Xml.XmlTextReader(path)
    Try
        Dim serializer As New System.Xml.Serialization.XmlSerializer(GetType(Hoge))
        Dim obj As Object = serializer.Deserialize(reader)
        hoge = CType(obj, Hoge)
    Finally
        reader.Close()
    End Try
End Using

2012年10月16日火曜日

Android ActionBarとFragmentを使用してTab画面を表示する(Android 4.0以上)

以前Android TabActivityとTabHostを使用してTab画面を表示するで作成したTab画面はTabActivityを継承し作成していましたが
Android3.xよりTabActivityは非推奨になったようです。

TabActivityを使用しないでTab画面を作成する方法です。
Android3.0より導入されたActionBarとFragmentを使用します。

対象:
Build SDK:Android 4.0 (API 14) / Min SDK:Android 4.0 (API 14)

Android 2.x でActionBarとFragmentを使用してTab画面を表示する方法はコチラ
Android ActionBarとFragmentを使用してTab画面を表示する(Android 2.x)

まずメインとなるアクティビティを作成します。
android.app.Activityを継承して作成します。
import android.os.Bundle;
import android.view.Menu;
import android.app.Activity;

public class MainActivity extends Activity {

     @Override
     public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
     }

     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
          getMenuInflater().inflate(R.menu.activity_main, menu);
          return true;
     }

}

次に各タブのコンテンツとなるFragmentを作成します。
こちらはandroid.app.Fragmentを継承して作成します。
※android.support.v4.app.FragmentはAndroid2.xでFragmentを使用できるようにサポートされているパッケージです。
今回はandroid4.0以上が対象なのでandroid.app.Fragmentを使用します。
レイアウトはそれぞれの違いがわかるように適当に変更しておいてください。
import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Tab1Fragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 第3引数のbooleanは"container"にreturnするViewを追加するかどうか
        //trueにすると最終的なlayoutに再度、同じView groupが表示されてしまうのでfalseでOKらしい
        return inflater.inflate(R.layout.fragment_tab1, container, false);
    }
}
今回は面倒なので2タブにします。
同じように2タブめに表示するFragmentを作成します。
import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Tab2Fragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 第3引数のbooleanは"container"にreturnするViewを追加するかどうか
        //trueにすると最終的なlayoutに再度、同じView groupが表示されてしまうのでfalseでOKらしい
        return inflater.inflate(R.layout.fragment_tab2, container, false);
    }
}

次にタブを変更したときのリスナーを作成します。
ActionBar.TabListenerインターフェースを実装します。
このクラスの各メソッドの引数「FragmentTransaction」はバグっており常にnullになっているため使用できません。
2012/12/11追記
縦横を切り替えたときFragment#OnCreateが2回走る不具合があり、
コンストラクタでFragmentを探すように変更し
onTabSelectedでもdetachされていないときだけattachするよう変更しました。
詳しくは・・・
Android ActionBarとFragmentを利用したTab画面でFragment#OnCreateが2回走る
import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.ActionBar.Tab;


public class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private Fragment mFragment;
    private final Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;
    
    /**
     * コンストラクタ
     * @param activity
     * @param tag
     * @param clz
     */
    public TabListener(Activity activity, String tag, Class clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        //FragmentManagerからFragmentを探す。  2012/12/11 追記    
    mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);      
    }

    /**
     * @brief タブが選択されたときの処理
     */
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        //ftはnullなので使用できない        
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            FragmentManager fm = mActivity.getFragmentManager();
            fm.beginTransaction().add(R.id.container, mFragment, mTag).commit();
        } else {
             //detachされていないときだけattachするよう変更   2012/12/11 変更   
            //FragmentManager fm = mActivity.getFragmentManager();   
            //fm.beginTransaction().attach(mFragment).commit();   
            if (mFragment.isDetached()) {      
                FragmentManager fm = mActivity.getFragmentManager();      
                fm.beginTransaction().attach(mFragment).commit();      
             }    

        }
    }
    /**
     * @brief  タブの選択が解除されたときの処理
     */
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (mFragment != null) {
            FragmentManager fm = mActivity.getFragmentManager();
            fm.beginTransaction().detach(mFragment).commit();
       }    
    }
    /**
   * @brief タブが2度目以降に選択されたときの処理
     */
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}

それではメインアクティビティに戻ります。
メインアクティビティではonCreateメソッドでActionBarを取得し、NavigationModeをタブにします。
後はActionBarにタブのコンテンツとなるFragmentを追加していくだけです。
import android.app.ActionBar;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Set up the action bar.
        final ActionBar actionBar = getActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        actionBar.addTab(actionBar.newTab() 
                .setText("ページ1") 
                .setTabListener(new TabListener( 
                        this, "tag1", Tab1Fragment.class))); 
        actionBar.addTab(actionBar.newTab() 
                .setText("ページ2") 
                .setTabListener(new TabListener( 
                        this, "tag2", Tab2Fragment.class))); 
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
         getMenuInflater().inflate(R.menu.activity_main, menu);
         return true;
    }

}