ラベル Android の投稿を表示しています。 すべての投稿を表示
ラベル Android の投稿を表示しています。 すべての投稿を表示

2014年12月20日土曜日

Android : Material Design の color の xml


このエントリーをはてなブックマークに追加
colors.xml として ここ で公開して作成してださっている方がいました。
右下の Download Gist ボタンでダウンロードして、colors.xml を引っ張ってきて res/values にコピーして使用します。

他の参考情報として、
色のリスト(パレット)は Material Design -> Style -> Color
色を AppTheme にどのように指定するか、指定した色がどこで使われるかは Using the Material Theme をそれぞれ参照です。

2014年12月6日土曜日

Android : Gradle DSL method not found 'zipAlign()' もしくは 'runProguard()'


このエントリーをはてなブックマークに追加
Android Studio をアップデートすると度々はまる Gradle 系のエラー。
Android Toos Project Site の更新履歴によると、
zipAlign は zipAlignEnabled、runProguard は minifyEnabled にプロパティ名が変わったようです。
build.gradle で zipAlign true と書いていた部分を
zipAlignEnabled true に書き換えると回復し、ビルドできるようになりました。

2014年12月2日火曜日

Android : アプリで認証したアクセス権の取り消し


このエントリーをはてなブックマークに追加
Google Developers Console で作成した認証した API を使ったアプリのデバッグ中、再度認証画面を表示したいなどでアクセス権を取り消すにはGoogle のアカウント権限のページを開き、アプリ名を選択して「アクセス権を取り消す」を押すと認証が解除され、アプリ起動時に再び認証画面が出るようになります(アプリを一度アンインストールする必要有り)。

2014年11月25日火曜日

Android : Google Drive 上のファイルの作成・読み込み(一般フォルダ編)


このエントリーをはてなブックマークに追加
Google Drive 上のファイルとして見えるファイルを作成、読み込みするアプリを開発する方法のメモです。
共通する部分は多数ありますが、アプリケーション専用フォルダ(APP_FOLDER)ではありません。
また、ファイルの編集(すでにあるファイルを読み込み、その内容を変えて保存、というのもこの記事では扱っていません。


準備1. サービスの作成、API の有効化

Set up the game in the Google Play Developer Console を参考にゲームサービスを作成し、作成したプロジェクトの Google Developers Console で Drive API を有効にします。


準備2. GoogleApiClient の作成

まずは GoogleApiClient を作成し、connect を呼んでログインしておく必要があります。
ここで必要な API は Drive.API, Scope は Drive.SCOPE_FILE です。
(ちなみにアプリケーション専用フォルダへの保存の場合はスコープに SCOPE_APPFOLDER が必要になります)
また、以降サンプル内で定義されていない変数・定数についてはクラスのメンバ変数などとしてどこかで定義されていると思ってください。
mGoogleApiClient = new GoogleApiClient.Builder(this)
        .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
            @Override
            public void onConnected(Bundle bundle) {
                Log.i(LOGTAG, "onConnected");
            }

            @Override
            public void onConnectionSuspended(int i) {
                Log.i(LOGTAG, "onConnectionSuspended:" + i);
            }
        })
        .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
            @Override
            public void onConnectionFailed(ConnectionResult connectionResult) {
                Log.i(LOGTAG, "onConnectionFailed:" + connectionResult.toString());
                if(connectionResult.hasResolution()) {
                    try {
                        // 初回起動時などで未認証の場合などはこちらにきます
                        connectionResult.startResolutionForResult(MainActivity.this, GOOGLE_API_CLIENT_RESOLUTION);
                    } catch (Exception e) {
                        Log.e(LOGTAG, e.toString());
                    }
                } else{
                    Log.e(LOGTAG, "hasNoResolution()");
                    int errorCode = connectionResult.getErrorCode();
                    Dialog dialog = GooglePlayServicesUtil.getErrorDialog(errorCode, MainActivity.this, GOOGLE_API_CLIENT_ERROR);
                    if( dialog != null){
                        dialog.show();
                    } else {
                        Log.e(LOGTAG, "no dialog");
                    }
                }
            }
        })
        .addApi(Drive.API).addScope(Drive.SCOPE_FILE)
        .build();
mGoogleApiClient.connect();
startResolutionForResult で OK が返ってきた時に再接続を試みるために onActivityResult に以下のようなコードを書き加えておきます。
※ startResolutionForResult は Activity から startActivity される扱いになるので、Activity の onActivityResult で処理必要があります。Fragment 内から呼んで、Fragment の onActivityResult で処理したい場合は、Activity の onActivityResult からその Fragment の onActivityResult を呼ぶよう明示的にコードを書く必要があります(他の Activity 起動系 API も同様です)。
if(requestCode == GOOGLE_API_CLIENT_RESOLUTION){
    switch (resultCode) {
        case Activity.RESULT_OK:
            mGoogleApiClient.connect();
            break;
        default:
            Log.e(LOGTAG, "" + resultCode);
    }
}


ファイルの作成

DriveApi.newDriveContents に設定したコールバック関数内で、DriveApi.newCreateFileAcitivtyBuilder() にメタデータや初期データを設定、アクティビティを呼ぶ、という流れです。

ResultCallback<DriveApi.DriveContentsResult> newDriveContentsCallback = new
    ResultCallback<DriveApi.DriveContentsResult>() {
        @Override
        public void onResult(DriveApi.DriveContentsResult result) {
            // MimeType は適宜変更する。他にも必要な項目があれば set 関数で set する。
            // setTitle で初期タイトルくらいは決めてあげると良さそう
            MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
                    .setMimeType("text/plain").build();

            DriveContents contents = result.getDriveContents();
            // OutputStream を取得して初期データを書き込む。例として、適当な文字列を書き込む。
            try {
                OutputStream os = contents.getOutputStream();
                String hello = "Hello World!";
                os.write(hello.getBytes());
                os.close();
            } catch (Exception e){
                Log.e(LOGTAG, e.toString());
            }

            // デフォルトで用意された UI を表示
            IntentSender intentSender = Drive.DriveApi
                    .newCreateFileActivityBuilder()
                    .setInitialMetadata(metadataChangeSet)
                    .setInitialDriveContents(contents)
                    .build(mGoogleApiClient);
            try {
                startIntentSenderForResult(
                        intentSender, REQUEST_CODE_CREATOR, null, 0, 0, 0);
            } catch (IntentSender.SendIntentException e) {
                Log.w(LOGTAG, "Unable to send intent", e);
            }
        }
    };

Drive.DriveApi.newDriveContents(mGoogleApiClient).setResultCallback(newDriveContentsCallback);

onActivityResult に以下のようなコードを書いておくと作成された Google Drive ファイルの DriveId が取得可能です(作りっぱなしでよいなら取得する必要はありません)。
if(requestCode == REQUEST_CODE_CREATOR){
    Log.i(LOGTAG,"REQUEST_CODE_CREATOR:" + resultCode);
    if(resultCode == RESULT_OK) {
        DriveId driveId = data.getParcelableExtra(CreateFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);
        Log.i(LOGTAG, "Drive Id:" + driveId.toString());
    }
}


ファイルの読み込み

DriveApi.newOpenFileActivityBuilder() を使用してアクティビティを呼び出し、onActivityResult で DriveId を取得、DriveId からファイルを開いて内容を読み込むという流れです。
まずはファイル選択ダイアログの呼び出し。
// MimeType でフィルタリングされる
IntentSender intentSender = Drive.DriveApi
        .newOpenFileActivityBuilder()
        .setMimeType(new String[] { "text/plain" })
        .build(mGoogleApiClient);
try {
    startIntentSenderForResult(
            intentSender, REQUEST_CODE_OPENER, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
    Log.w(LOGTAG, "Unable to send intent", e);
}

以下のコードは onActivityResult 内という想定です。DriveId からファイル内容を取得します。
if(requestCode == REQUEST_CODE_OPENER){
    if(resultCode == RESULT_OK){
        DriveId driveId = data.getParcelableExtra(
                OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);

        // 取得した driveId からファイルの内容を取得。
        // プログレスを取得して何か行う場合は file.open の第三引数にリスナーを設定する。
        DriveFile file = Drive.DriveApi.getFile(mGoogleApiClient, driveId);
        file.open(mGoogleApiClient, DriveFile.MODE_READ_ONLY, null).setResultCallback(new ResultCallback<DriveApi.DriveContentsResult>() {
            @Override
            public void onResult(DriveApi.DriveContentsResult driveContentsResult) {
                if(driveContentsResult.getStatus().isSuccess()){
                    DriveContents driveContents = driveContentsResult.getDriveContents();
                    // DriveContents の getInputStream で内容を取得する。
                    // 以下はファイルの内容がテキストであることを想定した例。
                    BufferedReader reader = new BufferedReader(
                            new InputStreamReader(driveContents.getInputStream()));
                    StringBuilder builder = new StringBuilder();
                    String contents = null;
                    String line;
                    try {
                        while ((line = reader.readLine()) != null) {
                            builder.append(line);
                        }
                        contents = builder.toString();
                    } catch (IOException e) {
                        Log.e(LOGTAG, "IOException while reading from the stream", e);
                    }
                    // 読み込むだけで内容に変更はなし。discard を呼んで閉じておく(意味は無いかも?)
                    driveContents.discard(mGoogleApiClient);

                    Toast.makeText(MainActivity.this, contents, Toast.LENGTH_LONG).show();
                }
            }
        });
    }
}


ちなみに、認証画面をもう一度出したい場合などで、認証を取り消したいときは このリンク先からアプリを 選択してアクセス権を取り消します。

参考
googledrive/android-demos
com.google.android.gms.drive
Google Drive Android API : Create files
Google Drive Android API : Working with File Contents

2014年11月21日金曜日

Android : Saved Games を使う


このエントリーをはてなブックマークに追加
Saved Games in Android 参照。
便利だけど、ログイン時の許可リクエスト量が多いのが難点。
  • Google Play Game Services : Getting Started for Android Game Development の Step 2 に沿って Google Play Services の設定(後で Internal Error が起こらないようにこの辺りに注意)
  • 作成したプロジェクトの Google Developers Console で Drive API、Google Play Game Management、Google Play Game Services をオンにする
  • GoogleApiClient を作るときは addApi(Games.API).addScope(Games.SCOPE_GAMES) と addApi(Drive.API).addScope(Drive.SCOPE_APPFOLDER) が必要?(コード後述)
    • 足りないと実行時に "appropriate api was not requested." というエラーでる
    • Games.API を指定するとログイン時に Google+ のプロフィールアクセスのような、ただデータを保存したい、という用途には必要のない許可まで求めてしまうが嫌だけど、削れそうにないか…。
  • Google play Developer Console のゲームサービス、「ゲームの詳細」で「保存済みゲーム」をオンにする(Saved Games の訳が保存済みゲーム…?)
    • オンにしていないと実行時に "Cannot use snapshots without enabling the 'Saved Game' feature in the Play console" というエラーが出る
  • Snapshot 新規作成時に snapshot.readFully() を呼ぶと長さ 0 の byte 配列が返る(null ではないので注意)
  • SnapshotMetadataChange の setCoverImage はしなくても大丈夫(デフォルトのコントローラの画像が表示される)
  • デバッグのためなどで、一回認証を解除して再度ログイン画面を出すには Google+ のアプリ設定 から接続を解除する
    • 他に方法はある?

GoogleApiClient 作成のコード例

mGoogleApiClient = new GoogleApiClient.Builder(this)
        .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
            @Override
            public void onConnected(Bundle bundle) {
                Log.i(LOGTAG, "onConnected");
            }

            @Override
            public void onConnectionSuspended(int i) {
                Log.w(LOGTAG, "onConnectionSuspended:" + i);
            }
        })
        .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
            @Override
            public void onConnectionFailed(ConnectionResult connectionResult) {
                Log.e(LOGTAG, "onConnectionFailed:" + connectionResult.toString());
                if(connectionResult.hasResolution()) {
                    try {
                        connectionResult.startResolutionForResult(MainActivity.this, REQUEST_CODE_GOOGLE_API_CLIENT_RESOLUTION);
                    } catch (Exception e) {
                        Log.e(LOGTAG, e.toString());
                    }
                } else{
                    Log.e(LOGTAG, "hasNoResolution()");
                    int errorCode = connectionResult.getErrorCode();
                    Dialog dialog = GooglePlayServicesUtil.getErrorDialog(errorCode, MainActivity.this, REQUEST_CODE_GOOGLE_API_CLIENT_ERROR);
                    if( dialog != null){
                        dialog.show();
                    } else {
                        Log.e(LOGTAG, "no dialog");
                    }
                }
            }
        })
        .addApi(Games.API).addScope(Games.SCOPE_GAMES)
        .addApi(Drive.API).addScope(Drive.SCOPE_APPFOLDER)
        .build();
mGoogleApiClient.connect();

Android : GoogleApiClient (Google Play Services) で発生する Internal Error の原因


このエントリーをはてなブックマークに追加
いろいろ調べた挙句、自分の場合は「Google Play Developer Console で設定したパッケージ名とアプリのパッケージ名が違う」でした。せっかくなので、それも含めてありえる原因をまとめておきます。

  • AndroidManifest.xml に指定した meta-data の com.google.android.gms.games.APP_ID が Google Play Developer Console のリンク済みアプリに記載されたアプリID とは異なる
  • Google Developer Console (API のオンオフを設定したりする方)に入力した SHA1 の値が間違っている
    • ビルド時と異なる keystore ファイルから SHA1 を取得していた、など。
    • デバッグ版の場合は、debug.keystore がある場所(Windows の場合は通常 C:\Users\%ユーザ名%\.android )で以下のコマンド。keytool は java の bin 以下にあるのでパスを通しておく。
      keytool -exportcert -alias androiddebugkey -keystore debug.keystore -list -v
  • Google Developer Console で同意画面を作成していない(「API と認証」セクション)
  • GoogleApiClient の setScope で設定するスコープが足りない
  • Google Play Developer Console で設定したパッケージ名と実際のアプリのパッケージ名が異なる
    • AndroidStudio の場合、build.gradle の defaultConfig の applicationId を変える必要あり(ここを見落としていて、src の方のディレクトリ構造とパッケージ名や AndroidManifest.xml のパッケージ名を変更していた場合も Internal Error でした。)

凡ミスには気をつけたいものです。。。

2014年11月14日金曜日

Android : string.xml リソースで太字・斜体などのスタイルを指定する


このエントリーをはてなブックマークに追加
リソースファイル string.xml では太字を html の bold として <b> タグで指定。
html として処理するので、改行はいつもの \n ではなく <br/> で。
%1$d は %d の引数番号指定。(引数一つしか無いのでここでは特に意味は無いですが…)

<string name="best_score"><![CDATA[Score : %1$d<br/><b>Best Score !</b>]]></string>

コード上からは、

mScoreTextView.setText(Html.fromHtml(getString(R.string.best_score, score)));

のように、getString で取得した値を Html.fromHtml でフォーマットすることで TextView にスタイルを反映できる Spanned インターフェースを作る。

参考:Is it possible to have multiple styles inside a TextView?

2014年11月8日土曜日

Android : AlertDialog のタイトル下の線(Divider)の色を変更する


このエントリーをはてなブックマークに追加


id から Divider 用のラインに使われている View を取得して setBackgroundColor で色を設定します。
NumberPicker の時と同じような方法です。)

AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.dialog)
       .setIcon(R.drawable.ic)
       .setMessage(R.string.dialog_msg);
//The tricky part
Dialog d = builder.create();
d.show();
int dividerId = d.getContext().getResources().getIdentifier("android:id/titleDivider", null, null);
View divider = d.findViewById(dividerId);
divider.setBackgroundColor(getResources().getColor(R.color.my_color));


参考 : How can I change the color of AlertDialog title and the color of the line under it

Android Stduio : Generate Signed APK 時の MissingTranslation エラーを回避する


このエントリーをはてなブックマークに追加
values 以下の string リソースを持ったファイル(一般的に strings.xml, arrays.xml)の resources タグに以下のような属性を追加します。

<?xml version="1.0" encoding="utf-8"?>
<resources
    xmlns:tools="http://schemas.android.com/tools"
    tools:ignore="MissingTranslation">

この方法だと、以下のように個別の item に translatable="false" をつけることなく一括で設定できます。

<string name="hello" translatable="false">hello</string>

Settings の Inspections から Incomplete translation の設定を Error から Warning にしても、build.gradle で lintOptions に abortOnError false をつけていても Generate Signed APK で署名付き APK を作るときはどうしてもエラーでビルドが中断してしまっていたので、この方法で解決しました。

参考 : Avoid Android Lint complains about not-translated string

2014年10月31日金曜日

SwitchCompat の inflate でエラー: android.view.InflateException: Binary XML file line #147: Error inflating class SwitchCompat


このエントリーをはてなブックマークに追加
レイアウト用 xml での宣言を SwitchCompat ではなく android.support.v7.widget.SwitchCompat とフルパスで指定したら解消しました。

Android : NumberPicker の Divider の色を変える


このエントリーをはてなブックマークに追加

こんな感じに、緑色にしてみました。
java.lang.reflect を使って NumberPicker の mSelectionDivider に独自の drawable を設定することで実現します。

Class<?> numberPickerClass = null;
try{
    numberPickerClass = Class.forName("android.widget.NumberPicker");
} catch (ClassNotFoundException e) {
    Log.e(LOGTAG, e.toString());
}

if(numberPickerClass != null){
    java.lang.reflect.Field selectionDivider;
    try {
        selectionDivider = numberPickerClass.getDeclaredField("mSelectionDivider");
        selectionDivider.setAccessible(true);
        Drawable drawable = getResources().getDrawable(R.drawable.numberpicker_selection_divider);
        selectionDivider.set(number_picker, drawable);
    } catch (NoSuchFieldException e) {
        Log.e(LOGTAG, e.toString());
    } catch (IllegalAccessException e) {
        Log.e(LOGTAG, e.toString());
    }
}

drawable には元の drawable のように右のような 9.patch を使っています →

広告 : 良いAndroidアプリを作る139の鉄則

2014年10月30日木曜日

AndroidStudio : Could not find property 'Compile' on project ... の解決方法


このエントリーをはてなブックマークに追加
tasks.withType(Compile) {
    options.encoding = 'UTF-8'
}


build.gradle の上記の記述の
Compile を JavaCompile に変更したら治りました。

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}


Android Studio をアップデートして Gradle のバージョンも上がったのですが、
Gradle 2.0 から Compile オプションは廃止されたようです。

2014年8月28日木曜日

Android Studio : Error:(1, 0) Plugin with id 'android' not found.


このエントリーをはてなブックマークに追加

解決策


builde.gradle の先頭、apply plugin: 'android'の前に以下のように buildscript ブロックを追加してから Sync しました。(build.gradle は1つのプロジェクトに2つありますが、apply plugin: 'android' のある方、階層的に深い位置にある方です。)
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.11.+'
    }
}

android plugin: 'android'
...
参考 : Android Studio: Plugin with id 'android-library' not found
Android Studio Error: (8, 0) Plugin with id 'android' not found

Android Studio : Manifest merger failed : uses-sdk:minSdkVersion 8 cannot be smaller than version 8 declared in library com.google.android.gms:play-services:5.2.08


このエントリーをはてなブックマークに追加
Android Studio で出る上記タイトルのエラー。

原因は Google Play Services が minSDKVersion が 9 以上でないと使えないとのことなので、素直に build.gradle に記載している minSDKVersion を 9 以上にすれば解決します
(Eclipse を使われている場合は AndroidManifest.xml を直編集だと思います。)

ちなみに、SDK Version 8 は Froyo (バージョン2.2)で、2014/8/28 現在で、割合にして 1% 切ってました(0.7%)。もうほとんどないですね。

一方、SDK Version 9 の Gingerbread (バージョン2.3.3) は、まだ 17.8% とそれなりにあるようです。

参考 : Android Dashboards -> Platform Versions

Android Studio : cannot.find.zip.align=The zipalign tool was not found in the SDK.


このエントリーをはてなブックマークに追加
cannot.find.zip.align=The zipalign tool was not found in the SDK.

Please update to the latest SDK and re-export your application
or run zipalign manually.

Aligning applications allows Android to use application resources
more efficiently.
久々に Android Studio でパブリッシュ用の Singed APK をビルドしようと思ってら、 上記のようなエラーが出ました。
ビルド中に zipalign が見つからなかったようです。
\sdk\build-tools\(version) 以下にはあるようなのですが…。

解決策


\sdk\build-tools\android-4.4W (どのバージョンにでもあるのでどこでも良いです)

にある zipalign.exe を

\sdk\tools

に配置したら直りました。 
それぞれ、フルパスで書くと

C:\Program Files (x86)\Android\android-studio\sdk\build-tools\android-4.4W



C:\Program Files (x86)\Android\android-studio\sdk\tools

でしたが、これは Android Studio のインストール先によりますので、参考程度に。

参考 : cannot find zip-align when publishing app

参考先によると、SDK Manager でビルドツールをアップデートして Android Studio を再起動すれば直ると書いてあるので、この方法も試してみると良いかもしれません。自分の場合は直らなかったので、zipalign をコピーしました。

2014年7月27日日曜日

Java : Zip から複数ファイルを解凍して保存する


このエントリーをはてなブックマークに追加
  1. ZipInputStream を作る
  2. ZipInputStream.getNextEntry でエントリー(ファイル)を取得する
  3. ZipInputStream.read で中身を読み込む
  4. 読み込んだ中身を FileOutputStream に書き込む
  5. 2,3,4 の繰り返し
  6. ZipInputStream.close
以下、サンプルコード。Zip 内にディレクトリ構造がある事は想定していない、単純なコードにしています。
Android で外部ストレージ(SDカード)にエクスポートしたデータをアプリのディレクトリに戻すために使用したコードの一部。

try {
    // infilePath で zip ファイルが指定されているとする
    ZipInputStream zipInputStream =
            new ZipInputStream(new FileInputStream(infilePath));
    ZipEntry zipEntry;
    int len;
    byte[] buffer = new byte[1024];

    while((zipEntry = zipInputStream.getNextEntry()) != null){
        // outFolder で出力先のディレクトリが指定されているとする
        FileOutputStream fileOutputStream = new FileOutputStream(outFolder + "/" + zipEntry.getName());
        while ((len = zipInputStream.read(buffer)) > 0) {
            fileOutputStream.write(buffer, 0, len);
        }
        fileOutputStream.close();
    }
    zipInputStream.close();

} catch (Exception e) {
    // エラー処理
}

Java : 複数ファイルを Zip に圧縮して保存する


このエントリーをはてなブックマークに追加
  1. ZipOutputStream を作る
  2. zip に入れるファイル毎に ZipOutputStream.putNextEntry で ZipEntry を追加する
  3. ZipOutputStream.write を書き込む
  4. 2, 3 の繰り返し
  5. ZipOutputStream.close
Android でアプリのデータを外部ストレージ(SDカード等)にまとめてエクスポートするために使いました。 以下サンプルコード。
try {
    // String outfilePath で出力先のファイルパスが指定されているとする
    ZipOutputStream out = new ZipOutputStream(new FileOutputStream(outfilePath));
    byte[] buffer = new byte[1024];
    int len;

    // 引数などで String[] files として zip に追加したいファイルのファイルパスが指定されているとする
    for(String file : files) {
        InputStream in = new FileInputStream(file);
        out.putNextEntry(new ZipEntry(file.getName())); // path名からファイル名だけにしておく
        while ((len = in.read(buffer)) > 0) {
            out.write(buffer, 0, len);
        }
        in.close();
        
    }
    out.close();

} catch(FileNotFoundException e){
    // エラー処理
} catch(Exception e){
    // エラー処理
}

2014年7月26日土曜日

Android : startActivityForResult がすぐ Activity.RESULT_CANCELED を返してしまう(もしくは ConnectionResult.startResolutionForResult が)


このエントリーをはてなブックマークに追加
原因は、startActivityForResult を呼ぶ起動元の Activity の launchMode を singleInstance にしてしまっていた事でした。singleTask に変更して解決。

<activity
    android:name=".MainActivity"
    android:label="@string/app_name"
    android:launchMode="singleInstance" > <!--これ-->
    (...)
</activity>

launchMode については Y.A.M の雑記帳 : Android launchMode の違いに詳しく解説されているのでそちらを参照です。要するに、singleInstance の Activity はそれ単体でタスク(Activity のスタック)になるという設定なので、singleInstande のアクティビティから startActivityForResult を呼んで別の Activity を積むことができない、という事でした。

startActivityForResult で呼ぶ起動先のアプリが singleTask, singleInstance の場合も同じ問題が起こりそうです(singleTask, singleInstance のアクティビティはタスクのルートとなるので)。

Google Drive Android API を使おうとサンプルにそっていた際に、ConnectionResult.startResolutionForResult でこの問題に遭遇していたのでその辺りを探してしまっていましたが、問題はもっと基本的なところにありました。
無事、接続・認証の表示が出るようになり、これで先に進めそうです。

参考 : Android - startActivityForResult immediately triggering onActivityResult

2014年7月25日金曜日

Android : ActionBar の overflow アイコンを変える(ActionBar に入りきらなかったメニューをリスト表示するアイコン)


このエントリーをはてなブックマークに追加

アプリのテーマの Style で android:actionOverflowButtonStyleで指定します。styles.xml で以下のように設定。

<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Holo.Light">
    <!-- Customize your theme here. -->
    <item name="android:actionOverflowButtonStyle">@style/ActionBarOverFlow</item>
</style>

<style name="ActionBarOverFlow" parent="@android:style/Widget.Holo.ActionButton.Overflow">
    <item name="android:src">@drawable/overflow</item>
</style>

ここで指定する src に指定するアイコン画像のサイズは、ActionBar のアイコンに指定するサイズ(32x32dp。xhdpi なら 64x64px、xxhdpi なら 96x96px。)と合っている必要があるようです。大きいサイズの画像を指定したところ、overflow メニューアイコンの表示が大きくなってしまいました。scaleType を fitCenter にしたところ画像のサイズは正常になったものの、アイコンサイズ自体は大きなままに。width 系のパラメータを設定しても変えられず。

ちなみに標準の overflow アイコンは ic_menu_moreoverflow_normal_holo_dark、ic_menu_moreoverflow_normal_holo_light、ic_menu_moreoverflow とかその辺りです。

参考 : Changing overflow icon in the action bar

Android : ActionBar のタイトルを2行表示にする(サブタイトルを表示する)


このエントリーをはてなブックマークに追加

この画像でいうところの「サマリー」がタイトル、All Web Site Data が「サブタイトル」になりますが、
サブタイトルは ActionBar クラスの setSubtitle 関数で設定する事ができます。
いろいろな種類のフラグメント、ビューを持つアプリの場合は、setTitle, setSubtitle を使って ActionBar に現在の状態を表示するようにしておくと良さそうです。

参考 : Action Bar Title with more than one line?