2012/02/24

gumroad考察

gumroadというサービスが注目を集めてますね。自分はちょっとしたトラウマがあって、Paypalのアカウントをまだ作っていないので試していないのですが、これまでとは比較にならないほど簡単に自分のデジタルコンテンツを世界に向けて販売できることが注目を集めている理由みたいです。

と同時に、いやこれは違法コンテンツの温床になるのでは無いか?とか、リンクが漏洩したら誰でも無制限にダウンロードできるのは問題だとか、負の面も報じられてます。

中には、gumroadだけに限った話では無いものもあるし、お手軽さ故にそういうことがより容易に出来てしまうということもあるのでしょうが、個人的には(まだ売れるものなんて何も無いけど)自分のコンテンツを売る販路のひとつとして、こういったサービスがうまく立ち上がってくれればいいなと思っているので、大きな問題が起きないよう、陰ながら応援しています。

で、今回はそういうことを話題にしたいんじゃなくて、私が個人的にgumroadがすごいと思った点を述べたいと思います。

と、その前にオンラインストアと言ってまず最初に思い浮かべるのは何でしょうか?アマゾンですかね、やっぱり。私がgumroadがすごいと思うのは、これのおおよそ対局を行っているのでは無いかという点です。

gumroadのサイトに行けばわかりますが、まず、ここで何を売っているのか?という情報がほとんどありません。それもそのはずで、gumroadは販売するコンテンツを簡単に紹介するページ及びそのリンクと決裁システムの提供しかしてません。

そのリンクがわからなければ、販売しているコンテンツにたどり着くことさえままならないのです。

アマゾンだったら、ユーザーのレビューとかサジェスト機能とか、あの手この手で訪問者にコンテンツを買わせる仕掛けが用意されているのですが、gumroadではそれがいっさい無くて、コンテンツが売れるかどうかは販売者の努力に強く依存しているのです。

このやり方では、ユーザーにリーチできるプレゼンスの無いひとのコンテンツはほとんど売れないでしょう。

ただ、そこをビジネスチャンスとばかりにgumroadで販売しているコンテンツを紹介するなどの周辺サービスがこの短期間で沢山立ち上がってます。gumad.meとか。

ちなみに、gumroadは販売時に売値の5%+30cの手数料を取ることで収益を上げるのですが、決済システムにStripeを使っているらしく、ここの手数料が2.9%+30cです。なので、そのまま単純に引かれるとgumroadの取り分は売値の2.1%と、かなり少ないことがわかります。

また、サービスの性格上、高額なコンテンツがバンバン売れるということも考えにくく、平均の販売単価もかなり低いのでは無いかと思われます。となれば、本来であれば、薄利多売で沢山コンテンツが売れてくれないと、儲けが出ないビジネスモデルであると考えられます。

にも関わらず、売る為の努力をほとんどしないというのは、一体どういうことなのか?で、ここがgumroadの巧妙なところではないかと「勝手に」思っているのですが、実のところそういった販売の為の努力はサーバーの運営コストに跳ね返ってくるわけです。

(えー、いまさらですが、この話はgumroadがAWSとかGAEとかのサービスを使って運営されているという仮定での考察です・・)

沢山のトラフィックを使って、画像やテキストなどで美辞麗句を並べたところで、ユーザーは商品を買ってくれるとは限りません。となると、その時の通信料は全くの無駄になるわけです。

アマゾンはユーザーにより多くの時間をサイトにとどまってもらって購入の機会を増やすのが戦略ですが、gumroadは恐らく、ユーザーがサイトに訪れるのは購入するその瞬間だけがベストで、買うか買わないかもわからないのに長くとどまってトラフィックを消費されるのはたまったものでは無いのでしょう。

だからこの先も販売しているコンテンツの検索とか、そういう類の機能は実装されないんじゃないかと想像してます。

つまり、商社としての機能を極限までシンプルに実現したのがgumroadではないか?ということです。

恐らく今後、gumroadに類似のサービスが沢山出てくるとは思いますが、gumroadという先駆者に対抗する手前、販売手数料はかなり低額な設定にせざるを得ないでしょう。

その際に、このことを理解しないでアマゾンのような機能てんこ盛りのサイトで対向しようとすると、運営コストに苦しんで足元をすくわれることになるやも知れません。まぁ、広告収入とか別の収益源を確保できれば、その限りでは無いのかも知れませんけどね。

もし、gumroadに死角があるとすれば、Stripeを利用すれば類似のサービスを立ち上げることがわりと簡単にできそうで、顧客へのリーチ力があるひとなら、個人で世界を相手に販売サイトを立ち上げる時代が実はすぐそこにやって来ているのかも知れない、ということでしょうか?

2012/02/03

ActionBarにDrop-downナビゲーションを追加する

ActionBarにCalendarやGmailのようなDrop-downナビゲーションを追加しようと思ったんです。

やり方自体は、SpinnerAdapterをActionBar.setListNavigationCallbacks()の引数に指定すれば良いのですが、公式ドキュメントに書いてあるサンプルは選択できる項目が予め決まっているResourceファイルから作るので、コーディングは最小限で済む代わりに動的に選択項目を作りたい場合などはこの方法は使えません。

で、軽くググって見た限りでは世の中にほとんどサンプルが無くて、ちょっと苦労したのでここに簡単にまとめておこうかと思います。そのうちTechBoosterさんにちゃんとした記事が載るような 気がするので、その時はこんないい加減なblogよりもそちらの方をどうかご参照ください(笑

SpinnerAdapterとして実装しないといけないインターフェースは下記
メソッド名概要
int getCount()選択項目の個数を返す
Object getItem(int position)position位置の要素を返す
long getItemId(int position)position位置の要素のIDを返す
int getItemViewType(int position)getView()で作成されるViewのタイプを返す
View getView(int position, View convertView, ViewGroup parent)position位置のViewを返す
int getViewTypeCount()Viewのタイプ数を返す
boolean hasStableIds()アイテムのIDがデータによらず安定しているか?
boolean isEmpty()リストが空か?
void registerDataSetObserver(DataSetObserver observer)データの変更を検出するオブザーバーを設定
void unregisterDataSetObserver(DataSetObserver observer)データの変更を検出するオブザーバーの設定を解除
View getDropDownView(int position, View convertView, ViewGroup parent)drop-downのpopupに表示されるViewを返す

ListViewなんかでAdapterに馴染みがあればほとんどのメソッドに関してはご存知かと思いますが、ここでの肝はgetView()とgetDropDownView()です。

メソッド名からもわかる?ように、getView()がActionBarに選択項目として描画されるViewを返し、getDropDownView()がdrop-downのポップアップに描画されるViewを返す為のメソッドです。

簡単にテキストだけ描画されるようなメニューならメソッド内でTextViewを作って返してやればいいようなものなんですが、getDropDownView()がどうにもうまく行きませんでした。コンパイルまでは問題無いのですが、ランタイム時にエラーが出てアプリが死にます。

LogCatのログを見てみると、どうもLayoutの型変換でエラーが出ているようなのですが、いろいろ試してもうまく行かないので最終的にはlayoutのxmlファイルを定義して、LayoutInflaterでViewを作成するようにしました。

ということで、最終的に以下のようなコードでうまくいったのですが、何か勘違いしているかも知れませんので、識者の方からアドバイスをいただけると幸いです。

SampleActivity.java
public class SampleActivity extends Activity implements OnNavigationListener {
  private ArrayList menuList = new ArrayList();
  private LayoutInflater mInflater;
  private ActionBar mActionBar;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mInflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mActionBar = getActionBar();
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
    mActionBar.setListNavigationCallbacks(new mSpinnerAdapter(), this);
 
    // drop-downメニューのデータを自由に作成
    // ここではコード上の文字列を設定してますが
    // 例えば、IntentのBundleで受け取ったデータ
    // をもとに作成してもいいでしょう
    menuList.add("One");
    menuList.add("Two");
    menuList.add("Three");
  }

  private class mSpinnerAdapter implements SpinnerAdapter {

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

    @Override
    public Object getItem(int position) {
      return menuList.get(position);
    }

    @Override
    public long getItemId(int position) {
      // とりあえずpositionをIDとして返す
      return position;
    }

    @Override
    public int getItemViewType(int position) {
      // 何を設定すればよいのかわからないのでとりあえず
      // IGNORE_ITEM_VIEW_TYPEを返す
      return IGNORE_ITEM_VIEW_TYPE;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {   
      if (convertView == null) {
        // getView()はこれでうまく行く
        convertView = new TextView(mContext);
        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        convertView.setLayoutParams(params);
      }
      ((TextView) convertView).setText(menuList.get(position));
      return convertView;   
    }

    @Override
    public int getViewTypeCount() {
      return 1;
    }

    @Override
    public boolean hasStableIds() {
      return false;
    }

    @Override
    public boolean isEmpty() {
      return false;
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer) {
      // TODO Auto-generated method stub   
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {
      // TODO Auto-generated method stub
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
      if (convertView == null) {
        // getDropDownView()はうまくいかないので
        // LayoutInflaterでViewを作成
        convertView = mInflater.inflate(R.layout.dropdown_menu, parent, false);    
      }
      ((TextView) convertView.findViewById(R.id.dropdown_menu_item)).setText(menuList.get(position));
      return convertView;
    } 
  }

  @Override
  public boolean onNavigationItemSelected(int itemPosition, long itemId) {
    // itemPositionの選択に応じた処理
    return true;
  }
}

dropdown_menu.xml
<?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="match_parent"
    android:orientation="vertical" >

 <TextView
        android:id="@+id/dropdown_menu_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>