2011-02-02
■[github][CalendarView][nfc-felica] githubにリポジトリを作成しました
CalendarViewとnfc-felicaは手慣れた環境であるGoogle Codeに、これまた使い慣れたSVNリポジトリを作成したのだが、「外部からの修正を期待するような場合はgitのほうがよいよ」というアドバイスを頂いたので、まずは慣れるために作ってみることにした。
Kazzz/CalendarView - GitHub
Kazzz/nfc-felica - GitHub
少しの間両方で運用してみて、使えそうだったらgithub側に全面的に移行しようと考えている。
2011-01-21
■[Android][CalendarView]CalendarViewが採用されました
Android Calendarview - Project Hosting on Google Code
とある商用アプリケーションで使って頂けるとのことです。
作リ手側としてはとても光栄なことなので、実際にリリースされた時には(許可が得られたら)採り上げたいと思います。
2010-11-19
■[Android][CalendarView]QuickActionの使い所
簡単に使えることが解ったQuickActionだが先日のエントリで妄想していた"バンド"として実際にどのようなユースケースで実装すれば良いかを考えてみた。
いきなり画面を作る。
このようにアクティビティにカレンダを配置して、カレンダの情報を基点にして何かの処理を行う。よくあるのはGoogle Calendarのようにスケジュール情報を登録する処理(アクション)だろう。
下端に配置しているバンドは「QuickActionBand」であり、アンカーとなっているアイコン(内部ではImageButton)をタップすることでQuickActionが表示される。(アイコンがandroidの内部リソースから引っ張ってきたものなのは急造なので勘弁してほしい)
QuickActionはなんらかのインタフェースを定義したバンドを介してActionItemを追加できるようにしておく。
- QuickActionBand.javaより抜粋
〜 final ImageButton btn = new ImageButton(this.getContext()); btn.setImageDrawable(this.getContext().getResources().getDrawable(android.R.drawable.ic_menu_agenda));
btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ActionItem item = new ActionItem(); Resources res = this.getContext().getResources(); item.setIcon(res.getDrawable(android.R.drawable.ic_menu_my_calendar)); item.setTitle("スケジュールを登録");
QuickAction quickAction = new QuickAction(btn); quickAction.addActionItem(item); quickAction.show(); }
- 実行結果
アクションアイテムの描画がいささか大きいのが気になるが、取りあえずこんな感じで実装していけば良さそうだ。
バンドは上端に配置するか下端に配置するか迷ったが、スマートフォンを片手で操作する際に、大抵の人は下に配置されている方が操作しやすいだろうということで、取りあえず下端に配置することにした。
2010-11-14
■[Android][CalendarView]CalendarView rev4
最近の縦方向に解像度が高い端末で見ると下方向が空いて:=カレンダーが圧縮されたように見える、とのことなので、ソースコードを見直してみた。
内部ViewFlipperのレイアウトパラメタをFILL_PARENTにした。(設定忘れてたという...)
これにより、コンテナが許可すれば親のViewの高さ一杯まで伸張して描画される。
ただし、リポジトリに上げているCalendarViewTestプロジェクトで使用しているレイアウトmain.xmlはルートのScrollView(MotionalScrollView)の属性"FillViewPort"がfalseになっており、このままではFILL_PARENTを指定しても意味が無い。
その場合は同MotionalScrollViewの同属性をtrueにセットすることで縦方向が伸張して描画される。
<org.kazzz.view.MotionableScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/ScrollView01" android:layout_height="fill_parent" android:layout_width="fill_parent" android:fillViewport="true"> 〜
実行結果 | 変更前 |
---|---|
縦方向に伸張されているのが解るだろうか。
個人的にはどんな解像度の端末でもカレンダの1日(1セル)が正方形で描画されたほうが綺麗なんだが、レイアウトとその上に配置されるViewの振る舞いとしてはこちらが正しいんだろうな。
Android Calendarview - Project Hosting on Google Code
追記:
実行結果のスクリーンショットをアップしていてandroid sdk 1.6でビルドした際と同2.2でビルドした場合でカレンダーの背景色が違うことに気がついた。
android 1.6にてビルド | android 2.2にてビルド |
---|---|
最初は目の錯覚かと思ったが、android 1.6でビルドした側は明らかに青みがかっている。背景の塗り潰し、透過の描画に何か変更があったのだろうか。
追追記:
Nexus oneでも標準のブラウザを使ってこのエントリを見てみたが、android 1.6でビルドした側がまともな白色に見え、android 2.2では紫がかって見えた。訳がわからん。
そもそも私のPCのカラーマネジメントが異常なのかもしれない。
2010-11-12
■[Android][CalendarView][SDK]CalendarView 公開しました
以前公開すると宣言して先延ばしになっていた、CalendarViewの公開を開始しました。
Android Calendarview - Project Hosting on Google Code※
CalendarViewはAndroid 1.6以降で動作するシンプルなカレンダービューです。
日本の祭日判定を行うK.Tsunoda氏の"kt祝日名取得"を阿蛭 栄一氏がJavaにポーティングした"KtHoliday.java"をほぼそのまま使用しており、ビュー上で祭日を判定/表示することができます。
年月を変更する方法として以下をサポートしています
- 「<<」「>>」のタップ
それぞれ前月、次月に遷移します
- 年月のタップ
ダイアログにより直接希望の年月に遷移します
- フリックモーション
左右又は上下のフリックモーションを検出して前月、次月に遷移します
ライセンスはApache2.0を採用していますので、自由に改変して使って頂いて構いません。ただし、内部で使用しているkt祝日名取得とそのJava版に関してはリンク先にて指示されている使用方法を遵守してください。
この場を借りてkt祝日名取得とそのJava版を公開されているAddinBoxの皆さん、NumberPickerの内部リソース使用方法を教えてくださったrmiyaさん、そして全てのオープンソース開発者にお礼申し上げます。
K.Tsunoda(AddinBox)
Java による祝日判定ロジック実装の試み
カスタムNumberPickerの作成 - にゃんだふる日記
2010-11-08
■[Android][SDK][CalendarView]上下のフリックモーションに対応する(ScrollView上のY軸モーションが無効になる)
拙作のカレンダービュー(CalendarView)は元々左右どちらかのフリック・モーションに反応して現在の月を変更する機能を実装している。
当初は左右のモーションだけで処理をしていたが、人間の指というものは横にしろ縦にしろ機械のように真っ直ぐに動かすものではなく必ずある程度のぶれが発生するため、右に指を弾いているはずが実際には斜め上に弾いている場合があったりと、多分に感覚的なものである。であれば左右の他に上下の動きも捕捉してやることで、より感覚的に操作できてミスも防ぐことができるのではないだろうか。
左右が実装できているのであれば上下は簡単だろうと早速改造に着手したのだが、テストした所CalendarView単体では問題ないものの、通常配置するであろう、ScrollView下に配置した場合に、Y軸のモーションイベント(MotionEvent)に全く反応しないことが判明した。
いろいろ調べて判ったのだがこの現象、ScrollView上に配置したViewでは避けられない問題のようだ。
- ScrollView.java (ViewGroup.java)
@Override public boolean dispatchTouchEvent(MotionEvent ev) { : : if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL); //ACTION_CANCELを送信 ev.setLocation(xc, yc); if (!target.dispatchTouchEvent(ev)) { } mMotionTarget = null; return true; } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { break; } final int pointerIndex = ev.findPointerIndex(activePointerId); final float y = ev.getY(pointerIndex); final int yDiff = (int) Math.abs(y - mLastMotionY); if (yDiff > mTouchSlop) { <span style="color:#FF0000;">mIsBeingDragged = true;</span> mLastMotionY = y; } break; } case MotionEvent.ACTION_DOWN: { : : } return mIsBeingDragged; }
ScrollView(ViewGroup)#onInterceptTouchEventは自身の子供のビューのタッチイベントを監視しており、ScrollViewクラスの実装では子供のViewのモーション中、Y軸側に一定量移動した条件で"mIsBeingDragged"を真に設定する、つまり上下へのモーションをスクロール開始の契機としているため、その子供のViewに対しては"ACTION_CANCEL"でMotionEventが送信されて、結果Y軸側のモーションをCalendarViewで捕捉できないのである。
ScrollViewが無ければいい訳だが、CalendarViewは比較的大きな画面を覆うタイプのビューであるため、機器によっては画面をランドスケープモードにした場合にカレンダの下端が切れてしまうため、ScrollViewを使わないと5週目が表示できないのだ。
例) ランドスケープにして下端が切れているCalendarView
この状態を避ける方法も無いことはない。ViewGroupを代表とするViewParentの実装クラスは子供のビューのタッチイベントを一切監視しないように指示することもできる。
public abstract void requestDisallowInterceptTouchEvent (boolean disallowIntercept)
requestDisallowInterceptTouchEventメソッドのパラメタにtrue(真)をセットすることにより、内部変数であるdisallowInterceptが真になる
public boolean dispatchTouchEvent(MotionEvent ev) { : : if (!disallowIntercept && onInterceptTouchEvent(ev)) {
この操作により、子供のViewにイベントを送信するか否かをonInterceptTouchEventの結果に依らないようにできる訳だ。
しかし、実際に試してみるとこの方法には問題があることが判るだろう。ScrollView上に配置したViewは確かにY軸方向のモーションイベントを捕捉するようになるが、せっかくScrollView上に配置したにも関わらず、今度はスクロール操作に反応しなくなるのである。(当たり前)
ということで、一番の解決策はスクロールが必要な場合はモーションを無視し、必要無い時にはモーションを有効にすることだろうが、それを実現するためには、やはりサブクラスを書く必要があるだろう。
長くなったので続きは明日にでも。
2010-06-15
■[Android][SDK][CalendarView]TouchEventをGestureDetectorで置き換える
拙作のカレンダビューに関して、以前にフリック・モーションで月を変えることに言及した。
ViewFlipperによるビューの切替えとアニメーション
実装としてはこれでOKだと思っていたのだが、いざ実機でテストしてみると腑に落ちない振る舞いをする。
- 現象
左右のフリック・モーションが認識されないことがある。(不定期)
実装は以前にエントリに書いたように、ViewFlipperクラスのonTouchEvent中のアクションの切替え時に、タッチされてからの移動変量を閾値として次月又は前月に移動することで実装している。
- ViewFlipper#onTouch抜粋
protected float lastTouchX; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastTouchX = event.getX(); break; case MotionEvent.ACTION_UP: float currentX = event.getX(); if (lastTouchX < currentX) { viewflipper.showNext(); } if (lastTouchX > currentX) { viewflipper.showPrevious(); } break; } return true; }
不具合に関しては昔ながらの手法で各アクションの発生時にログを出力しながら調べてみたのだが、MotionEvent.ACTION_DOWNは確実に発生しているのに対して、MotionEvent.ACTION_UPは発生したりしなかったりしている。これが直接の原因のようだが、しかしその理由が分からない。
- GestureDetector
タッチイベントに関しては様々なジェスチャを検知するための専用のリスナがあることが解っていたが、プリミティブなタッチイベントでなんとかなると思って気にしていなかった。しかし、今回のような不具合が起きると標準的な実装に頼りたくなるが人情というものだ。
GestureDetectorにViewのonTouchEventを委譲することにより、より抽象度の高い、以下のジェスチャを捕捉することができるようになる。
- onDown タッチ
- onShowPress プレス開始?(タッチ後、指が動き出す前を捕捉したい場合に)
- onSingleTapUp シングルタップ(タッチ後すぐに指を離した。マウスのクリックに相当)
- onScroll スクロール(タッチしたまま指を滑らせた)
- onLongPress ロングプレス(長押し)
- onFling フライング(タッチしたまま一定の距離を移動した)
それぞれが独立したイベントとして捕捉可能なため、上記のコードのようにアクションをまたいで値を保持する必要が無く、プログラミングが簡素になるメリットがある。
- ViewFlipper#onTouchをGestureDetectorに委譲
final GestureDetector detector = new GestureDetector(context, new OnGestureListener(){ @Override public boolean onDown(MotionEvent e) { return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (e1.getX() < e2.getX()) { viewflipper.showPrevious(); } else { viewflipper.showNext(); } return true; }
@Override public void onLongPress(MotionEvent e) {}
@Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return true; }
@Override public void onShowPress(MotionEvent e) {}
@Override public boolean onSingleTapUp(MotionEvent e) { MonthlyCalendarView currentView = (MonthlyCalendarView)viewFlipper.getCurrentView(); performSelectionCalendar(currentView); return result; }}); viewFlipper.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return detector.onTouchEvent(event); } });
onFlingの発生閾値は、タッチ後に移動距離がViewConfiguration.getMinimumFlingVelocity()で取得した値を超えたか否かを判定している。
確かにシンプルになるのだが、処理する必要の無いイベントの記述が邪魔だな。適切なアダプタを作ったほうが良いのかもしれない。
と、これで治ると思ったら大間違いだった。ソースコードを見てみたが、OnGestureListenerの各メソッドも結局はonTouch内で同様に判定をしているに過ぎないからだ。相変わらずACTION_UPが処理されないケースがある。
まだ何かが間違っている。それを書こうと思ったが今日はここまで。
※ UI操作の名称の基準が分かり難いので、今後はApplieが提唱する呼称で統一する。(しっくりこなかったら止める)
- タップ……指で軽く叩く操作。マウスのクリックに相当
- ダブルタップ……2回叩く操作。ダブルクリックに相当
- ドラッグ……写真を移動する時に指をずらす操作
- フリック……リストをスクロールする時に指で軽くはらう操作
- ピンチ……2本指でのつまむ操作の総称
- ピンチアウト/ピンチオープン……2本指の間を広げて拡大する時の操作
- ピンチイン/ピンチクローズ……2本指の間を縮めて縮小する時の操作