Archive for the ‘Android’ Category

    はてなブックマーク - Android キーボードの出現と同時に画面がスクロールしないときに
    このエントリーをはてなブックマークに追加

    こんにちは、山本です。
    最近はUXに興味をもち、勉強会を開催しているのですが、今回はAndroidのEditTextに関わるお話です。

    Objective-cにおけるUITextFieldとキーボードの関係

    Androidと言いながらまずはiPhoneの話ですが、iPhoneを作られた方なら一度はこんな経験ありませんか?

    “テキストフィールドにフォーカスを当てると、キーボードで隠れる”

    とくに意識せず作っちゃうと、キーボードの高さ分画面が上部に動くことは無いので
    ・UIScrollViewにする
    ・キーボードの高さを計算して、キーボードの出現と同時に手動で画面を上部に移動させる
    のような対処が必要です。

    ※ページの下部にあるテキストフィールドが


    ※キーボードの出現で隠れる

    Androidは機種によって挙動が違う!

    これがAndroidなら、単純に作ってもオンフォーカスと同時に、もしテキストフィールドがキーボードで隠れるようなら勝手に画面を押し上げてくれます。(OS4.0で作成時)
    しかし、一部の機種ではキーボードの出現と同時に画面が動かない! いわゆる機種依存ということになるのでしょうか。
    ああ、こまった。。

    EditTextの背景色と右寄せに注意

    なぜそういうことが起きるのか。まずは以下のソースをご覧ください。

    <EditText
            android:id="@+id/editText1"
            android:layout_width="200dp"
            android:layout_height="25dp"
            android:background="#00ffffff"
            android:gravity="right" />

    このようなEditTextの定義にしていると動かない機種があります。
    注目は「背景色」「右寄せ」のプロパティを付けているところです。
    このダブルコンボこそが、どうも諸悪の根源のようなのです。
    どちらかを外す、もしくは「左寄せ」にすると画面が動いてくれました。
    しかし、要件定義上、この2つを外すワケにはいきません。

    paddingRightのプロパティを付けると解決

    背景色を付けるためには、色の付いた画像を用意してそれを背景画像にすればいいので、”background”プロパティを使わなくて済みます。ですが、その方法以外で解決しました。

    <EditText
            android:id="@+id/editText1"
            android:layout_width="200dp"
            android:layout_height="25dp"
            android:background="#00ffffff"
            android:gravity="right"
            android:paddingRight="1dp" />

    新たに”paddingRight”プロパティを定義しました。ポイントは、dp値を必ず1以上にすることです。0だと書かないのと同じですので。
    こうすると、テキストフィールドの右辺からすこーしだけずれてテキストが表示されることになります。

    Viewの工夫だけで解決

    「背景色」「右寄せ」のせいでスクロールしない原因は不明ですが、この現象だけ切り取って考えると、テキストポインタが右辺にくっついちゃっていると、テキスト入力モードだと認識されないのかな、と思います。現に「背景色」「右寄せ」のEditTextでも、文字が入ってる状態にしてテキストポインタが文字の左側にいくようにタップするとスクロールします。

    ちなみに、解決にあたっては、
    ・AndrodManifest.xml に windowSoftInputMode=”adjustPan” のプロパティを付ける
    ・ScrollViewにする
    も試しましたが、結果は変わらずでした。

    また、
    ・iPhoneのように、キーボード出現イベントをトリガーにViewを上に動かす
    も検討しましたが、機種によってキーボードの形状が異なると思うので現実的ではないかなと。

    結果、Viewの調整でうまくいきました。
    お困りの方がいたらお役立てできればと思います。

      はてなブックマーク - ZENPRE for Android Galleryとintent-filter
      このエントリーをはてなブックマークに追加

      3連続出しゃばってすみません。
      山本です。

      プレゼン配信サービス「ZENPRE」では、スライドの切り替えを手軽に行えるように、スマートフォン向けアプリも配布しております。
      iPhone用とAndroid用をリリースしておりますが、私はAndroidアプリを担当しました。

      アプリを開発するにあたって、スライドの切り替えをどのような操作で行うかいろいろ考えました。
      スタンダード、というか、スマホの醍醐味的な操作で言えば、指でスライドさせるフリック操作ですね。
      ただ、その仕様だと1ページずつしかめくれないのと、次のスライドはどんなものだったっけ、というときに確認できません。

      そこで、AndroidにあるGalleryクラスに注目しました。
      これは、画像を横一列に羅列することができます。
      フリック操作で左右に移動でき、クリックイベントも取得できるということで、
      フリック操作でスライドの移動を行い、クリックすることでページを確定するという仕様にすることにしました。

      その際に書いたソースコードを一部公開します。

      まずは、galleryを定義したビューxml

      <LinearLayout
      	android:layout_width="fill_parent"
      	android:layout_height="wrap_content"
      	android:layout_weight="4">
      	<ImageView
      		android:id="@+id/selectedImageView"
      		android:layout_width="fill_parent"
      		android:layout_height="fill_parent"
      		android:adjustViewBounds="true"
      		android:scaleType="centerInside" >
      	</ImageView>
      </LinearLayout>
      <LinearLayout
      	android:layout_width="fill_parent"
      	android:layout_height="wrap_content"
      	android:layout_weight="1"
      	android:padding="5px">
      	<Gallery
      		android:id="@+id/imageGallery"
      		android:layout_width="fill_parent"
      		android:layout_height="wrap_content">
      	</Gallery>
      </LinearLayout>

      画面上部に選択したスライド画像。
      下部に登録しているスライド画像のgalleryを配置します。

      処理を行うActivityクラスには以下のように書きました。

      public void onCreate(Bundle savedInstanceState) {
      	 // galleryから選択した画像を表示するImageView
      	ImageView selectedImageView = (ImageView) findViewById(R.id.selectedImageView);
       
      	 // gallery定義
      	Gallery gallery = (Gallery) findViewById(R.id.imageGallery);
      	PresenImageAdapter presenImageAdapter = new PresenImageAdapter(this);
       
      	// galleryに表示したい分だけ、bitmap形式のオブジェクトを追加する
      	// presenImageAdapter.addBitmap(bitmap);
      	// presenImageAdapter.addBitmap(bitmap);
       
      	// adapterのセット
      	gallery.setAdapter(presenImageAdapter);
       
       
      	// 画像クリックでサムネイル表示
      	gallery.setOnItemClickListener(new AdapterView.OnItemClickListener(){
      		public void onItemClick(AdapterView<?> adapterView, View parent, int position, long id) {
      			// 選択された画像をイメージビューに表示
      			Bitmap selectedBitmap = HttpClient.getImage("クリックされた部分にヒットする画像のURL");
      			selectedImageView.setImageBitmap(selectedBitmap);
      		}
      	});
      }

      galleryと接続するアダプタークラスは、Android DevelopersサイトにあるBaseAdapterを拡張したものを使用しました。

      public class PresenImageAdapter extends BaseAdapter {
      	// bitmapのリスト
      	private List<Bitmap> imageItems;
       
      	public PresenImageAdapter(Context context) {
      		......
      		imageItems = new ArrayList<Bitmap>();
      	}
       
      	// bitmapのリストに画像を追加
      	public void addBitmap(Bitmap image) {
      		imageItems.add(image);
      	}
       
      	// 画像の取得
      	public Object getItem(int position) {
      		return imageItems.get(position);
      	}
      	....
      	....
      	public View getView(int position, View view, ViewGroup parent) {
      		// 表示する画像
      		Bitmap bitmap = (Bitmap) getItem(position);
      		imageView.setImageBitmap(bitmap);
      		.......
      		return imageView;
      	}
      }

      これにより、画面下部には画像のgallery、そのgalleryにある画像をクリックすることで、上部にその画像が表示されます。
      さらに、クリックイベントの中に、ページ情報を送信する処理を書いてますので、プレゼンを見ている人に情報がブロードキャストされるようにしてます。
      これで基本形の完成です。

      あと、余談なのですが、AndroidManifest.xmlを以下のように書いたらどうなると思いますか。

      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="jp.zenpre"
       android:versionCode="1"
       android:versionName="1.0">
      	<uses-sdk android:minSdkVersion="4" />
      	<application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="false">
      		<activity android:name=".SplashActivity" android:screenOrientation="portrait" android:label="@string/app_name">
      			<intent-filter>
      				<action android:name="android.intent.action.MAIN" />
      				<category android:name="android.intent.category.LAUNCHER" />
      			</intent-filter>
      		</activity>
      		<activity android:name=".HomeZenpreActivity" 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>

      この、宣言を2つしてしまった状態でインストールを行うと、

      のように、同じアプリが2つ表示できてしまいます。
      片方は”SplashActivity”で定義した画面が立ち上がり、もう片方は”HomeZenpreActivity”で定義した画面が立ち上がります。
      とくにエラーは起きません。しかも、この状態で、マーケットに登録までできます。

      ただし、マーケットからダウンロードして、マーケットアプリ上で開こうとすると、

      マーケット(com.android.vending)が予期せず停止しました。やり直してください。

      The application Market (process com.android.vending)has stopped unexpectedly.Please try again.

      と表示されて開けません。
      たぶん、どっちのアプリを開けばいいのか分からないのでしょう。
      ホーム画面からアプリを直接指定すると開けますが。
      何を隠そう、これをアップしてしまい、ちょっと混乱してしまった経緯があります。
      たぶん、誰もやらないと思いますが、ご注意を。。。

      というわけで、携帯端末でプレゼンの操作ができる、このAndroidアプリをぜひご利用してください。
      AndroidMarketで「ZENPRE」と検索すればヒットします。
      そして、いろいろとご意見頂ければ幸いです。

        はてなブックマーク - Androidでスレッド内でアラートを出すと
        このエントリーをはてなブックマークに追加

        プレゼン配信サービス「ZENPRE」で、次期バージョンに対応したAndroidアプリを作っている際に、
        ちょっと詰まった点を1つご紹介します。

        ウェブAPIにアクセスしてデータを取る間に別処理を、なんていうことをしたいときは、
        別スレッドを定義してその中で処理を書くかと思います。

        そして、そのデータ取得でエラーが発生したりしたら、Toastなどでアラートを出すでしょう。
        それをやりたくて、以下を書いてみました。

        new Thread(new Runnable(){
          @Override
          public void run() {
            try{
              connect("xx.xxx.xxx.xxx",80);
            } catch (Exception e) {
              Toast.makeText(PresenImageActivity.this, getText(R.string.alert_fail_connect), Toast.LENGTH_LONG).show();
            }
          }
        }).start();

        こういう風にすると、実際にエラーが発生したときに、以下のシステムエラーに出くわします。

        java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()

        別スレッド内でUIの操作を行うのはだめのようです。

        そこでHandlerの登場です。
        UI Threadが持つキューにジョブが入り、UIの操作ができるようになります。
        というわけで、以下のように書きました。

        private final Handler handler = new Handler();
        new Thread(new Runnable(){
          @Override
          public void run() {
            handler.post(new Runnable() {
              public void run() {
                try{
                  connect("xx.xxx.xxx.xxx",80);
                } catch (Exception e) {
                  Toast.makeText(PresenImageActivity.this, getText(R.string.alert_fail_connect), Toast.LENGTH_LONG).show();
                }
              }
            });
          }
        }).start();

        これで、別スレッド内で起きたエラーを表示できるようになりました。
        簡単ですが、備忘も込めて。

        今回開発したAndroidアプリにも実装したZENPREの新機能、「同期録画コンテンツ」を使って、
        見逃した勉強会をいつでも閲覧できる機能の紹介は次回。