2013年3月15日金曜日

[Android TIPS] Bitmap.createBitmap ではなく Canvas を使って画像を回転する

SD カードに配置した画像 (img_src.jpg) をサイズ圧縮し、回転なし画像 (img_dst1.jpg) と
回転あり画像 (img_dst2.jpg) として保存するアプリを作ってみました。
Matrix + Bitmap.createBitmap で回転しようとすると Out of Memory で落ちてしまうので、
回転した Canvas に画像を描画することで実現してみました。

activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dip" >

    <ImageView
        android:id="@+id/imgPreview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sample.bitmaprotate"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="17" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.sample.bitmaprotate.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
MainActivity.java
package com.sample.bitmaprotate;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

import android.os.Bundle;
import android.os.Environment;
import android.widget.ImageView;
import android.widget.Toast;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;

public class MainActivity extends Activity {

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

    // プレビュー用ウィジェット
    ImageView imgPreview = (ImageView) findViewById(R.id.imgPreview);

    // SD カード内の対象画像の読み込み (img_src.jpg)
    String path = Environment.getExternalStorageDirectory() + "/img_src.jpg";
    if (!new File(path).exists()) {
      Toast.makeText(this, "エラー: SDカードに img_src.jp を格納して再度実行してください。", Toast.LENGTH_LONG).show();
      return;
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = 2; // 1/4のサイズで読み込み
    options.inPreferredConfig = Bitmap.Config.RGB_565; // 低クオリティでの読み込み
    Bitmap bmpSrc = BitmapFactory.decodeFile(path, options);
    // [圧縮済 回転'なし'] 画像を SD カード内に保存 (img_dst1.jpg)
    save(bmpSrc, "img_dst1.jpg");

    // 90度回転したキャンバスを用意
    Bitmap bmpDst = Bitmap.createBitmap(bmpSrc.getHeight(), bmpSrc.getWidth(), Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas(bmpDst);
    canvas.save();
    canvas.rotate(90, bmpSrc.getWidth() / 2, bmpSrc.getHeight() / 2);

    // 回転後の中心位置のずれを考慮して画像を描画
    float diff = bmpSrc.getWidth() / 2 - bmpSrc.getHeight() / 2;
    canvas.drawBitmap(bmpSrc, diff, diff, null);

    // 回転をもとに戻す → 結果的に描画した画像が回転することとなる
    canvas.restore();

    // プレビュー
    imgPreview.setImageBitmap(bmpDst);

    // [圧縮済 回転'あり'] 画像を SD カード内に保存 (img_dst2.jpg)
    save(bmpDst, "img_dst2.jpg");
  }

  /**
   * 画像の保存
   * 
   * @param bmp 対象画像
   * @param name ファイル名
   */
  private void save(Bitmap bmp, String name) {
    OutputStream out = null;
    String path = Environment.getExternalStorageDirectory() + "/" + name;

    try {
      File file = new File(path);
      if (file.createNewFile()) {
        out = new FileOutputStream(file);
        bmp.compress(CompressFormat.JPEG, 70, out);
      }

    } catch (Exception ex) {
      ex.printStackTrace();

    } finally {
      if (out != null) {
        try {
          out.close();
        } catch (Exception ex2) {}
      }
    }
  }
}
本当はオリジナル画像の解像度を落とさず無圧縮で回転したかったのですがその方法は分からず...
NDK を使わないといけなかったりするもんでしょうか?

2013年1月14日月曜日

[Android TIPS] 色々な情報をリストに表示する

色々な情報をリストに表示するというのをやってみました。
表示対象は画像と文字列ですが、予め res/drawable とコード内に用意しました
国旗の画像は ここ から取得し、flag01.png ~ flag08.png とし、res/drawable 配下に配置しています。

サンプル野良apkはこちらからどうぞ。

レイアウトはこんな感じ。

activity_country.xml

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

    <HorizontalScrollView
        android:id="@+id/scrollView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#FFFFFF" >

        <LinearLayout
            android:id="@+id/listCountry"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:paddingLeft="4dip"
            android:paddingRight="4dip" />
    </HorizontalScrollView>

</FrameLayout>

info_country.xml

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >

    <TableRow>
        <ImageView
            android:id="@+id/imgFlag"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_span="2"
            android:scaleType="center" />
    </TableRow>

    <TableRow>
        <TextView
            android:layout_width="38dip"
            android:layout_height="wrap_content"
            android:text="国名:" />

        <TextView
            android:id="@+id/txtTitle"
            android:layout_width="142dip"
            android:layout_height="wrap_content" />
    </TableRow>

    <TableRow>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="首都:" />

        <TextView
            android:id="@+id/txtCapital"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </TableRow>

    <TableRow>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="通貨:" />

        <TextView
            android:id="@+id/txtCurrency"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </TableRow>

</TableLayout>

コードはこんな感じ。

package com.sample.sampleapp;

import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.app.Activity;

public class CountryActivity extends Activity {

 /** WRAP_CONTENT */
 private static final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;

 /**
  * 国情報保持クラス
  */
 private static class Country {
  /** 国旗画像のリソース ID */
  public int flag;
  /** 国名 */
  public String title;
  /** 首都 */
  public String capital;
  /** 通貨 */
  public String currency;

  public Country(int flag, String title, String capital, String currency) {
   this.flag = flag;
   this.title = title;
   this.capital = capital;
   this.currency = currency;
  }
 }

 /** 国情報リスト */
 private static final Country[] mCountries = {
   new Country(R.drawable.flag01, "バハマ", "ナッソー", "バハマ・ドル"),
   new Country(R.drawable.flag02, "バングラディシュ", "ダッカ", "タカ"),
   new Country(R.drawable.flag03, "ベナン", "ポルトノボ", "CFAフラン"),
   new Country(R.drawable.flag04, "カメルーン", "ヤウンデ", "CFAフラン"),
   new Country(R.drawable.flag05, "コロンビア", "ボゴタ", "コロンビア・ペソ"),
   new Country(R.drawable.flag06, "デンマーク", "コペンハーゲン", "デンマーク・クローネ"),
   new Country(R.drawable.flag07, "エクアドル", "キト", "アメリカ合衆国ドル"),
   new Country(R.drawable.flag08, "エルサルバドル", "サンサルバドル", "アメリカ合衆国ドル"),
 };

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_country);

  // ウィジェットの初期化
  initWidget();
 }

 /**
  * ウィジェットの初期化
  */
 private void initWidget() {
  int len = mCountries.length;
  for (int i = 0; i < len; i++) {
   addCountry(i);
  }
 }

 /**
  * 国情報の追加
  * 
  * @param index
  */
 private void addCountry(int index) {
  // レイアウトをインフレート
  View v = this.getLayoutInflater().inflate(R.layout.info_country, null);

  // 国旗
  ImageView imgFlag = (ImageView) v.findViewById(R.id.imgFlag);
  imgFlag.setImageResource(mCountries[index].flag);

  // 国名
  TextView txtTitle = (TextView) v.findViewById(R.id.txtTitle);
  txtTitle.setText(mCountries[index].title);

  // 首都
  TextView txtCapital = (TextView) v.findViewById(R.id.txtCapital);
  txtCapital.setText(mCountries[index].capital);

  // 通貨
  TextView txtCurrency = (TextView) v.findViewById(R.id.txtCurrency);
  txtCurrency.setText(mCountries[index].currency);

  // レイアウトパラメータの調整
  LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(WC, WC);
  params.setMargins(4, 8, 4, 8);
  v.setLayoutParams(params);

  // リストに追加
  LinearLayout listCountry = (LinearLayout) findViewById(R.id.listCountry);
  listCountry.addView(v);
 }
}

TableLayout で幅を設定するのに、任意業の要素1つ1つに幅を設定しなければいけないものなのかしらん。

[Android TIPS] キーワード履歴に使えそうなボタン配置

キーワード履歴に使えそうなボタン配置を作ってみました。
こういう配置を自動的にやってくれるレイアウトとかはないですよね。たぶん(汗)





サンプル野良apkはこちらからどうぞ。

レイアウトはこんな感じ。

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <EditText
            android:id="@+id/editInput"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <Button
            android:id="@+id/btnAdd"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Add" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/layoutTag1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <LinearLayout
        android:id="@+id/layoutTag2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <LinearLayout
        android:id="@+id/layoutTag3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

コードはこんな感じ。

package com.sample.sampleapp;

import java.util.ArrayList;

import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.app.Activity;
import android.graphics.Paint;

public class TagActivity extends Activity {

 /** WRAP_CONTENT */
 private final int WC = ViewGroup.LayoutParams.WRAP_CONTENT;

 /** タグのラベル最大長 */
 private final int MAX_LABEL_LEN = 8;

 /** ボタンのパディング */
 private final int BTN_PADDING = 8;

 /** 横幅に占めるボタンに必要な最低幅の割合 */
 private final float RATIO_BUTTONS = 0.8f;

 /** タグを表示するリニアレイアウト */
 private final int[] mListTagLayout = { R.id.layoutTag1, R.id.layoutTag2, R.id.layoutTag3 };

 /** タグボタンのリスト */
 private final ArrayList<Button> mListTagButton = new ArrayList<Button>();

 /** 画面幅 */
 private int mScreenWidth = 0;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_tag);

  // 画面幅の取得
  WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
  mScreenWidth = wm.getDefaultDisplay().getWidth();

  // ウィジェットの初期化
  initWidget();
 }

 /**
  * ウィジェットの初期化
  */
 private void initWidget() {
  // 「add」ボタンを非活性化
  final Button btnAdd = (Button) findViewById(R.id.btnAdd);
  btnAdd.setEnabled(false);

  // 文字が入力されている場合に「add」ボタンを活性化
  final EditText editInput = (EditText) findViewById(R.id.editInput);
  editInput.addTextChangedListener(new TextWatcher() {
   @Override
   public void onTextChanged(CharSequence s, int start, int count, int after) {
   }

   @Override
   public void beforeTextChanged(CharSequence s, int start, int before, int count) {
   }

   @Override
   public void afterTextChanged(Editable s) {
    btnAdd.setEnabled(0 < s.length());
   }
  });

  // 「add」ボタン押下時に入力された文字列を元にタグを生成
  btnAdd.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View v) {
    // ボタンを生成
    final Button btn = new Button(TagActivity.this);
    btn.setText(getInputText());
    btn.setPadding(BTN_PADDING, BTN_PADDING, BTN_PADDING, BTN_PADDING);
    btn.setOnClickListener(new OnClickListener() {
     @Override
     public void onClick(View v) {
      editInput.setText(btn.getText());
     }
    });
    // 同一の内容があれば削除
    for (int i = mListTagButton.size() - 1; 0 <= i; i--) {
     if (mListTagButton.get(i).getText().equals(btn.getText())) {
      mListTagButton.remove(i);
     }
    }
    mListTagButton.add(btn);

    // ボタンを配置
    int index = mListTagButton.size() - 1;
    for (int id : mListTagLayout) {
     LinearLayout targetLayout = (LinearLayout) findViewById(id);
     targetLayout.removeAllViews();

     // リストの末尾 (直近に生成されたボタン) から配置
     while (0 <= index) {
      Button target = mListTagButton.get(index);

      // 同一行のボタンに必要なボタン幅を算出
      int totalWidth = 0;
      for (int i = 0; i < targetLayout.getChildCount(); i++) {
       Button child = (Button) targetLayout.getChildAt(i);
       totalWidth += getVirtualButtonWidth(child);
      }
      // 同一行のボタン幅と次に追加するボタンの幅が規定値より大きければ次の行へ
      if (mScreenWidth * RATIO_BUTTONS < totalWidth + getVirtualButtonWidth(target)) {
       break;
      }

      // 対象行へボタンを配置
      LayoutParams params = new LinearLayout.LayoutParams(0, WC);
      params.weight = getVirtualButtonWidth(target);
      targetLayout.addView(target, params);

      index--;
     }
    }

    editInput.setText("");
   }
  });
 }

 /**
  * 入力文字列の取得 (規定文字数以上は省略)
  * 
  * @return
  */
 private String getInputText() {
  final EditText editInput = (EditText) findViewById(R.id.editInput);
  String input = editInput.getText().toString();

  if (MAX_LABEL_LEN < input.length()) {
   input = input.substring(0, MAX_LABEL_LEN) + "...";
  }

  return input;
 }

 /**
  * ボタンの仮想幅を取得
  * 
  * @param button
  * @return
  */
 private int getVirtualButtonWidth(Button button) {
  Paint p = new Paint();
  p.setTextSize(button.getTextSize());
  int labelWidth = (int) p.measureText(button.getText().toString());
  return (labelWidth + button.getTotalPaddingLeft() + button.getTotalPaddingRight());
 }
}

「ボタンの仮想幅」はそのボタンを表示した時にラベルがきちんと見える最小の幅を算出したかったので書いてみましたが、これでよいかしらん。でも「i」とか表示した際に潰れることはないから、これでもよいかしらん。

2012年6月21日木曜日

[Android TIPS] iPhone みたいな ToggleButton

iPhone みたくスイッチ時にアニメーションする ToggleButton を自作してみました。
トグルボタンをタップすると8段階にアニメーションして状態が切り替わります。


サンプルの野良 apk はこちらからどうぞ。


パッケージ・エクスプローラーはこんなかんじ。


追加したのは下記のファイル
  • CustomToggleButton.java
  • toggle_off.xml
  • toggle_on.xml
  • toggle01~09.png

toggle01~09.png のボタンはこんなかんじ。


では、コードを順番に。まずは CustomToggleButton.java から。
package com.sample.togglesample;

import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;

/**
 * カスタムトグルボタンクラス
 */
public class CustomToggleButton extends ImageView {

 /** 値変更イベントリスナ */
 private OnToggleChangeListener mListener;

 /** チェック状態 */
 private boolean mChecked;

 /** チェック状態: setter */
 public boolean isChecked() {
  return mChecked;
 }

 /** チェック状態: getter */
 public void setChecked(boolean checked) {
  mChecked = checked;

  // チェック状態に応じて画像を変更
  if (mChecked) {
   this.setBackgroundResource(R.drawable.toggle09);
  } else {
   this.setBackgroundResource(R.drawable.toggle01);
  }

  // イベント発火
  if (mListener != null) {
   mListener.onChange(this, mChecked);
  }
 }

 /**
  * コンストラクタ
  * 
  * @param context
  * @param attrs
  */
 public CustomToggleButton(Context context, AttributeSet attrs) {
  super(context, attrs);
  
  // ウィジェットの初期化
  initWidget();
 }

 /**
  * ウィジェットの初期化
  */
 private void initWidget() {
  // 初期値は false に設定
  setChecked(false);

  // クリックされた際に自身の値を変更する
  setOnClickListener(new OnClickListener() {
   public void onClick(View v) {
    // 値を反転
    CustomToggleButton.this.setChecked(!CustomToggleButton.this.isChecked());

    // 実施するアニメーションを選択
    CustomToggleButton.this.setBackgroundResource(
      (CustomToggleButton.this.isChecked() ?
        R.drawable.toggle_on : R.drawable.toggle_off));

    // アニメーション開始
    AnimationDrawable frameAnimation = (AnimationDrawable)
      CustomToggleButton.this.getBackground();
    frameAnimation.start();
   }
  });
 }

 /**
  * イベントリスナの設定
  * 
  * @param listener
  */
 public void setOnToggleChangeListener(OnToggleChangeListener listener) {
  mListener = listener;
 }

 /**
  * 値変更イベントインタフェース
  */
 public interface OnToggleChangeListener {
  public void onChange(View v, boolean isChecked);
 }
}


次は toggle_off.xml。


    
    
    
    
    
    
    
    
    




次は toggle_on.xml。off の順番を逆にしただけ。


    
    
    
    
    
    
    
    
    




最後に main.xml と



    
    

    
    




MainActivity.java
package com.sample.togglesample;

import com.sample.togglesample.CustomToggleButton.OnToggleChangeListener;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

/**
 * サンプル画面クラス
 */
public class MainActivity extends Activity {

 /** トグルボタンの状態表示テキストビュー */
 private TextView mTxtValue;

 /** カスタムトグルボタンウィジェット */
 private CustomToggleButton mToggleButton;

 /**
  * onCreate
  */
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  // トグルボタンが押された際に値をテキストビューに表示
  mToggleButton = (CustomToggleButton) findViewById(R.id.customToggle);
  mToggleButton.setOnToggleChangeListener(new OnToggleChangeListener() {
   public void onChange(View v, boolean isChecked) {
    // トグルボタンの値を表示
    refreshToggleValue();
   }
  });

  // トグルボタンの値を表示
  mTxtValue = (TextView) findViewById(R.id.txtValue);
  refreshToggleValue();
 }

 /**
  * トグルボタンの値を表示
  */
 private void refreshToggleValue() {
  if (mTxtValue != null && mToggleButton != null) {
   mTxtValue.setText(mToggleButton.isChecked() ? "On" : "Off");
  }
 }
}

こんなかんじー。 スワイプして切り替えれるようにしたいけど、まずはタップからやってみました :-)

2012年3月20日火曜日

[Android TIPS] Monkey Runner でアプリ自動実行

Monkey Runner について、これまでに
「一通りやったよ」というような記事があまりなかったと思うので、
今回は Monkey Runner でのアプリ自動実行をご紹介。


Monkey Runner というのは Andorid の操作を予め
決めておいて、それを基に自動実行できる仕組みです。
自動実行の途中でスクリーンショットもとれるので、
テストにはもってこいなわけです。

アプリのレイアウトに依存するところが大きいのですが、
複数の条件を切り替えてテストするような場面や、
長時間実行によるリソース使用量の変化を観測する
場合などに使えるツールです。


自動実行するアプリのサンプル
として、←のような四則演算を
行うアプリを取り上げます

アプリのソースは こちら からどうぞ。

まずは←のアプリをエミュレータで
実行できるように準備します。

また、Monkey Runner は
ボタンなどのウィジェットの操作を
座標を元に行うので、
エミュレータのサイズを 480x800
にします。




2012年1月30日月曜日

[Android TIPS] AlertDialogのボタンサイズを調整する

ダイアログのボタンサイズを調整したかったんすが、
ずっとやり方が分からなくて。苦手なテーマをいじったり、
自分でレイアウトを作ってみたり、色々やってたんすが、
ダイアログの下部に余白が残ってしまったりと、うまく
表示できなかったんです。

で、やっとやり方がわかったので書いてみました。

下記みたいなコードを書くと...

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setTitle("こんばんわ");
builder.setMessage("一杯いかが?");
builder.setPositiveButton("ちょうだい", null);
builder.setNeutralButton("うーん", null);
builder.setNegativeButton("いいやまたあとで", null);
Dialog dialog = builder.create();
dialog.show();


こんな感じでボタン幅が均等になって、ボタンラベルが
2行になっちゃいます。うまくシュリンクしてくれれば
1行で収まるのに...

そんな時には、下記のように書くと...

final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
final int FP = LinearLayout.LayoutParams.FILL_PARENT;
final LinearLayout.LayoutParams layoutParams = 
 new LinearLayout.LayoutParams(WC, FP, 1);

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setTitle("こんばんわ");
builder.setMessage("一杯いかが?");
builder.setPositiveButton("ちょうだい", null);
builder.setNeutralButton("うーん", null);
builder.setNegativeButton("いいやまたあとで", null);
Dialog dialog = builder.create();

dialog.setOnShowListener(new OnShowListener() {
 public void onShow(DialogInterface dialog) {
  Button btnPositive = ((AlertDialog)dialog).getButton(
   DialogInterface.BUTTON_POSITIVE);
  if (btnPositive != null) {
   btnPositive.setLayoutParams(layoutParams);
   btnPositive.setSingleLine();
  }
  
  Button btnNeutral = ((AlertDialog)dialog).getButton(
   DialogInterface.BUTTON_NEUTRAL);
  if (btnNeutral != null) {
   btnNeutral.setLayoutParams(layoutParams);
   btnNeutral.setSingleLine();
  }
  
  Button btnNegative = ((AlertDialog)dialog).getButton(
   DialogInterface.BUTTON_NEGATIVE);
  if (btnNegative != null) {
   btnNegative.setLayoutParams(layoutParams);
   btnNegative.setSingleLine();
  }
 }
});

dialog.show();


うまく収まってくれます。ポイントは ...

  • getButton() メソッドでボタンの参照がとれるけど、OnShowListener とかの DialogInterface 経由でとらないと null になっちゃいます。
  • setLayoutParams() メソッドで、幅を WRAP_CONTENT に、weight を1に設定します。
  • setSingleLine() メソッドで1行に収まるようにします。

そんなかんじー。

2012年1月9日月曜日

[Android TIPS] デバッグに有用な情報を取得する

前回の更新から1ヶ月半あけてしまった... まだ2回目なのに...
というようなことは最初から予想できたので気を取り直して... ;-)

今回は開発者がデバッグする際に有用になる情報を取得する方法です。

青空読手では下記の場合にデバッグ情報を GAE 経由で自分の Gmail にメールを投げています。

  • UncaughtExceptionHandler で補足したエラーが前回起動時にあった場合
  • 設定画面からコメント機能を通じてメッセージが送られた場合

収集しているデバッグ情報は下記のとおりです。

  • 端末情報
    • ブランド
    • 製造元
    • モデル
  • OS 情報
    • バージョン
    • SDK
    • ロケール
  • アプリ情報
    • バージョン
    • SharedPreferences の設定値

これらは下記のようなメソッドにより収集可能です。

public static String getDeviceInfo(Activity activity)
{
 String brand = Build.BRAND;
 String manufacturer = Build.MANUFACTURER;
 String model = Build.MODEL;
 
 DisplayMetrics metrics = new DisplayMetrics();
 activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
 int w = metrics.widthPixels;
 int h = metrics.heightPixels;
 
 return String.format("%s - %s - %s (%dx%d)", brand, manufacturer, model, w, h);
}

public static String getOsInfo()
{
 String relese = Build.VERSION.RELEASE;
 String sdk = Build.VERSION.SDK;
 Locale locale = Locale.getDefault();
 
 return String.format("%s (api: %s) %s", relese, sdk, locale);
}

public static String getVersion(Context context)
{
 try
 {
  PackageManager manager = context.getPackageManager();
  PackageInfo packInfo = manager.getPackageInfo(context.getPackageName(), 0);
  return packInfo.versionName;
 }
 catch (Exception e)
 {
  e.printStackTrace();
 }
 return "1.0.0";
}

とれる情報はこんな感じです。

  • KDDI - HTC - PC36100 (480x800), 2.3.4 (api: 10) ja_JP
  • KDDI - HTC - ISW12HT (540x960), 2.3.4 (api: 10) ja_JP
  • KDDI - KYOCERA - ISW11K (480x800), 2.3.5 (api: 10) ja_JP
  • KDDI - FUJITSU TOSHIBA MOBILE COMMUNICATIONS LIMITED - IS04 (480x854), 2.2.2 (api: 8) ja_JP
  • KDDI - FUJITSU TOSHIBA MOBILE COMMUNICATIONS LIMITED - ISW11F (720x1280), 2.3.5 (api: 10) ja_JP
  • KDDI - PANTECH - IS06 (480x800), 2.2.1 (api: 8) ja_JP
  • KDDI - SHARP - IS11SH (540x960), 2.3.3 (api: 10) ja_JP
  • samsung - samsung - SC-02C (480x800), 2.3.3 (api: 10) ja_JP
  • docomo - Sony Ericsson - SO-01C (480x854), 2.3.2 (api: 9) ja_JP
  • docomo - Sony Ericsson - SO-01D (480x854), 2.3.4 (api: 10) ja_JP
  • docomo - Sony Ericsson - SO-03C (480x854), 2.3.4 (api: 10) ja_JP
  • DOCOMO - FUJITSU - F-01D (1280x752), 3.2 (api: 13) ja_JP
  • dell - Dell Inc - 001DL (480x800), 2.2.2 (api: 8) ja_JP
  • softbank_jp - HTC - 001HT (480x800), 2.3.3 (api: 10) ja_JP
  • SBM - SHARP - SBM003SH (800x480), 2.2.1 (api: 8) ja_JP
  • LGE - LGE - L-04C (320x480), 2.2.2 (api: 8) ja_JP
  • acer - Acer - E140 (240x320), 2.2 (api: 8) ja_JP
  • Huawei - HUAWEI - Ideos (240x320), 2.2.1 (api: 8) ja_JP
  • ZiiLABS - Creative Technology Ltd - ZiiO7 (800x480), 2.2.1 (api: 8) ja
  • emxx - renesas - Full Android (800x480), 2.2.1 (api: 8) ja_JP

上記メソッドではアプリのバージョンは AndroidManifest.xml の android:versionName 属性の値がとれてきます。

そんなかんじー。