2012年12月23日日曜日

InputFilterとValidator

ValidatorじゃなくてAndroidには InputFilterがあるじゃないか?って話がでたので...

フィルタとは (filter): - IT用語辞典バイナリ
フィルターは、受け取ったデータに対して何らかの処理や加工を行った上で出力すること

Androidでは以下のInputFilterが提供されています。
InputFilter | Android Developers

public interface

InputFilter (view source)

android.text.InputFilter
Known Indirect Subclasses
DateKeyListenerFor entering dates in a text field. 
DateTimeKeyListenerFor entering dates and times in the same text field. 
DialerKeyListenerFor dialing-only text entry
As for all implementations of KeyListener, this class is only concerned with hardware keyboards. 
DigitsKeyListenerFor digits-only text entry
As for all implementations of KeyListener, this class is only concerned with hardware keyboards. 
InputFilter.AllCapsThis filter will capitalize all the lower case letters that are added through edits. 
InputFilter.LengthFilterThis filter will constrain edits not to make the length of the text greater than the specified length. 
LoginFilterAbstract class for filtering login-related text (user names and passwords)  
LoginFilter.PasswordFilterGMailThis filter is compatible with GMail passwords which restricts characters to the Latin-1 (ISO8859-1) char set. 
LoginFilter.UsernameFilterGMailThis filter rejects characters in the user name that are not compatible with GMail account creation. 
LoginFilter.UsernameFilterGenericThis filter rejects characters in the user name that are not compatible with Google login. 
NumberKeyListenerFor numeric text entry
As for all implementations of KeyListener, this class is only concerned with hardware keyboards. 
TimeKeyListenerFor entering times in a text field. 


これら以外のフィルターを行いたい場合はカスタムフィルタを作成する必要があります。
作成の方法はこちらが参考になると思います。
EditTextにカスタムInputFilterを適用して入力値の制限を行う | MoaiApps Labo 
Y.A.M の 雑記帳: Android Filterを使ってみた

フィルタは、特定の文字列以外は認めない機能です。
入力された文字列を検査する機能では有りません。
それがバリデータになります。

バリデータとは 「バリデーター」 (validator): - IT用語辞典バイナリ
入力されたデータが仕様にそって適切に記述されているかを判断し、不適切な箇所があった場合にはエラーとして通知する。


特定の文字以外を入力させなくしているから、バリデータは不要でしょうか?

そんなことは無いと思います。
  • ユーザーID/パスワードなどの必須項目は入力されているか? 
  • パスワードはある桁以上であるか?特定の文字列を含んでいるか?
  • パスワードの確認入力などの二つの項目が同じであるか?
  • 電話番号や郵便番号などの"-"区切りのフォーマットが正しいか?
など、入力確定後に検査をしなければならないと思います。
※フォーマット付き項目については、項目を分けたらとか、フォーマット無しで入力させたらいいじゃないかとかありますけどね。鶴様が既存PCのフォームがフォーマット付きだから踏襲し...(ry

検査のタイミングですが、
  • フォーカスが外れたタイミング
  • ボタン押下などアクションのタイミング
となると思います。
※必須項目の検査は、全ての入力項目を確定させたアクションでしか行えないと思います。

AndroidのEditViewの場合、

  1. android:inputType 入力制御 (IMEのパネルを制御)
  2. InputFilter 入力制御 入力されたタイミングでフィルタ
  3. Formatter 文字列入力後にフォーマット
  4. Validator 最終的な文字列の検査

の順に処理をすればいいんじゃないかと思います。

バリデータなんて古いぜこっちの方がクールだぜ。ってのあったら教えてほしいな〜。

でわでわ

2012年12月20日木曜日

ViewHolderとValidatorForAndroid

Android Advent Calendar 2012 #androidadvent2012
12/20(裏) 担当の @katsummy です。表担当は@stachibanaさんとなります。

よろしくお願いします〜。

初心者向けなニッチな内容です。
シニアの方にはこんなの自分で実装してるよ〜な内容になるかもです。

■ViewHolder

XMLでレイアウトを作成した後、ActivityまたはFragmentの中で扱う為に、inflateしてViewをfindViewByIdして、メンバー変数に格納しますよね?

結構面倒くさくないですか?
  • レイアウトファイルからidを抜き出し
  • メンバー変数にViewのクラスを記述して、mから始める名前で定義
  • mXxxXxx = (View) findViewById(R.id.xxxx); を記述

Viewの項目が増えるとコピペの作業と文字列の置き換えが大変。
  android:id="@+id/first_name"

EditText mFirstName;
mFirstName = (EditText) findViewById(R.id.first_name);

また、Activity/Fragmentにメンバー変数が多くなり見難くなると思います。

そこで、まずは、これらViewの項目の定義を外だしにしてしまいましょう。

1つのレイアウトに対してViewHolderクラスを作成します。
ViewHolderってのはListAdapterのテクニックでも使用しているあれです。

ViewHolderには、基本的にプロパティ値としてViewも持ちます。


使用前

public class EditActivity extends Activity implements OnClickListener { public EditText mFirstName; public EditText mLastName; public TextView mLabelLastName; public TextView mLabelFirstName; public Spinner mSex; public FrameLayout mFrameSex; public TextView mLabelSex; public TextView mLabelBirthday; public DatePicker mBirthday; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mFirstName = findViewById(R.id.first_name); mLastName = findViewById(R.id.last_name); mLabelLastName = findViewById(R.id.label_last_name); mLabelFirstName = findViewById(R.id.label_first_name); mSex = findViewById(R.id.sex); mFrameSex = findViewById(R.id.frame_sex); mLabelSex = findViewById(R.id.label_sex); mLabelBirthday = findViewById(R.id.label_birthday); mBirthday = findViewById(R.id.birthday); } }
使用後
public class EditActivity extends Activity implements OnClickListener { ViewHolder mViewHolder; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mViewHolder = new ViewHolder(getWindow().getDecorView()); } } public class ViewHolder { public EditText mFirstName; public EditText mLastName; public TextView mLabelLastName; public TextView mLabelFirstName; public Spinner mSex; public FrameLayout mFrameSex; public TextView mLabelSex; public TextView mLabelBirthday; public DatePicker mBirthday; public ViewHolder(View v) { mFirstName = findViewById(v, R.id.first_name); mLastName = findViewById(v, R.id.last_name); mLabelLastName = findViewById(v, R.id.label_last_name); mLabelFirstName = findViewById(v, R.id.label_first_name); mSex = findViewById(v, R.id.sex); mFrameSex = findViewById(v, R.id.frame_sex); mLabelSex = findViewById(v, R.id.label_sex); mLabelBirthday = findViewById(v, R.id.label_birthday); mBirthday = findViewById(v, R.id.birthday); } @SuppressWarnings("unchecked") private T findViewById(View v, int id) { return (T) v.findViewById(id); } }


これで、Activity/Fragmentから大量のメンバー変数が消えて、コードを追いやすくなるのではないでしょうか?

でも、ViewHolderにいろいろ書くのは変ってないし、やっぱり面倒くさいよね?

なので、レイアウトからViewHolderを作成するスクリプトを作成しました。


レイアウトからViewHolderクラスを作成

なお、指定するレイアウトは予めフォーマーッター(Ctrl+Shift+F)でフォーマットしておく必要があります。要は、Viewタグの後にIDアトリビュートが定義されてる必要があります。


ValidatorForAndroid


社員管理・顧客管理などの編集画面で、一度に多くの項目を入力を行わせるアプリを作成することもあります。

※PCアプリ/Webアプリの機能をそのままAndroid側でも実現させようとする要件ですね。

入力項目で使用するEditViewには、InputTypeでIMEを制御はありますがValidatorは備わってません。エラーメッセージを表示する機能はあるけど、バリデーションは自分でやってねって感じでしょうか。

StrutsみたいなValidator欲しいなっと思ったので作ってみました。

granoeste/ValidatorForAndroid · GitHub 


StrutsみたいなValidator欲しいなっと思ったので作ってみました。
apache commons validatorを参考にしています。

サンプル1 EditViewとValidatorの組み合わせ
// Validators Validators mValidators = new Validators(); EditText mTextPersonName; EditText mTextPassword; Button mButton; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextPersonName = (EditText) findViewById(R.id.textPersonName); mTextPassword = (EditText) findViewById(R.id.textPassword); mButton = findViewByIdAndCast(v, R.id.button); // Define validators for views mValidators.put(mTextPersonName, new Validator[] { new RequiredValidator("Be sure to input. "), new MaxLengthValidator(30, "Being allowed is to 30 characters. "), }); mValidators.put(mTextPassword, new Validator[] { new RequiredValidator("Be sure to input. "), new RangeValidator(8, 16, "Being allowed is from 8 to 16 characters. "), }); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Execute Validators mValidators.clearError(); if (mValidators.isValid()) { Toast.makeText(getApplicationContext(), "It has some errors. ", Toast.LENGTH_SHORT).show(); } } }); }

サンプル2 バリデータ付きEditView
// Validators Validators mValidators = new Validators(); net.granoeste.validator.ValidatableEditText mTextPersonName; net.granoeste.validator.ValidatableEditText mTextPassword; Button mButton; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextPersonName = (net.granoeste.validator.ValidatableEditText) findViewById(R.id.textPersonName); mTextPassword = (net.granoeste.validator.ValidatableEditText) findViewById(R.id.textPassword); mButton = findViewByIdAndCast(v, R.id.button); // add ValidatableEditText to Validators mValidators.put(mTextPersonName); mValidators.put(mTextEmailAddress); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Execute Validators mValidators.clearError(); if (mValidators.isValid()) { Toast.makeText(getApplicationContext(), "It has some errors. ", Toast.LENGTH_SHORT).show(); } } }); } 

こんな感じで使用します。

詳しくは、GitHubに上げてるのでソース見てね。
解んなかったら聞いて下さい。
※バグがあったらIssue書いてくれても、ForkしてPull Requesteしてくれてもいいんだよ。

以上になります。

明日は、@out_of_kayaさん と @tarotaro4さん が担当されます。



でわでわ。
メリークリスマス&よいおとしを。

2012年12月4日火曜日

Google Maps Android API v2 がリリースされたようですね。


Google Maps Android API v2 がリリースされたようですね。


とりあえず、サンプル動かしてみる。
Developer Guide 通りにやるだけだけどw

1. Sample Codeをインポート
  1. まずは、Google Play Services のライブラリをインポート
    1. Select File > Import > Android > Existing Android Code Into Workspace
    2. /extras/google/google_play_services/libproject/google-play-services_lib
  2. 続いて、サンプルアプリケーションのインポート
    1. Select File > Import > Android > Existing Android Code Into Workspace
    2. /extras/google/google_play_services/samples/maps
  3. ライブラリーの追加
    1. Context Menu > Android Tools > Add Support Library...
2. API Keyの取得
  1. Google APIs Console にアクセス
  2. Services 選択
  3. Google Maps Android API v2 をONにする。
    • MapsのAPIがいくつかあるけど、Androidってついてるやつです。
  4. API Access 選択
  5. [Create new Android key...] 選択
  6. テキストボックスに、証明書のSHA1 fingerprintとパッケージをセミコロンで区切って入力
    • 証明書のSHA1 fingerprintは以下のコマンドで確認出来る。 
    • keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
  7. 登録したら、API keyを確認

3. AndroidManifest.xmlにAPI Keyを記述

4. ビルドして実機で確認


Google Play 開発者サービスがインストールされてないと、以下のようにインストールするように言われます。
※しかし、開発者サービスって表示どうにかなりませんかね。理解してないユーザのGoogle Playのコメントが酷い。。。

サンプルソースチラ見

  • MapはFragmentで提供されてます!
  • ようやく、FragmentにMAPが!
  • しかも、SupportMapFragment!
  • 2.xでも動くようですな
  • Galaxy S (2.3.?)でも動いてました。
  • AndroidManifestのには、OpenGL ES 2.0. の定義があるので、2.2(Lv8)以上でないとだめなようです。

くわしくは、Google Maps Android API v2 — Google Developers を見る必要がありますね。


以上 


2012年9月10日月曜日

4.0(ICS)でCamera.takePicture呼び出し時にシャッター音が鳴らない?

4.0(ICS)で、Camera シャッター音が鳴らなくなりました。???

よくある実装サンプル。 mCamera.autoFocus(new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { camera.takePicture(null, null, pictureCallback); } }); private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { // save picture } }; takePictureの第1引数のShutterCallbackをnull で呼び出してます。
4.0(ICS)以降では、nullにしているとシャッター音が鳴らなくなっているようです。

空実装のShutterCallbackを指定すると鳴るようになりました。 mCamera.autoFocus(new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { camera.takePicture(shutterCallback, null, pictureCallback); } }); private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { // save picture } }; private Camera.ShutterCallback shutterCallback = new Camera.ShutterCallback() { @Override public void onShutter() { // NOP } };
GALAXY NEXUS SC-04D で試しましたが、端末依存とかあるのでしょうか。。。

2012年8月17日金曜日

FragmentからstartActivityForResult

FragmentからstartActivityForResultすると、戻ってきた時にActivityのonActivityResultとFragmentのonActivityResultが呼ばれるようなので。。。覚え書き

ソースコードを見るとよくわかるね。

startActivityForResult の動作

Fragment
ActivityのstartActivityFromFragmentを呼んでます。 /** * Call {@link Activity#startActivityForResult(Intent, int)} on the fragment's * containing Activity. */ public void startActivityForResult(Intent intent, int requestCode) { if (mActivity == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } mActivity.startActivityFromFragment(this, intent, requestCode); }
FragmentActivity
requestCodeをビット演算させてActivityのsuper.startActivityForResultを呼んでます。 /** * Called by Fragment.startActivityForResult() to implement its behavior. */ public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { if (requestCode == -1) { super.startActivityForResult(intent, -1); return; } if ((requestCode&0xffff0000) != 0) { throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); } super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff)); }

onActivityResult の動作

FragmentActivity
requestCodeをビット演算でFragmentからstartActivityForResultActivityが実行されてるかチェックして、 FragmentのonActivityResultの呼び出しを制御しています。 /** * Dispatch incoming result to the correct fragment. */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { int index = requestCode>>16; if (index != 0) { index--; if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) { Log.w(TAG, "Activity result fragment index out of range: 0x" + Integer.toHexString(requestCode)); return; } Fragment frag = mFragments.mActive.get(index); if (frag == null) { Log.w(TAG, "Activity result no fragment exists for index: 0x" + Integer.toHexString(requestCode)); } else { frag.onActivityResult(requestCode&0xffff, resultCode, data); } return; } super.onActivityResult(requestCode, resultCode, data); }
Fragment
/** * Receive the result from a previous call to * {@link #startActivityForResult(Intent, int)}. This follows the * related Activity API as described there in * {@link Activity#onActivityResult(int, int, Intent)}. * * @param requestCode The integer request code originally supplied to * startActivityForResult(), allowing you to identify who this * result came from. * @param resultCode The integer result code returned by the child activity * through its setResult(). * @param data An Intent, which can return result data to the caller * (various data can be attached to Intent "extras"). */ public void onActivityResult(int requestCode, int resultCode, Intent data) { }

Y.A.M の 雑記帳: Android Support Package の Fragment から startActivityForResult() を使うときの注意点

2012年8月2日木曜日

XOOM (海外ROM版) JellyBean (4.1.1) アップデート

XOOM (海外ROM版)に、JellyBean (4.1.1)のOTAが来たのでアップデートしてみた。

UI
Nexus7のNavigationBarのPhone UIとは異なり、CombindBarのTablet UIになってます。


ホームボタン長押しで、Google Nowが表示されます。

残念な事に Portrait にしか対応してませんでした。。。



システム情報
Android Version 4.1.1
Kernel Version 2.xのまま (Nexus 7は3.x)

Nortification
4.1から追加された BigStyleにも対応してます。
通知の拡張は、下から上に2タッチでスワイプさせます。




こんな感じ〜。


2012年7月18日水曜日

ライブラリにソースとJavadocをアタッチ

ようやく、ADT r20で、libsのライブラリのソースとJavadocのjar/zipを添付出来るようになりました。


その手順メモです。

元ネタ
Issue 28658 - android - Attaching source code for jar files in Android dependencies


1.  libsのライブラリと同じ名前でpropertiesファイルを作成します。


2. propertiesファイルに、srcとdocのパスorファイルを記述します。
android-support-v4.jar.properties
src=/Applications/android-sdk/extras/android/support/v4/src/
doc=
gson-2.2.1.jar.properties
src=../libsrc/gson-2.2.1-sources.jar
doc=../libsrc/gson-2.2.1-javadoc.jar
 ※ファイルはzipでもOKでした。

3. プロジェクトのAndroid Dependencies のjarアイコンに ドキュメントマークが付きます。

4. プロジェクト -> Properties -> Java Build Path -> Libraries の Android Dependencies の各ライブラリにソースとJavaDocがアタッチされています。


設定が反映されない場合、プロジェクトを閉じて開き直すと良いかもしれません。

一応 githubにプロジェクトを置いておきます。
https://github.com/granoeste/UseThirdPartyLib

2012年6月15日金曜日

PNGから不要なメタデータの削除しよう。

Icon Design Guidelines に記載されてるように PNGの圧縮を行う。

When saving image assets, remove unnecessary metadata

Although the Android SDK tools will automatically compress PNGs when packaging application resources into the application binary, a good practice is to remove unnecessary headers and metadata from your PNG assets. Tools such as OptiPNG or Pngcrush can ensure that this metadata is removed and that your image asset file sizes are optimized.


PNGから不要なメタデータの削除




参考
  png圧縮をして容量、メモリにやさしいアプリ制作 - 素人のアンドロイドアプリ開発日記


ツール
  PNGGauntlet - PNG Compression Software
  PNGOUTWin - A Batch Mode PNG Optimizer and Converter for Windows.
  OptiPNG
  ImageOptim — make websites and apps load faster (Mac app)




Mac環境なので、ImageOptimを使用しました。


メタ情報は次のコマンドで確認することができます。
 /Applications/ImageOptim.app/Contents/MacOS/pngcrush -n -v xxxx.png




5% ~ 10% ものによっては 30% ファイルサイズが削減されてました。




画像が劣化しているかどうか確認したかったのですが、いいツールが有りませんでした。。。

2012年5月24日木曜日

2012 夏モデル端末の ナビゲーションバー 対応状況

端末下部のハードウェアキーが、4.0から標準のタッチパネル上のソフトウェアキー置き換わった端末のチェックとメモ

注)
 ホームページ上の写真から判断しているので間違えがあるかもしれません。
 スペック一覧は他のサイトでまとめられているのでそっちを確認してください。