2013年12月7日土曜日

PullToRefreshで下からの引っ張りにも対応する(実装編)

さて、前回準備した環境で実際に「引っ張って更新」を試してみます。

いまもう27時とかめっちゃ眠いので文章で説明するより実際にコードを
見た方がわかりやすいかと思いますので、詳細はコードを貼るとして
とりあえず要点だけ。

・通常の ListView のかわりに PullToRefreshListView を使用

・上下両方の引っ張りに対応するよう Mode.BOTH を設定

・ PullToRefreshListView の setOnRefreshListener にて
 「引き下げ/上げて更新」の挙動を設定
 →OnRefreshListener2 で、引き下げ/上げ のそれぞれについて
  異なる動作を設定できます。
 →ただ、前回書いた通り、同名(PullToRefresh)の異なるライブラリを
  使用している場合、OnRefreshListener2 がなかったりしますので
  ご注意ください。


で、実際のサンプルコードはこんな感じになります。
(上から引っ張る→初期状態に戻す、下から引き上げ→項目を追加する)

■MainActivity のレイアウト(activity_main.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.handmark.pulltorefresh.library.PullToRefreshListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>


■リストの項目用レイアウト(list_item.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/listItemText"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center" />

</LinearLayout>


■コード(MainActivity.java)
public class MainActivity extends Activity {

    private ArrayList<String> mListItems;
    private PullToRefreshListView mListView;
    private SampleListAdapter mAdapter;

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

        //項目初期化
        initializeItems();

        //ListView 設定
        mListView = (PullToRefreshListView)findViewById(R.id.listView);
        mAdapter = new SampleListAdapter();
        mListView.setAdapter(mAdapter);

        //PullToRefresh 設定
        mListView.setMode(Mode.BOTH);//モード指定:上下からの引っ張りに対応
        mListView.setOnRefreshListener(new OnRefreshListener2<ListView>(){

            //上から引っ張った場合
            @Override
            public void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {
                initializeList();
                new FinishRefresh().execute();
            }

            //下から引き上げた場合
            @Override
            public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {
                addItem();
                new FinishRefresh().execute();
            }

        });
    }

    //項目を初期化
    private void initializeItems(){
        mListItems = new ArrayList<String>(Arrays.asList("Item 1", "Item 2", "Item 3"));
    }

    //リストを初期状態に戻す
    private void initializeList(){
        initializeItems();
        mAdapter.notifyDataSetChanged();
    }

    //リストにアイテムを追加
    private void addItem(){
        int count = mListItems.size();
        mListItems.add("Item " + Integer.toString(++count));
        mAdapter.notifyDataSetChanged();
    }

    //アダプタクラス
    private class SampleListAdapter extends BaseAdapter{

        @Override
        public int getCount() {
            return mListItems.size();
        }

        @Override
        public Object getItem(int index) {
            return mListItems.get(index);
        }

        @Override
        public long getItemId(int index) {
            return index;
        }

        @Override
        public View getView(int index, View view, ViewGroup arg2) {
            if(view == null){
                LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.list_item, null);
            }
            TextView textView = (TextView)view.findViewById(R.id.listItemText);
            textView.setText(mListItems.get(index));
            return view;
        }
    }

    //リスト更新終了
    private class FinishRefresh extends AsyncTask<Void, Void, Void>{
        @Override
        protected Void doInBackground(Void... params) {
            return null;
        }

        @Override
        protected void onPostExecute(Void result){
            mListView.onRefreshComplete();//更新アニメーション終了
        }
    }
}


で、細かいことは確認してないんですが、どうやら onPullDownToRefresh
とかの中で直接  onRefreshComplete を呼んでも効かない模様?
(「更新中」状態のままとなってしまうみたいです。)

onPullDownToRefresh とかって非メインスレッドで呼ばれてるってこと
なんですかね?あ、「自分でソース確認しろ」とか言うの禁止ね。
 とりあえず↑みたいに AsyncTask の onPostExecute で呼び出してやると
期待する挙動になるみたいですが。。。


一応こんな感じで動きます。

【上から下に引っ張る】


【下から上に引き上げる】




どうでもいいけど、最近ホント夜更かしできなくなったなぁ…
昔はゲーム買ったら31時とかまで平気でやってたのになw
コレがアラサークオリティってやつか。

PullToRefreshで下からの引っ張りにも対応する(導入編)

最近(?)流行の ListView を下に引っ張ると更新する機能を
いざ組み込むべし、てことで。

ついでに上に引き上げた時もなんか動作つけたいなーとか思ったり。

ただ、コレって Android 標準ライブラリの機能じゃないんですな。
いくつかライブラリは公開されてますが PullToRefresh ってのが
一番ポピュラーっぽい。

それにしてもよく見る機能だから簡単に実装できるかと思ったら
意外とめんどいのね。


とりあえず今回は事前準備ということでライブラリの導入まで。
※そんなん言われんでもわかっとるわ、て方は次の回へどぞ。


0.前置き
「引っ張って更新」機能を使用するプロジェクトはもちろん
どんな名前でも構いませんが、とりあえずここでは P2RSample
しておきます。 そのへんは適宜読み替えてください。


1.ライブラリダウンロード
https://github.com/chrisbanes/Android-PullToRefresh
ココからダウンロードできます。
私みたいに Git 慣れてない方は↓から ZIP 取るのが良いかと。

解凍すると Android-PullToRefresh-master とかってフォルダができます。

※ちなみに、ほかに「PullToRefresh」でググると
 https://github.com/johannilsson/android-pulltorefresh
 とかも引っかかりますが、こっちは微妙に機能が違うみたいです。
 そっちを使っちゃうと次回「実装編」で「アレェッー!??」てなりますので
 ご注意を。


2.Eclipse にライブラリ追加
Eclipse の「ファイル」→「インポート」を選択。


で、Androidの既存コード追加を選択。
(「既存プロジェクトをワークスペースへ」でも問題なさそうですが。)


 追加するのは「Library」 フォルダだけでいいみたいっす。

※管理上、「プロジェクトをワークスペースにコピー」としておいた方が
 良いかと。 デスクトップとかにリンク貼られても困るし。


3.プロジェクトのプロパティで使用ライブラリ追加
P2RSample プロジェクトのプロパティからライブラリ追加の設定。


これで準備OKっす。

次回「実装編」はこのライブラリの機能を使って実装していきます。。。

2013年11月10日日曜日

AsyncTask の onPostExecute が呼ばれない件について

Android でネットワークに接続して情報を取得したりするときは
UIスレッド(メインスレッド)とは別のスレッドで処理を行う必要があります。

メインスレッドでネットワーク接続しようとすると
例外吐いてアプリが落ちやがります

※どうでもいいけど Android ってビルドでは何も言われないのに
 実行すると落ちるみたいなパターンけっこう多いような。。。


で、非同期でのネットワーク処理には AsyncTask クラスを使用するのが
最もポピュラーなやり方かと思います。

具体的な処理はGoogle先生にでもお尋ねすればいくらでも出てくるので
ざっくり省略しますが、だいたい↓こんな感じの処理となります。

public class MainActivity extends Activity {

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

  Button button = (Button)findViewById(R.id.button1);
  //ボタンを押したら非同期処理実行
  button.setOnClickListener(new OnClickListener(){
   @Override
   public void onClick(View arg0) {
    //非同期処理を実行させる
    new MyAsyncTask().execute();
   }
  });
 }

 //非同期処理を行うためのクラス
 private class MyAsyncTask extends AsyncTask<Void, Void, Void>{

  @Override
  protected Void doInBackground(Void... params) {
   //別スレッドで実行される非同期処理
   return null;
  }
  
  @Override
  protected void onPostExecute(Void result){
   //メインスレッドで実行される処理
   //(非同期処理の結果をUIに反映する等)
  }
 }
}



で本題。
タイトルにある通り、特定の条件において
非同期処理が終わった後に onPostExecute が呼ばれない
とかいう状況が発生します。

参考)onPostExecute not being called in AsyncTask

要は AsyncTask クラスのスタティックイニシャライザがメインスレッド以外で
実行された場合、結果的に onPostExecute がそのスレッド(非メインスレッド)に
紐付けられるようです。
※当然といえば当然ですが onPreExecute も同様。

という書き方をすると
「そもそも非メインスレッドから AsyncTask なんか呼ばねーよ m9(^Д^)プギャー」
とか思われるかもしれませんが、

↑のリンクにもある通り、
C2DM(プッシュ通知機能)なんかではわりとポピュラーに発生する、
てかサンプルコードがそれを実際にやってやがるみたいです

てか私自身もGCM(C2DMの後継)で見事にハマったワケで。

※GCMIntentService の onRegistered で受け取った regId を Web サービスに
 登録しようとして AsyncTask 使ったら見事にこの状態になったっぽい。
 その AsyncTask 自体はUIいじってるワケでもないのに。。。


回避策としてはアクティビティの onCreate メソッドとか、
・確実にUIスレッドで実行される箇所
・なるべく早い段階
で、例えば以下のようにして AsyncTask クラスに触れてやればいいみたいです。

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

  //とりあえず一度 AsyncTask に触れてやる
  try{
   Class.forName("android.os.AsyncTask");
  }catch(ClassNotFoundException e){}

  //以下略
 }



…それにしても Android って標準クラスのバグがまだまだ多いみたいで。
1つのアプリ作るのに既に4つくらい標準クラスのバグに遭遇してるんですが
何すかこの大当たりっぷり。。。



一応↑の StackOverFlow の解説もざっと折りたたみで載せときますか。


2013年10月12日土曜日

Viewにborder的な罫線をつける方法

Android の View に枠線をつけるのはそれほど難しいことではありません。

■枠線を付けたい TextView のレイアウト(layout/text_layout.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="12dp"
    android:orientation="vertical" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:text="どうも、適当なTextViewです"
        android:background="@drawable/simple_frame" />

</LinearLayout>

■枠線のレイアウト(drawable/simple_frame.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#FFFFFF"/>
    <stroke android:width="2dp" android:color="#AAAAAA" />
</shape>




が、bottom だけ、とか top 以外、とか特定の方向に限定しようとすると
急激に面倒なことになります。

てか、直接それを実現できる方法は存在しないっぽいです。

html/css なら border 指定で簡単にできるのに…困ったもんです。


んじゃどうすんのよ、というと、

通常の枠線(4方向)もしくは色付き長方形を作成
 →ちょっとずらした長方形(背景色で塗りつぶし)を重ねる

てな指定のしかたで対応方法が一般的みたいです。

参照1)[Android]View に border-bottom をつける
参照2) Is there an easy way to add a border to the top and bottom of an Android View?

やろうとしてる事に対してのまわりくどさがハンパないですな。

■TextView のレイアウト
 →さっきのやつの background を @drawable/underline に変えるだけ

■枠線のレイアウト(drawable/underline.xml)
 <?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- 枠線付き長方形 -->
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#FFFFFF"/>
            <stroke android:width="2dp" android:color="#AAAAAA" />
        </shape>
    </item>

    <!-- 2dp分の padding を設定した枠線なし長方形 -->
    <item android:bottom="2dp">
        <shape android:shape="rectangle">
            <solid android:color="#FFFFFF"/>
        </shape>
    </item>
</layer-list>




んで、実はここからが本題。

何がしたかったかというと。

ListView 的なモノを作りたい。
→でも項目は固定だし項目数も少ない。
 →わざわざ ListView と Adapter 用意すんの面倒…
  →そのレベルなら TextView 縦に並べるだけで良くね?
   →そのまま並べると各 TextView の間に区切りがない…
    →border 的なの何かないかな?

…ここまでであれば前述の方法で問題ないワケですが、さらに

     →タップしたときに他の Activity に遷移する必要がある
      →タップしたときにアクションがあるなら色とか変えたいよね

こうなるのが人情ってもんです。


で、コレを実現しようとすると、さらに selector を組み合わせることになります。

■TextView のレイアウト
 →さっきのやつの background を @drawable/selectable_underline に変える
 →タップ動作を検出するため android:clickable="true" を追加

■枠線のレイアウト(drawable/selectable_underline.xml)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- タップされたときの設定 -->
    <item android:state_pressed="true">
        <layer-list>
            <!-- 枠線付き長方形 -->
            <item>
                <shape android:shape="rectangle">
                    <solid android:color="#FFC0CB"/>
                    <stroke android:width="2dp" android:color="#AAAAAA" />
                </shape>
            </item>

            <!-- 2dp分の padding を設定した枠線なし長方形 -->
            <item android:bottom="2dp">
                <shape android:shape="rectangle">
                    <solid android:color="#FFC0CB"/>
                </shape>
            </item>
        </layer-list>
    </item>

    <!-- 通常時の設定 -->
    <item android:state_pressed="false">
        <layer-list>
            <!-- 枠線付き長方形 -->
            <item>
                <shape android:shape="rectangle">
                    <solid android:color="#FFFFFF"/>
                    <stroke android:width="2dp" android:color="#AAAAAA" />
                </shape>
            </item>

            <!-- 2dp分の padding を設定した枠線なし長方形 -->
            <item android:bottom="2dp">
                <shape android:shape="rectangle">
                    <solid android:color="#FFFFFF"/>
                </shape>
            </item>
        </layer-list>
    </item>

</selector>



ようやくそれっぽく動くようになったけど、コレ大人しく Adapter 実装した方が
早かったんじゃ…とか思ったり。

2013年9月1日日曜日

startActivityForResult で返却された結果(Intent)が反映されなくて大ハマリした件

Android で twitter 連携しようとしたときのはなし。

twitter4j を使ってるんですが、 twitter ってヤツは初回にアカウントの
認証が必要なんすね。

んで、認証の処理の流れは大体以下のような感じになってます。

  1. 認証オブジェクトを用意
  2. 認証オブジェクトとコールバック用URIから認証ページURL生成
  3. 認証ページを startActivityForResult で開く
  4. 認証ページで「連携アプリを認証」ボタンを押すと元のアプリに戻ってくる
  5. Intent にセットされている URI を取得
  6. URI にくっついてる verifier(文字列) を取得
  7. verifier とか使ってアクセストークンを取得
  8. 以降、各種 Twitter API を使うときはアクセストークンを使用
※細かいことは↓とか見ると吉。
 Android再入門 - Twitterクライアントを作ってみよう - OAuth認証

で、問題は3~5の部分。

3で startActivityForResult を呼び出した Activity と
5で Intent を受け取る Activity が異なる場合は問題ないんですが、

3と5が同じ Activity の場合(全く同じ画面に戻ってくる場合)に、
なぜか Intent に URI がセットされていない???


で、さんざんハマったワケですが、ようやく原因がわかりまして。

既に存在する画面に対して startActivity(Intent) を実行した場合、

 onNewIntent(Intent) → onResume()

の順に呼び出されるんですが、この onNewIntent のソースを見たところ、

protected void onNewIntent(Intent intent) {
}


・・・・・・何もしてねぇ!!


要は新しい intent に更新されない=初回 onCreate 時のままなんで
Intent に URI が存在しない、と。何この超絶トラップ。


しかもそういう仕様だとかしっかりソースにもコメント書いてやがります。

 Note that {@link #getIntent} still returns the original Intent.
 You can use {@link #setIntent} to update it to this new Intent. 

 →getIntent は相変わらず元の Intent 返すかんね。
  更新したかったら setIntent 使ってね。


てことで対応策としては Intent 受け取る Activityで onNewIntent を
オーバーライドするなりして setIntent 呼んでやりゃいいだけの話みたいですが。

@Override
protected void onNewIntent(Intent intent){
    setIntent(intent);
}


それにしてもなんでまたこんな仕様になってるんですかね??

察するに、「単に画面遷移で startActivity 呼ばれた時に更新かけちゃうと
元の intent の情報が消えちゃうから」とか??


ちなみに初回画面作成時は onNewIntent 自体呼ばれないんで、
常に intent の値をチェックするなら最低2箇所に処理書かないといかん、と。

うーん、なんとも初心者には釈然としない話ですなぁ…。
なんか真っ当な事情があっての仕様なのかしらん。

2013年8月3日土曜日

UnityでFBXファイルを読み込む際にハマったこと

興味本位でちょっと Unity でもいじってみるか、とか思って
とりあえずいわゆる「黄本」の写経をちょこちょこやってるわけですが、
Unity のバージョンの差なのか、ときどきインターフェースとかが
本と違ってるなんていうトラップが仕込まれてたりします。

今回ハマったのは FBX(3Dモデル)ファイルの読み込み部分なんですが

アニメーションが正常に読み込まれてない…??


てか何が困るって Unity はまだまだ情報が少ないもんで、ちょっと
つまづいただけでもネットで情報調べながら解決しようとすると
丸1日作業になったりするという…




んで本題。

黄本に書いてある手順だと、大まかには

1.fbx(3Dモデル情報) と png(テクスチャ) をインポート
 →prefab として登録される
2.prefab をインスタンス化(Hierarchy ビューにドラッグ&ドロップ)
3.アニメーションスクリプト作成
4.スクリプトを2で作ったオブジェクトにドラッグ&ドロップ

てな感じですが、自分の環境でこの通りにやると以下のようなエラーが
出やがりました。

「Animation なんかそのオブジェクトにはないよ」みたいな。


てかその前段階のアニメーション動作確認とかの時点でもう
インターフェースがだいぶ違っちゃってて、↑の画像見ての通り
そもそもオブジェクトに「Animation コンポーネント」とかないワケですよ。



で、ネットを漁りまくってようやく見つけた解決方法。



FBX インポートインスペクタの Rig パネルの Animation Type を
Generic → Legacy に変更して Apply。

すると



無事オブジェクトのとこに Animation コンポーネントが出てきました。
あとは黄本のとおりでOK、と。やれやれ。



まあもっと別の解決法もあるんだろうなーとは思いますが、とりあえず
写経の段階だし動けば万事OKってことで。

Javaでのクラス継承時のコンストラクタ

仕事で Android アプリの開発をすることになったものの、
java とかほとんど触ったこともないワケで。

ちょっくら java のコンストラクタの呼ばれ方が気になったので
実験してみた。

  • SuperClass.java
public class SuperClass {

 protected String status = "status :";

 //1.引数なしスーパークラスコンストラクタ
 public SuperClass()
 {
  status += " Super 1 Called";
 }

 //2.引数ありスーパークラスコンストラクタ
 public SuperClass(Context context)
 {
  status += " Super 2 Called";
 }

 @Override
 public String toString()
 {
  return status;
 }
}

  • SubClass.java
public class SubClass extends SuperClass{

 private Context context;

 //3.引数なしサブクラスコンストラクタ
 public SubClass()
 {
  status += " Sub 1 Called";
 }

 //4.引数ありサブクラスコンストラクタ
 public SubClass(Context _context)
 {
  status += " Sub 2 Called";
  context = _context;
 }

 //結果表示
 public void checkValue()
 {
  Toast.makeText(context, toString(), Toast.LENGTH_SHORT).show();
 }
}

  • MainActivity.java
public class MainActivity extends Activity {

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

  final SubClass subClass = new SubClass(this);

  Button button = (Button)findViewById(R.id.button1);
  button.setOnClickListener(new OnClickListener(){
   @Override
   public void onClick(View arg0) {
    subClass.checkValue();
   }
  });
 } 
  • activity_main.xml
(ボタン1コ置いてあるだけなんで省略。。。)


んで実行結果が

status : Super 1 Called Sub 2 Called

なので、呼び出し順としては

 1.スーパークラス引数なしコンストラクタ
 4.サブクラス引数ありコンストラクタ

ってことか。

どのコンストラクタがどの順番で呼ばれるか、てのは実際やってみると
なるほどって感じですな。