2013/03/27

Androidでスプラッシュ画面を表示する

何番煎じかわかりませんが、Androidでスプラッシュを表示する方法です。

スプラッシュを実現するにあたって、編集するファイルは

  • 一番最初に表示するActivity(MainActivity.java)
  • アクティビティのxmlファイル(activity_main.xml)

の2つです。

まずxmlファイルの方を編集していきます。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:animateLayoutChanges="true"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        // アクティビティに表示するコンポーネントをここに書く
        
    </LinearLayout>

    <ImageView
        android:id="@+id/splash_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@string/content_description_splash"
        android:scaleType="fitXY"
        android:src="@drawable/splash" />

</FrameLayout>

一番外側をFrameLayoutでラップして、通常の画面の上にSplash画像が重なるようにします。

android:animateLayoutChanges="true"

これは、レイアウトが変更されるときにアニメーションするかどうかを設定しています。
フェードアウトしたほうが綺麗なので、trueに設定しています。(APILevel11以上の時なので,2.xではアニメーションしません)

次に、MainActivity.javaの方を編集します。
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.actiivty_main);
        
        initSplashView();
        
        SplashClearTask splashClearTask = new SplashClearTask();
        splashClearTask.execute();
    }
    
    private void initSplashView() {
        ImageView splashView = (ImageView)findViewById(R.id.splash_view);
        // スプラッシュ表示中はクリックを無視する
        splashView.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                return;
            }
        });
    }

    private void clearSplash() {
        ImageView splashView = (ImageView)findViewById(R.id.splash_view);
        splashView.setVisibility(View.GONE);
    }

    private class SplashClearTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... params) {
            try {
                // 一定時間待機
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            clearSplash();
        }
    }

ポイントは、スプラッシュ表示中のクリックを無効化している部分と、SplashClearTaskの部分です。

FrameLayoutでViewを重ねるだけでは、その下にあるViewをクリックできてしまうので、スプラッシュ画像が表示されている間、その下にあるコンポーネントをクリックできないようにしています。

SplashClearTaskでは、スプラッシュを一定時間表示してからスプラッシュを消すためにdoInBackgroundでThread.sleep()を使っています。

アクティビティのレイアウトの階層が1段深くなりますが、スプラッシュ用にアクティビティを使わなくてすみます。

注意するポイントとしては、アクションバーやメニューバーは表示されてしまうので、スプラッシュの前後で表示の切替が必要です。

2013/03/12

アプリで個人として独立してやっていくためにいくら稼げばいいのか

そろそろ確定申告の締切が迫って来ました。

独立されている方や、副業でそこそこ稼いでいる方、もう確定申告は済ませました?

青色申告で65万円控除できる締切は3/15日です。

僕は今日、提出してきました。(昨年はこんなにギリギリじゃなかったのに)

いつもはこのブログでは、Androidの備忘録的なのをぽろぽろ書いているのですが、今日はちょっと違ったことを書きます。

それは、

「アプリで食べていくためにはどれくらい稼がないとダメなのか。」

です。

これを書こうと思った理由は、
  • 今までアプリの売上で2回確定申告してきて、だいたいどのくらい稼げば大丈夫なのか感じがつかめてきたから
  • 新年度を迎えるにあたり、今の会社をやめて独立しようとか思っている人の参考になればとか
そんな感じです。

まだ2年ぐらいですが、そんなに的はずれな内容にはならないと思います。

例として「都内一人暮らし」で出してみます。

まず、ひと月にかかる生活費ですが、だいたいこんな感じだと思います。

家賃:6~7万ぐらい
食費:3~4万ぐらい
水道光熱費:1万ぐらい
通信費:1万ぐらい
その他:1~2万ぐらい

で大体月に15万ぐらいかな。自炊とかもっと安いところに住んでたりすると12万ぐらいで済むかもしれません。家族を養う必要があるひとはもっとかかってきますね。とりあえず一例なので。合計は180万ぐらいです。

でアプリを開発ってことでその他必要なものを書いていきます。(1年でかかる費用)
PCとか設備費:10万ぐらい(ノートPC買ったり、PCのパーツ買ったり)
書籍費:1~5万ぐらい(ネット主体の人は安く済む)
開発者登録とか:1万
検証端末購入費とか:0~10万

大体20万ぐらいです。

その他にかかってくる費用(1年でかかる費用)
国民健康保険:前年の稼いだ額に依存するけど大体20~30万ぐらい
年金:14980円x12=18万ぐらい
奨学金返済:1~2万x12=12~24万ぐらい

社会保険料+αでだいたい60~80万ぐらいです。奨学金借りてこなかった人はその分安くなります。両親に感謝ですね。

ここまでで「一年に必要な金額」はだいたい180+20+70で270万円です。じゃあこの額を1年で稼げばいいやって思って270/12で大体月に23万ぐらい稼げばいいなって試算がでますが、そうはいきません。

そうです。「税金」です。

生活している以上、その国に税金を納めなければいけません。
で、かかってくる税金の種類は次のとおりです。

  • 所得税
  • 個人事業税(アプリ開発の場合、地方によって課税されたり非課税だったり)
  • 住民税
  • 消費税(売上が1000万以上あった場合、その2年後から課税)

所得税は「総売上-必要経費」の金額が高くなるほど、税金が高くなります。細かい計算は省いて、だいたい15%ぐらいかかってきます。

次に個人事業税が大体5%ぐらいです。知り合いの方でも課税されてたり、非課税だったりします。ただ、まだアプリで稼ぐ人が現れ始めたのがここ2~3年なので、今後アプリ開発で稼ぐ人がもっと増えると制度が変わって課税されると思います。

次に住民税ですが、均等割という全員にかかるものが3000円で、市民税が6%、県民税が4%です。なので10%ととしておきます。

次に消費税ですが、これは1期目2期目ではまず課税されません。1期目で1000万以上売り上げた場合、3期目から消費税を納めなければいけません。今は5%ですが、一年後は8%、二年後の10月からは10%です。
見なし仕入率とかあるので実際にはその半分ぐらいです。その分を所得から計算し、申告納税します。

税金は以上ですが、だいたい稼いだ金額のうち30%ぐらいは持っていかれると見ていたほうがいいです。
経費を積むと納税額を抑えられますが、アプリ開発自体はそんなに経費がかかる事業じゃないので、積むにも限界があります。

これらから、簡単ながら計算式が作れます。

「稼がなければいけない金額」-「税金(稼いだ金額の30%)」=「一年に必要な金額(270万)」

「稼がなければいけない金額」× 0.7 = 「一年に必要な金額(270万)」

「稼がなければいけない金額」 = 「一年に必要な金額(270万)」× 1.43

「稼がなければいけない金額」 = 386万

になります。これを12ヶ月で割ると大体32万円ぐらいです。

ただ、独立してやっていくと、会社員のような保証は無いので、体を壊して稼げなくなった時とかのための保険として貯蓄するようにもっと稼いでないとまずいです。
年金も、国民年金だけで、会社員のように厚生年金に入らないので、もらえる年金も少ないです。(年金システム自体が無くなるかもしれないけど)
なので余裕をもてるように一年に100万ぐらい貯蓄していくとして、だいたい月に40~50万ぐらいは稼いでおかないとやっていくのは厳しいです。

結論

アプリで食べていくためには月40~50万ぐらい稼がないと厳しい。

アプリで独立しようとか考えている人の参考になれば。

2013/03/03

ListViewのlayout_heightにwrap_contentを使ってはいけない

androidの開発でよく使うListViewのコンポーネントですが、使うときに注意する事があります。

レイアウト指定で、ListViewのlayout_heightの値にwrap_contentの値を使わないことです。

これをwrap_contentとかにしておくと、listviewにつけるadapterのgetView()メソッドが何度も呼び出されて無駄な処理が発生します。

同じListViewでlayout_heightの属性を変えた場合のLogを出してみました。

layout_height="match_parent"のとき

layout_height="wrap_content"のとき

実際にはcountが54まで繰り返し処理されていました。

原因はよくわからないですが、wrap_contentだと高さの値がコンテンツのサイズになるのでそこら辺が関係してるのかなと思います。

ちなみに,「layout_height="0dp" layout_weight="1"」でも 「layout_height="match_parent"」と同じ振る舞いで問題はありませんでした。

2013/02/28

Androidの自作CalendarView

AndroidでCalendarViewを使うにはいくつか方法があります。
  1. Android公式のandroid.widget.CalendarViewを使う
  2. CalendarView 公開しましたのCalendarViewを使う
  3. 自分で作る
1.に関してはAPIが11以上でないと使えないので、2.x系以上をサポートするアプリでは使えません。

2.に関してはページ送り、祝日情報などが含まれていてかなり有用なCalendarViewだと思います。

3.本当に最低限のCalendarViewでいいとか、後で色々細かいところを調整したいとかはこっちの方がいいのかなと思います。

今作っているアプリにCalendarView使いたいなってことで、上記の3件を考慮したのですが、今回作っているアプリに一番しっくりきそうなのは3.の形だったので、


のソースコードを元に作りました。(主にリファクタリングと、少しの調整)
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.TextView;

import <package_name>.R;

/**
 * 指定した年月日のカレンダーを表示するクラス
 */
public class CalendarView extends LinearLayout {
    @SuppressWarnings("unused")
    private static final String TAG = CalendarView.class.getSimpleName();
    
    private static final int WEEKDAYS = 7;
    private static final int MAX_WEEK = 6;
    
    // 週の始まりの曜日を保持する
    private static final int BIGINNING_DAY_OF_WEEK = Calendar.SUNDAY;
    // 今日のフォント色 
    private static final int TODAY_COLOR = Color.RED;
    // 通常のフォント色
    private static final int DEFAULT_COLOR = Color.DKGRAY;
    // 今週の背景色 
    private static final int TODAY_BACKGROUND_COLOR = Color.LTGRAY;
    // 通常の背景色 
    private static final int DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT;
    
    // 年月表示部分
    private TextView mTitleView; 
    
    // 週のレイアウト
    private LinearLayout mWeekLayout;
    private ArrayList<Linearlayout> mWeeks = new ArrayList<Linearlayout>();
    
    /**
     * コンストラクタ
     * 
     * @param context context
     */
    public CalendarView(Context context) {
        this(context, null);
    }
    
    /**
     * コンストラクタ
     * 
     * @param context context
     * @param attrs attributeset
     */
    @SuppressLint("SimpleDateFormat")
    public CalendarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setOrientation(VERTICAL);
        
        createTitleView(context);
        createWeekViews(context);
        createDayViews(context);
    }

    /**
     * 年月日表示用のタイトルを生成する
     * 
     * @param context context
     */
    private void createTitleView(Context context) {
        float scaleDensity = context.getResources().getDisplayMetrics().density;
        
        mTitleView = new TextView(context);
        mTitleView.setGravity(Gravity.CENTER_HORIZONTAL); // 中央に表示
        mTitleView.setTextSize((int)(scaleDensity * 14));
        mTitleView.setTypeface(null, Typeface.BOLD); // 太字
        mTitleView.setPadding(0, 0, 0, (int)(scaleDensity * 16));
        
        addView(mTitleView, new LinearLayout.LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
    }

    /**
     * 曜日表示用のビューを生成する
     * 
     * @param context context
     */
    private void createWeekViews(Context context) {
        float scaleDensity = context.getResources().getDisplayMetrics().density;
        // 週表示レイアウト
        mWeekLayout = new LinearLayout(context);
        
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.DAY_OF_WEEK, BIGINNING_DAY_OF_WEEK); // 週の頭をセット
        
        for (int i = 0; i < WEEKDAYS; i++) {
            TextView textView = new TextView(context);
            textView.setGravity(Gravity.RIGHT); // 中央に表示
            textView.setPadding(0, 0, (int)(scaleDensity * 4), 0);
            
            LinearLayout.LayoutParams llp = 
                    new LinearLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT);
            llp.weight = 1;
            
            mWeekLayout.addView(textView, llp);
            
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }
        addView(mWeekLayout, new LinearLayout.LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
    }

    
    /**
     * 日付表示用のビューを生成する
     * 
     * @param context context
     */
    private void createDayViews(Context context) {
        float scaleDensity = context.getResources().getDisplayMetrics().density;
        
        // カレンダー部 最大6行必要
        for (int i = 0; i < MAX_WEEK; i++) {
            LinearLayout weekLine = new LinearLayout(context);
            mWeeks.add(weekLine);
            
            // 1週間分の日付ビュー作成
            for (int j = 0; j < WEEKDAYS; j++) {
                TextView dayView = new TextView(context);
                dayView.setGravity(Gravity.TOP | Gravity.RIGHT); 
                dayView.setPadding(0, (int)(scaleDensity * 4), (int)(scaleDensity * 4), 0);
                LinearLayout.LayoutParams llp = 
                        new LinearLayout.LayoutParams(0, (int)(scaleDensity * 48));
                llp.weight = 1;
                weekLine.addView(dayView, llp);
            }
            
            this.addView(weekLine, new LinearLayout.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        }
    }
    
    /**
     * 年と月を指定して、カレンダーの表示を初期化する
     * 
     * @param year 年の指定
     * @param month 月の指定
     */
    public void set(int year, int month) {
        setTitle(year, month);
        setWeeks();
        setDays(year, month);
    }

    /**
     * 指定した年月日をタイトルに設定する
     * 
     * @param year 年の指定
     * @param month 月の指定
     */
    @SuppressLint("SimpleDateFormat")
    private void setTitle(int year, int month) {
        Calendar targetCalendar = getTargetCalendar(year, month);
        
        // 年月フォーマット文字列
        String formatString = mTitleView.getContext().getString(R.string.format_month_year);
        SimpleDateFormat formatter = new SimpleDateFormat(formatString);
        mTitleView.setText(formatter.format(targetCalendar.getTime()));
    }

    /**
     * 曜日を設定する
     */
    @SuppressLint("SimpleDateFormat")
    private void setWeeks() {
        Calendar week = Calendar.getInstance();
        week.set(Calendar.DAY_OF_WEEK, BIGINNING_DAY_OF_WEEK); // 週の頭をセット
        SimpleDateFormat weekFormatter = new SimpleDateFormat("E"); // 曜日を取得するフォーマッタ
        for (int i = 0; i < WEEKDAYS; i++) {
            TextView textView = (TextView) mWeekLayout.getChildAt(i);
            textView.setText(weekFormatter.format(week.getTime())); // テキストに曜日を表示
            week.add(Calendar.DAY_OF_MONTH, 1);
        }
    }

    /**
     * 日付を設定していくメソッド
     * 
     * @param year 年の指定
     * @param month 月の指定
     */
    private void setDays(int year, int month) {
        Calendar targetCalendar = getTargetCalendar(year, month);
        
        int skipCount = getSkipCount(targetCalendar);
        int lastDay = targetCalendar.getActualMaximum(Calendar.DATE);
        int dayCounter = 1;
        
        Calendar todayCalendar = Calendar.getInstance();
        int todayYear  = todayCalendar.get(Calendar.YEAR);
        int todayMonth = todayCalendar.get(Calendar.MONTH);
        int todayDay   = todayCalendar.get(Calendar.DAY_OF_MONTH);
        
        for (int i = 0; i < MAX_WEEK; i++) {
            LinearLayout weekLayout = mWeeks.get(i);
            weekLayout.setBackgroundColor(DEFAULT_BACKGROUND_COLOR);
            for (int j = 0; j < WEEKDAYS; j++) {
                TextView dayView = (TextView) weekLayout.getChildAt(j);
                
                // 第一週かつskipCountが残っていれば
                if (i == 0 && skipCount > 0) {
                    dayView.setText(" ");
                    skipCount--;
                    continue;
                }
                
                // 最終日より大きければ
                if (lastDay < dayCounter) {
                    dayView.setText(" ");
                    continue;
                }
                
                // 日付を設定
                dayView.setText(String.valueOf(dayCounter));
                
                boolean isToday = todayYear  == year  && 
                                  todayMonth == month && 
                                  todayDay   == dayCounter;
                
                if (isToday) {
                    dayView.setTextColor(TODAY_COLOR); // 赤文字
                    dayView.setTypeface(null, Typeface.BOLD); // 太字
                    weekLayout.setBackgroundColor(TODAY_BACKGROUND_COLOR); // 週の背景グレー
                } else {
                    dayView.setTextColor(DEFAULT_COLOR);
                    dayView.setTypeface(null, Typeface.NORMAL);
                }
                dayCounter++;
            }
        }
    }

    /**
     * カレンダーの最初の空白の個数を求める
     * 
     * @param targetCalendar 指定した月のCalendarのInstance
     * @return skipCount
     */
    private int getSkipCount(Calendar targetCalendar) {
        int skipCount; // 空白の個数
        int firstDayOfWeekOfMonth = targetCalendar.get(Calendar.DAY_OF_WEEK); // 1日の曜日
        if (BIGINNING_DAY_OF_WEEK > firstDayOfWeekOfMonth) {
            skipCount = firstDayOfWeekOfMonth - BIGINNING_DAY_OF_WEEK + WEEKDAYS;
        } else {
            skipCount = firstDayOfWeekOfMonth - BIGINNING_DAY_OF_WEEK;
        }
        return skipCount;
    }

    private Calendar getTargetCalendar(int year, int month) {
        Calendar targetCalendar = Calendar.getInstance();
        targetCalendar.clear(); // カレンダー情報の初期化
        targetCalendar.set(Calendar.YEAR, year);
        targetCalendar.set(Calendar.MONTH, month);
        targetCalendar.set(Calendar.DAY_OF_MONTH, 1);
        return targetCalendar;
    }
}

実際に使うときはこんなかんじで

CalendarView calendarView = (CalendarView) findViewById(R.id.carendar);
calendarView.set(2013, 3-1); // 2013年3月にセット

実際に生成されるViewは参考元とほぼ同じです。

後は、日付部分にListenerつけたり、ViewPagerにセットするなりで色々カスタマイズすれば、独自のカレンダーが作れます。

2013/02/07

SimpleCursorAdapterのListViewが即座に更新されない時に確認すべき箇所

SimpleCursorAdapterを使っているLIstViewが更新されなくて色々調べたので書く。

問題だったのは、ListViewのアイテムをクリックした時にSQLiteの該当データを削除する機能を実装していたんだけど、押しても即座に更新されなくてどーすんのかなーっと思って調べた。

よくあるのは
mAdapter.notifyDataSetChanged();
とかで更新するってあるけどこれはどうもArrayAdapterだけの時っぽい。

SimpleCursorAdapterのListView更新について
とかも試したけど、Cursor#requery()ってのがUIスレッドでの更新処理なので使うなって怒られたので、これもちがうなーと。

で、さらに調べてたら次の記事を見つけた。

CursorAdapterとContentProviderの関係

どうやら正しいContentProviderを作ってないとSimpleCursorAdapterが変更を検知できないらしい。

で、自分の作ったContentProvider見てみると、ContentProvider#query()で次のコードが抜けてた。
cursor.setNotificationUri(getContext().getContentResolver(), uri);
上のコードをqueryBuilderからcursorを取得した後に書いておかないとダメだった。

作るときに参考にしたサンプルにはちゃんと書かれていたので普通に自分のミスでした。

で、修正すると、即座に更新されるようになった。

他にもありそうなのはContentProvider#insert()とかで
getContext().getContentResolver().notifyChange(uri, null);
とかかな。

SimpleCursorAdapterを使っているListViewのアイテム要素にListenerをつける

AndroidでSimpleCursorAdapterを使っているListViewでレイアウトの中にListenerをつけたので、その備忘録として。

やりたかったのは、SimpleCursorAdapterを使っているListViewのItem要素の中に複数のclickできるViewがあり、それぞれのclickで異なる動作をすること。

ポイントは

  • ListViewの拡張
  • SimpleCursorAdapterの拡張

サンプルとして使うItem要素のxmlはこんな感じ
(今回はTextViewにlistenerをつける。
@stringと@dimensは別に定義してるもの)

list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="8dp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingBottom="8dp"
    android:orientation="vertical" >
    
    <TextView
        android:id="@+id/name_textview"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="@dimen/text_size_large" />
    
    <TextView
        android:id="@+id/point_textview"
        android:layout_below="@id/name_textview"
        android:layout_alignParentLeft="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="@dimen/text_size_small" />
    
    <TextView
        android:id="@+id/delete_textview"
        android:layout_below="@id/name_textview"
        android:layout_alignParentRight="true"
        android:layout_width="48dp"
        android:layout_height="24dp"
        android:gravity="center"
        android:text="@string/textview_label_delete"
        android:textSize="@dimen/text_size_small" />
    
    <View
        android:id="@+id/separator_view_to_left_of_delete"
        android:layout_below="@id/name_textview"
        android:layout_toLeftOf="@id/delete_textview"
        android:layout_width="2dp"
        android:layout_height="24dp"
        android:background="@android:color/darker_gray" />
    
    <TextView
        android:id="@+id/edit_textview"
        android:layout_below="@id/name_textview"
        android:layout_toLeftOf="@id/separator_view_to_left_of_delete"
        android:layout_width="48dp"
        android:layout_height="24dp"
        android:gravity="center"
        android:text="@string/textview_label_edit"
        android:textSize="@dimen/text_size_small" />
    
    <View
        android:id="@+id/separator_view_to_left_of_edit"
        android:layout_below="@id/name_textview"
        android:layout_toLeftOf="@id/edit_textview"
        android:layout_width="2dp"
        android:layout_height="24dp"
        android:background="@android:color/darker_gray" />
    
    <TextView
        android:id="@+id/record_textview"
        android:layout_below="@id/name_textview"
        android:layout_toLeftOf="@id/separator_view_to_left_of_edit"
        android:layout_width="48dp"
        android:layout_height="24dp"
        android:gravity="center"
        android:text="@string/textview_label_record"
        android:textSize="@dimen/text_size_small" />
    
</RelativeLayout>

次にListViewを拡張したMyListView。
/**
 * リスト内にボタンを配置して、ボタンが押された時にonItemClickを通知するListView
 */
public class MyListView extends ListView implements OnClickListener {
    
    /**
     * コンストラクタ
     */
    public MyListView(Context context) {
        super(context);
    }
    
    /**
     * コンストラクタ
     */
    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onClick(View v) {
        int pos = (Integer)v.getTag();
        this.performItemClick(v, pos, v.getId());
    }
}

次にSimpleCursorAdapterを拡張したMyCursorAdapter
private class MyCursorAdapter extends SimpleCursorAdapter {

        public MyCursorAdapter(Context context, int layout, Cursor c,
                String[] from, int[] to, int flags) {
            super(context, layout, c, from, to, flags);
        }
        
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // viewのセットなどはスーパークラスのメソッドに任せる
            View view = super.getView(position, convertView, parent);
            
            /*
             * それぞれのTextViewにpositionTagと
             * MyListViewのlistenerをつける
             */
            TextView deleteTextView = (TextView)view.findViewById(
                    R.id.delete_textview);
            deleteTextView.setTag(position);
            deleteTextView.setOnClickListener((MyListView)parent);
            
            TextView editTextView = (TextView)view.findViewById(
                    R.id.edit_textview);
            editTextView.setTag(position);
            editTextView.setOnClickListener((MyListView)parent);

            TextView recordTextView = (TextView)view.findViewById(
                    R.id.record_textview);
            recordTextView.setTag(position);
            recordTextView.setOnClickListener((MyListView)parent);
            return view;
        }
    }

これで、アイテム要素の中のonClick()でListViewのonItemClick()が呼ばれるようになるので、
onItemClick()の中でidで処理を切り替えればOK。
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {

            switch(view.getId()) {
                case R.id.delete_textview:
                    // 消去の時の処理
                    break;
                case R.id.edit_textview:
                    // 編集の時の処理
                    break;
                case R.id.record_textview:
                    // 記録の時の処理
                    break;
                default:
                    // 通常のonItemClick()の時の処理
                    break;
            }
        }

参考にした記事
ListViewの中のボタンのクリックイベントをActivityに通知する

2013/02/06

Androidのテキストサイズはdimens.xmlに書いておく

アプリ作ってる時のレイアウトで結構次のように書く場合が多い。

android:textSize="18sp"

直に書いてもいいんだけど、Googleさんが公式:Typography
 「テキストの大きさは限定したほうがいいよ。一つのアプリでテキストサイズがバラバラだと見づらいしね。Android Frameworkでは12sp、14sp、18sp、22spの4つに限定してる」
って言ってて、俺もGoogleさんに従うかな〜と最近はその4種類だけ使うようにしてます。

で、いっつもレイアウトファイルいじっているわけじゃないので、たまに数値を忘れるんだけど、こうすれば忘れないなーってのを思いついたのでメモ。

dimens.xml

    12sp
    14sp
    18sp
    22sp
あとは
android:textSize="@dimen/text_size_medium"
って書くだけでいい。

別にこの値じゃなくてもいいんだけど、種類は絞った方がいいのでdimens.xmlに書いて限定しておくといいと思う。

2013/01/27

Androidのアイデアシート作ってみた

Fireworksの使い方勉強してて、ある程度使い方覚えたので、練習兼ねてアイデアシート作ってみた。

iPhoneのアイデアシートは頂いたものはあるんだけど、Androidの無かったし、探してもGalaxyNexusのは無かったっぽいので、GalaxyNexusのアイデアシート。

こんな感じ


欲しい方はPDFどーぞ

2013/01/19

AndroidのLogを使う時のちょっと便利な方法

Androidで開発している時に、よく使うのがandroid.util.Logクラスですよね。

みなさんもよく使っていると思います。
    Log.d(TAG, "hogehoge");
こんな風に使うのが一般的なのですが、これをいろんな場所に組み込んでログを出力してると、後でめんどうなことになります。

リリースビルドした時も、このログ出力は消えないので、リリース前にはLogの部分を削除したりコメントアウトしたりといった事が必要になってきます。

Logを組み込んでる場所が5箇所とか10箇所ぐらいなら問題ありませんが、100箇所を超えるようになると、消す作業そのものが大変ですし、消し忘れなども出てきます。

そして、この問題を解決できる便利な方法があります。

ADT17から/genに生成されるようになったBuildConfig.javaを利用します。
public final class BuildConfig {
    public final static boolean DEBUG = true;
}
このDEBUGはデバッグビルドの時はtrueのままですが、リリースビルドの時falseになります。

なのでログを出力する際に以下のようにすると、開発してる時はログが出力され、リリースするときはログが出力されないようになります。
if (BuildConfig.DEBUG) {
    Log.d(TAG, "hogehoge");
}

さらに、いちいちログ出力するのに3行も書くのはちょっとめんどうなので、Loggerクラスを作りました。
public class Logger {
    
    private static final String TAG = "MyApplication";
    
    public static final void d(String msg) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, msg);
        }
    }
    
    public static final void d(String tag, String msg) {
        if (BuildConfig.DEBUG) {
            Log.d(tag, msg);
        }
    }
    
    public static final void e(String msg) {
        if (BuildConfig.DEBUG) {
            Log.e(TAG, msg);
        }
    }
    
    public static final void e(String tag, String msg) {
        if (BuildConfig.DEBUG) {
            Log.e(tag, msg);
        }
    }
    
    public static final void i(String msg) {
        if (BuildConfig.DEBUG) {
            Log.i(TAG, msg);
        }
    }
    
    public static final void i(String tag, String msg) {
        if (BuildConfig.DEBUG) {
            Log.i(tag, msg);
        }
    }
    
    public static final void v(String msg) {
        if (BuildConfig.DEBUG) {
            Log.v(TAG, msg);
        }
    }
    
    public static final void v(String tag, String msg) {
        if (BuildConfig.DEBUG) {
            Log.v(tag, msg);
        }
    }
    
    public static final void w(String msg) {
        if (BuildConfig.DEBUG) {
            Log.w(TAG, msg);
        }
    }
    
    public static final void w(String tag, String msg) {
        if (BuildConfig.DEBUG) {
            Log.w(tag, msg);
        }
    }
    
    public static final void heap(){
        heap(TAG);
    }
    
    public static final void heap(String tag) {
        if (BuildConfig.DEBUG){
            String msg = "heap : Free=" + Long.toString(Debug.getNativeHeapFreeSize() / 1024) + "kb" + 
                    ", Allocated=" + Long.toString(Debug.getNativeHeapAllocatedSize() / 1024) + "kb" + 
                    ", Size=" + Long.toString(Debug.getNativeHeapSize() / 1024) + "kb";
            
            Log.v(tag, msg);
        }
    }
}
このクラスを適当な場所に置いて、ログ出力したい箇所で
Logger.d("hogehoge");
// もしくは
Logger.d(TAG, "hogehoge");
とすると、いつもと同じようにログ出力ができます。もちろんリリースビルド時にはログが出力されません。
あと、heap()メソッドはヒープメモリの情報をログに出力するので、メモリ関連の調査をしてる時に役に立つかなとおもいます。