無限Swipeを実装してみる(ViewPager編)

無限にSwipeできる画面ってどうやって実装しているのかなぁと考えたのが事の発端。無限だったり循環だったりどっちでもいいんだけど、左はじまでいったら右はじに戻る奴だったり何個表示するかわからないものをSwipeで実装したりする場合を考えてみる。ぱっと思いつくのは以下の2つ。

  1. FrameLayoutで次のやつをずらしたりして頑張る
  2. ViewPagerでページ数をほぼ無限にする

ほんとうの意味で無限といえるのはFrameLayoutでtouchイベント拾ってScrollイベントをOverrideするやつなんだろうけど、数が増えるとLayoutの管理を自分で実装しないとメモリをばかみたいに食うことになるから手間がかかる。ということで、今回はViewPagerを使ってさっくりと組んでみた。

MainActivity.java

public class Main extends FragmentActivity {

    private InfinateSwipeViewPagerAdapter mPagerAdapter;
    private ViewPager mViewPager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mPagerAdapter = new InfinateSwipeViewPagerAdapter(getSupportFragmentManager());

        mViewPager = (ViewPager)findViewById(R.id.viewpager);
        mViewPager.setAdapter(mPagerAdapter);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mViewPager.setCurrentItem(InfinateSwipeViewPagerAdapter.MAX_PAGE_NUM/2);
    }
}

InfinateSwipeViewPagerAdapter.java

public class InfinateSwipeViewPagerAdapter extends FragmentPagerAdapter {

    public static final int MAX_PAGE_NUM = 1000;
    private static final int OBJECT_NUM = 3;

    public InfinateSwipeViewPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
    	Fragment fragment;
    	int diff = (position - (MAX_PAGE_NUM / 2)) % OBJECT_NUM;
    	int index = (0 > diff) ? (OBJECT_NUM + diff) : diff;
    	switch(index) {
    	case 0:
    		fragment = new FirstFragment();
    		break;
    	case 1:
    		fragment = new SecondFragment();
    		break;
    	case 2:
    		fragment = new ThirdFragment();
    		break;
    	}
        return  fragment;
    }

    @Override
    public int getCount() {
        return MAX_PAGE_NUM;
    }
}

解説

今回の例は3つのFargmentが循環する例。ポイントは以下の3点。

  1. PagerAdapterのgetCountでそんなにSwipeできないだろーって数を与える(*1)。
  2. MainActivityのonResumeでPositionを真ん中に持ってくることで最初から左にもSwipeできるようにする。
  3. Positionは初期値をonResume()で変えてるからgetItem()で渡されるpositionを元に初期値からどれだけ移動したかを計算してInstanceを返答する。

注意点

実装する上で気をつけなければならないのは以下の3点。

  1. getItem()で返答するInstanceはPositionごとに別のものを返さなければならない。そうしないとAndroidが怒って落ちちゃう。開放をAndroidがやってくれてるからだめなんだと推測される。
  2. 毎回NewしてるからNewするコストがかかる。
  3. 循環しているとはいえ実際は別のInstanceが返答されてるから前に変更した値が循環して表示した時には初期値に戻る。

まとめ

ViewPagerでやるとAndroidが勝手に表示しているViewの左右1つを作ってくれてそれ以外は開放してくれるからメモリ管理がひたすら楽。循環じゃない場合はindexをFragmentのコンストラクタに渡してごちゃごちゃやってやればうまくいくんじゃないかと。気が向いたらFrameLayoutで実装してみるかも。まぁ、使えるところはCalenderとかそのへんだろうから、わざわざFrameLayoutとかで実装しないでViewPagerでやるほうが無難だと思う。

*1: Integer.MAX_VALUEとか本当はやりたいんだけど、それやるとViewPagerが許容してくれなくてバグるみたいだから適当な値を入れる。

LinearLayoutをタップ時にBackgroundの色を変えたい(詳細)

せっかくコメントを頂いたので詳細版を。

作るもの(左:押す前、右:押した後)

/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/lnrButton"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="left|center"
    android:clickable="true"
    android:background="@xml/linear" >

    <ImageView
        android:id="@+id/imgBefore"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:duplicateParentState="true"
        android:src="@xml/img" />

    <TextView
        android:id="@+id/txtTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:duplicateParentState="true"
        android:textColor="@xml/text"
        android:text="Sample Title" />

    <ImageView
        android:id="@+id/imgAfter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:duplicateParentState="true"
        android:src="@xml/img" />

</LinearLayout>

/res/xml/linear.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:state_pressed="true" >
        <color android:color="#555" />
    </item>
    <item android:state_pressed="false">
        <color android:color="#000" />
    </item>

</selector>

/res/xml/text.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:state_pressed="true"
        android:color="#FF0" />

    <item android:state_pressed="false"
        android:color="#FFF" />

</selector>

/res/xml/img.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:state_pressed="true"
        android:drawable="@android:drawable/btn_star_big_on" />

    <item
        android:state_pressed="false"
        android:drawable="@android:drawable/btn_star_big_off" />

</selector>

ざっくり解説

各要素でタップ時に変更したい要素についてselector要素を指定している。selector要素のstate_pressedが押されているかどうか。trueなら押されていて、falseなら押されていない。それぞれで設定したい値をitemに書き込む。後は前回書いたようにclickできるようにしてあげればきちんと動く。本当はSelectorのxmlファイルはdrawableフォルダに入れてあげたほうがいいのかもしれないけど、ごちゃごちゃしてわかりにくくなるから僕はxmlフォルダに入れる派(*1)。itemの各状態についてはここらへんが参考になるかも。

一応動作確認したので動くはずです。もしもっといいやり方があれば教えてください。

*1: 別にこだわりがあるわけじゃないからすぐに辞めるかも

プロセスにこだわるのは悪なのか?

例えば彼女を作りたいって考えたとする。彼女を作るために多くの人と出会ったほうが確率が高いと考えて合コンに明け暮れる。もしくは一人に絞ったほうがいいから誰か一人に全ての時間を費やしたほうがいいと考えて一人の人に時間をかける。目標は同じであってもアプローチの仕方が全然違う。どっちが正解かはやってみなければ分からない。けど、目標に至るまでのプロセスをすごく考えてしまって、これをやるぐらいならこっちをやったほうが効果が上がるのではないか?とかいうことをよく考える。

目標に向かっているという事実はどちらも変わりない。けど両方を取ることはできない。多くの人と会う時間を作るということは一人の人と会う時間が少なくなるということだし、一人の人に時間を使うということは多くの人と会う機会を減らいているということだ。それじゃあどっちに進めばいいの?って迷ったり考えたりする。どちらかの手段を選んだからといって、その一方しか使えないというわけではないから合コンしながら一人の女性とあってもいいし、一人の女性を口説きながら合コンに行ったりすればいい。けど、どちらかを選んでいる状態で他方のことをやると「それって時間の無駄じゃね?」って思ってしまう。

どちらの道が正解というのはなくてやってみても正解がわからないことのほうが多いということは頭ではわかっている。けれども「もっといい方法があるのではないか?」とか「もっと視点を変えれば別の考えが出てくるんじゃないのか?」とか考えてしまう。例えば彼女を作りたいっていう考えは一人でいる時間が寂しいっていうことだったり話し相手が欲しいってことだったり結婚がしたいってことだったりその先の何かを求めるための一つの施策なのかもしれない。それなら、そっちの目標に向かって別の視点で考えたほうが効率がいいのではないのか?って今度はそっちに考えが堂々めぐりしだす。

これが人生の話で僕一人だけで完結するのであれば別に問題ない。回り道は回り道でそれなりに楽しいし、なにもやらないよりやったほうが絶対マシだし考えることすら無駄なんじゃないかと思う。けど、仕事となると話が違う。僕の行動にはお金が払われていてそれに見合った価値を出さなければならないし出したいと思っている。1時間でできることを10時間でやる人間より1時間でできることを1分でできる人間のほうがいいに決まってる。それは間違いない。だから僕は目標に向かっていくためのプロセスにこだわってしまうし目標を明確にして作業を行わなければならないんじゃないかと考えてしまう。

一人でやっているときは自分なりに目標とそれに至るまでのプロセスを明確にして考えたとおり進めばいいだろう。けど複数人でやるときはどうなんだろうか?僕が思い描くプロセスと他の人が思い描くプロセスが違った場合はどちらを尊重すべきなんだろうか?プロセスと目標が決まるまで動かずに話し合いを続けるべきなんだろうか?プロセスにこだわって何もやらないのは悪だと思うが、全くプロセスにこだわらずに作業を行うのもなにか違う気がする。それはそれで時間に対する無頓着さっていう点で悪だとみなされてもしょうがないのではなかろうか。プロセスと目標の明確化は複数人でやるときの肝だとは思うが、それに引きずられすぎられすぎて作業が行えないという状況は避けたい。だけど、ただ闇雲に進みたくはないというこのジレンマ感の解消法はどこかにあるんだろうか?

書いてるうちに結論が出るかと思ったけど別段結論は出ない。みんなその人なりに考えた結果のベストなプロセスを主張していて、最短距離で目標に向かって進もうとしているということだけは間違いない。それが疑いようもない事実としてそこにあるからそれでいいんだろう。目標が同じであればそれぞれが思い描くプロセスに向かって全力で進めばいい結果が出るんだろう。っという短絡的で楽観的な結論が今のところの僕の中での答えだ。

この考えを踏まえた結果、僕は彼女を作るという考えを伴侶を作るという考えに改めて、両刀になることで幅を広げるという試作に出てみることにする。これなら間違い無いだろう。これはモテる。

LinearLayoutをタップ時にBackgroundの色を変えたい

LinearLayoutを使用してちょっと凝ったボタンみたいなのを作った時に有効。クリックした時に色を変えてちゃんとこいつがクリックされてますよーってのがわかるようにする。

selectorを作る必要があるけど、そこの説明は省略。こことか参考になる

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:background="@drawable/button_sample_background" >

    <ImageView
        android:id="@+id/imgIcon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:duplicateParentState="true"
        android:src="@drawable/button_sample_icon" />

    <TextView
        android:id="@+id/txtTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:duplicateParentState="true"
        android:textColor="@drawable/button_sample_title" />

</LinearLayout>

重要なのはLinearLayoutのclickableと子Viewに設定しいるduplicateParentState。

clickable
trueを設定するとClickを察知するようになる。
duplicateParentState
trueを設定すると親ViewのClickを優先的に動くようになる。要は親ViewをClickすると子Viewにもそれが伝わる。

この2つを設定することで、LinearLayoutのClickイベントを子Viewに伝えて文字の色を変えたり、画像を差し替えたりしてちょっと凝ったボタンを作ることができる。duplicateParentStateがわからなかったなー。

ギャラリーの共有からアプリを呼び出す方法

Androidのgalleryで画像を選択して共有を押した時にアプリを呼び出したい場合はIntent-filterをActivityに設定してあげればいい。

<intent-filter>
	<action android:name="android.intent.action.SEND" />
	<category android:name="android.intent.category.DEFAULT" />
	<data android:mimeType="image/*" />
</intent-filter>

複数選択したものを取得したい場合はactionを変更する。

<intent-filter>
	<action android:name="android.intent.action.SEND_MULTIPLE" />
	<category android:name="android.intent.category.DEFAULT" />
	<data android:mimeType="image/*" />
</intent-filter>

mimeType=image/*だと動画まで取得してしまう。写真だけ撮りたい場合はmimeType=pngとか色々設定してあげるといい。

<intent-filter>
	<action android:name="android.intent.action.SEND" />
	<category android:name="android.intent.category.DEFAULT" />
	<data android:mimeType="image/jpeg" />
	<data android:mimeType="image/jpg" />
	<data android:mimeType="image/png" />
	<data android:mimeType="image/bmp" />
	<data android:mimeType="image/bitmap" />
</intent-filter>

こんな感じで大丈夫。以下、参考になったURL。

http://shokai.org/blog/archives/5401

http://stackoverflow.com/questions/4722928/intent-for-getting-multiple-images

スマートフォンの画面で表示されている高さを取得する

UserAgentを取得して高さをUserAgent毎にごちゃごちゃやると上手くいく。

    // スマホ種別判定
    var ua = navigator.userAgent;
    if(ua.indexOf('Android') > 0) {
        var nPageH = document.height > document.width ? document.height : document.width;
        var nViewH = window.outerHeight > window.outerWidth ? window.outerHeight : window.outerWidth;
        if (nViewH > nPageH) {      
            nViewH = nViewH / window.devicePixelRatio;
            $('body').css('height', nViewH + 'px');
        }
    } else if(ua.indexOf('iPhone') > 0) {
        var nViewH = document.documentElement.clientHeight > document.documentElement.clientWidth ?
                                document.documentElement.clientHeight : document.documentElement.clientWidth;
        if (ua.indexOf('iPhone') && !window.navigator.standalone) nViewH += 60;
        $('body').css('height', nViewH + 'px');
    } else {
        return;
    }

これでやると結果的に高さがnViewHに入る。

パチンコから学ぶコンプガチャに変わる試作3つ

先に行っておくと、僕はカードゲームを作っている中の人でもなんでもない単なるパチンコ好きのカードゲーム嫌い。だからこれから書くことは、法律云々でやっちゃダメなことかもしれないし、既にやってることかもしれない。

ガチャを引く台を自分で決めれるようにする

台によってレアカードが出る確率が違うふうなことを記載して、自分で台が選べるようになると引く人も増えるんじゃないだろうか。自分で選んだ台だからレアカードが出なかったのは違う台を選ばなかったからじゃないのか?っと錯覚させることで次に別の台でガチャを引くっていう動作につながるかもしれない。

台の直近で出たカード履歴を出す

今まで出たカードの履歴を出すことによって、クズカードが続いたから今度はレアカードが出るんじゃないかと錯覚してガチャを回してしまう。俗に言われるギャンブラーの誤謬ってやつ。これに台選択の自由まで与えると、クズカードが出続けた台でやればレアカードが出るんじゃないの派とレアカードがいっぱい出てるほうがレアカードが出る確率がいいんじゃないのか派に分かれてガチャを引く人が増えそう。

確率変動を設ける

何分の1の確率で確率変動に入るようにする。確率変動中は今まで30分の1だったのが2分の1で引けるようになりますよみたいな触れ込みを書いておく。こうするとこれまで使ったものも確率変動一発でレアカードがじゃんじゃん出るんじゃないのか?と錯覚してクズカードを引きまくる人が出てくる。台選択の自由を出しておくと、レアカードが出てすぐだと、これは確率変動にいるんじゃないのか?と錯覚してガチャを回してしまう。

まとめ

ざっくりとすぐに思いつくものを書くと↑の3つぐらいだけど、これ以外にも「天井制度」とか「フリーガチャカード」とか色々浮かんでくる。ということは結局ガチャがあるカードゲームってパチンコと作りはあまり変わらないんじゃないのかなーっておもったりもする。僕個人としてはあまりカードゲームは好きではないので(*1)、これを気に単純なカードゲームは全部潰れてしまってみんなが楽しめるゲームがもっと流行ってくれるといいなぁと思う。

*1: クリックするだけで進むやつね。マジック・ザ・ギャザリング的なのは好き

属人化とシステム化

考えさせられたので書いてみます。

デシジョンメーカー

システムを属人化させるな!っていうのは僕が働き出した頃すごく言われていたのを覚えています。確かに誰かがいなくなっても回る状況・誰かが倒れても動く状況というのを作るというのは大事なことですし必要です。ただ、すべてを属人化しなければならないというのは僕は否定的でどうしても属人化せざる得ない部分ていうのは出てくると思っています。

例えば何か物を作る場合を考えてみます。既に作成方法が決まっている「物を作る」という作業のみで言えば、すべてを機械でやって機械のメンテナンス方法・動作方法をマニュアル化すれば属人化せずに誰が倒れてもすぐに新しい人が動けます。これがうまく動く属人化ですしシステムだと考えています。ただ、機械やシステムというのは既に発明された物を作り上げるには適していますけど、なにか新しいものを創りだすというのには不向きだと思います。なにかの機械が新しい機械とか道具を作り出せるわけが無いですし、それを創りだすっていうのが人間が唯一機械に勝てる部分だと思います。決まったルールの中では既に機械のほうが頭がいいというのは証明されていますし(*1)、複雑で色々新しいことをする場合に属人化というのは適しているんでしょうね。

0か1で割りきってしまえばすごい楽なのです。「属人化は悪だ!替えがきくようにしとかないと行けないじゃないか。」とか、「属人化いいじゃない。効果高そうじゃん。」とか、全面的に賛成したり反対すると楽なんだけど、でも多分正解は適材適所で、うまく自分のなかで使い分けることが必要で、自分が成長できそうなんだけど考えることが増えて困ります。

言われているとおり0か1かで割り切れる話じゃなくて適材適所なんですよねー。今は新しいことを始めている。言い換えれば機械を作り出す作業の方をやられていて、創りだす部分ではどうしても属人化せざる得ないと思います。そこを過ぎると自分の中である程度この問題にはこの答えってものが出てくるようになって、それをどこかに吐き出すことでシステム化がなされるんだと思いますよ。んで、そこが一番きつくて楽しい作業なんじゃないでしょうか。産みの苦しみというかなんというか。

まぁ、そんなことわかってるよーって声が聞こえてきそうですけど、自分の考えをまとめて吐き出す意味でも書いてみました。なにか新しいものを作るって難しいけど楽しい!!!

*1: 例えばチェスとか

history.back()で戻るとscrollイベントが取得できない

ハマったのでメモ。

挙動はタイトル通りなんだけど、iPhoneのSafariでhistory.back()を使用すると戻った画面でscrollイベントが取得できない模様。以下でscrollイベントを検出しようと3つを試してみたけどどれもだめ。

// jQueryで普通に
$(window).scroll(function() {
   console.log('scroll now');
});

// イベントにbindしてみる
$(window).on('scroll', function() {
    console.log('scroll now');
});

// 生のjavascriptで
window.onscroll = function() {
    console.log('scroll now');
};

history.back()で戻る前はどれもログが出力されるけど、戻った後は出力されない。Android標準ブラウザだと検出されるからiPhoneのSafariのバグなんじゃないかと疑っている。そもそもiPhoneのSafariとAndroid標準ブラウザだとscrollのイベント検出タイミングが

  • iPhoneのSafari:Scrollが完了したタイミングでイベント発生
  • Android標準ブラウザ:Scrollが発生している最中は定期的にイベントが発生し続ける

という風に違う。

PCブラウザだと、Android標準ブラウザと同じ挙動をするものが多い(*1)のでどうもiPhoneのSafariのScrollイベントとのbindがうまく動いてないんじゃないかと予想。position:fixed;のバグといい、iPhoneのSafariって出来が悪い気がする。ちなみにiOSの5.0で試してみました。なにか打開策をご存知のかたは教えてください。

追記

一応思いつく回避策を列挙。どれもぱっとしない。。。

  • history.back()を使わずに、location.hrefで頑張る
  • scrollイベントを使わずに、touchmoveイベントで頑張る
  • history.back()で戻ってきた場合は強制リロード

*1: Chrome, Firefoxで試した

人生は取捨選択だと思う

前にも書いた気がするけどもう一回

人生は取捨選択の繰り返しだと思う。何かを得るためには何かを捨てないといけないし、逆に何かを捨てれば何かを得ることができる。

結婚は幸せを得る代わりに一人の時間を捨てる。

深酒は楽しさを得る代わりに明日のすっきりした目覚めを捨てる。

仕事はお金を得る代わりに自由な時間を捨てる。

こんな感じで、基本的には何かを得るためには何かを捨てなければならない。そう考えると人生はすごく単純でわかりやすいと思う。要所要所で自分はうまくいったことがあったのであれば、それは運がいいのではなくて過去に何かを捨てて努力した結果だと思うし、うまくいかなかったことがあったのであれば、それは過去に未来の成功を捨てる何かを得た結果だと思う。

言いたいことがよくわからなくなってきたからココらへんでやめる。

一つだけわかっていることはゴールデンウィークの最初に遊びすぎたせいで体調を崩し、後半は体調が悪くて何も出来なかったっていう結果が残ったってこと。これは成功をとったのか失敗をとったのかはわからないけど、少なくとも失敗を犯していることは確か。

歳をとって無理が効かなくなったもんだ。。。

来週から走りだそう。