はうすてんぼぶ

コードかいてて疑問に思ったことや、興味あることをつらつらと暇なときに書く場所、ここはそんな場所

保存先フォルダの任意設定について

MayViewerで画像の保存先を今までは2つぐらいの選択肢しか無かったんだけど、
ちょっと保存先を自由に選びたい、って要望をそろそろ1年ぐらい寝かしてたので実装しよう、と思ったのでトライしてみますた

とりあえずファイルを除くフォルダ一覧取得→一個下の階層へ→一覧取得→(ループ)
みたいな処理をして、Android上でこれやるならDialogに表示すればいいのかなーと考えて、Google先生に聞いてみたところ

以下のサイト様が引っかかったので、参照してかりかり書きました
Androidでファイル選択ダイアログを使う (理ろぐ)
http://relog.xii.jp/archives/2010/07/android.html

■20110616 androidフォルダ一覧からフォルダ選択(ダイアログ) - developer_of_Tyam
https://sites.google.com/site/developeroftyam/yoshiya/20110616-androidforuda-yi-lankara-xuan-ze-daiarogu

×かりかり書いた
ほとんどコピペ

上記のサイト様に書いてある方法でも必要十分なんですが、
「フォルダを一つ階層上に戻る」、
ってのと
「[OK]ボタンじゃなくて選択肢の一つに「このフォルダにする」」
って機能を入れたかったのでそこだけ変えました

こんな感じに動くので、とりあえずスクショとソースだけ貼ってみます
今回はPreferenceActivityから飛んでくるようにしてるため、
保存先フォルダが変更されたときに、summaryを変えたかったのでListenerでごりごり変えてます

スクショはこんな感じ
f:id:Silent-Bob:20110706023837p:image
f:id:Silent-Bob:20110706023838p:image
f:id:Silent-Bob:20110706023839p:image
f:id:Silent-Bob:20110706023840p:image
f:id:Silent-Bob:20110706023841p:image

1.PreferenceActivityから設定可能なようにしてる
2.とりあえず元あるやつと自由選択できる項目をDialogを表示
3.「それ以外」を押すと任意設定用のDialogを表示 ROOTはSDカード直下
4.DCIMを押したとこのスクショ ここで「このフォルダにする」を押すと
5.prefにフォルダパスを入れておいて、こんな感じにsummaryは変わる

ぐちゃぐちゃなソースはこんな感じ

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnClickListener;
import android.content.SharedPreferences.Editor;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.widget.Toast;
public class SettingOfFolderForSavingImageUtil {
    public static final String PREF_NAME = "FOLDER_PATH_FOR_SAVING_IMAGE";
    private static final String ROOT_PATH = Environment.getExternalStorageDirectory().getPath()+"/";
    public static final String DEFAULT_FOLDER_PATH = ROOT_PATH+"MayVieweLite/SaveImg/";
    private static ChangeSummaryListener csListener;

    public static void openDialog(final Context context,ChangeSummaryListener listener){
        csListener = listener;

        //まず3or4択のダイアログを表示
        AlertDialog.Builder ad = new AlertDialog.Builder(context);
        ad.setTitle("保存先のフォルダを選択");
        String items = {"/MayViewerLite/SaveImg/","/download/","これ以外"};
        ad.setItems(items, new OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                switch(which){
                case 0:
                    setFolderPathToPref(context,ROOT_PATH+"MayViewerLite/SaveImg/");
                    break;
                case 1:
                    setFolderPathToPref(context,ROOT_PATH+"download/");
                    break;
                case 2:
                    openFreeSelectDialog(context,ROOT_PATH);
                    break;
                }
            }
        });

        ad.create().show();
    }

    private static void setFolderPathToPref(Context context,String path){
        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);

        Editor e = pref.edit();
        e.putString(PREF_NAME,path);
        e.commit();

        if(csListener != null){
            csListener.changeSummaryOfPreferenceScreen("setting_of_folder_for_saving_image","現在の保存先:"+path);
        }
    }

    private static void openFreeSelectDialog(final Context context,final String parentPath){
        AlertDialog.Builder ad = new AlertDialog.Builder(context);

        ad.setTitle(parentPath);
        try{
            File files = new File(parentPath).listFiles();
           
            ArrayList<String> pathList = new ArrayList<String>();

            for (File file : files) {
                if(file.isDirectory()){
                    //ディレクトリの場合
                    pathList.add(file.getName() + "/");
                }
            }

            Collections.sort(pathList);//ソート
            pathList.add(0,"このフォルダを保存先にする");
            pathList.add(1,"←一つ前に戻る");

            //配列に変換
            final String items = (String)pathList.toArray(new String[0]);
            ad.setItems(items,
                    new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    switch(which){
                    case 0:
                        setFolderPathToPref(context,parentPath);
                        break;
                    case 1:
                        if(parentPath.equals(ROOT_PATH)){
                            Toast.makeText(context,"これ以上戻れません",Toast.LENGTH_SHORT).show();
                            openFreeSelectDialog(context,parentPath);
                        }else{
                            //parentの一つ前のパスを取得
                            String grandparentPath = parentPath.substring(0,parentPath.length()-1);
                            grandparentPath = grandparentPath.substring(0,grandparentPath.lastIndexOf("/")+1);
                            openFreeSelectDialog(context,grandparentPath);
                        }
                        break;
                    default:
                        openFreeSelectDialog(context,parentPath+""+items[which]);
                        break;
                    }
                }
            });
        }catch(Exception e){
            e.printStackTrace();
        }
        ad.create().show();
    }

    //summaryを更新する
    public interface ChangeSummaryListener{
        public void changeSummaryOfPreferenceScreen(String prefKey,String folderPath);
    }

}


PreferenceActivityはこんな感じにしてsummaryを動的に変えてます

public class MyPreferenceActivity extends PreferenceActivity implements SettingOfFolderForSavingImageUtil.ChangeSummaryListener{

//〜〜〜〜省略〜〜〜〜

        PreferenceScreen settingOfFolderForSavingImagePs = (PreferenceScreen)findPreference("setting_of_folder_for_saving_image");
        settingOfFolderForSavingImagePs.setOnPreferenceClickListener(
                new OnPreferenceClickListener(){
                    @Override
                    public boolean onPreferenceClick(Preference preference) {
                        SettingOfFolderForSavingImageUtil.openDialog(MyPreferenceActivity.this,MyPreferenceActivity.this);
                        return true;
                    }     
                }
            );

        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
        settingOfFolderForSavingImagePs.setSummary("現在の保存先:"+pref.getString(SettingOfFolderForSavingImageUtil.PREF_NAME,SettingOfFolderForSavingImageUtil.DEFAULT_FOLDER_PATH));

//〜〜〜〜省略〜〜〜〜

@Override
    public void changeSummaryOfPreferenceScreen(String prefKey, String folderPath) {
        PreferenceScreen prefScreen = (PreferenceScreen)findPreference(prefKey);
        prefScreen.setSummary(folderPath);
    }
}

要所要所を変えれば動くとは思うので、良かったら使ってみてください

しかしあれだね
2年くらい前から全然ソース書く力が上がってないよね(´・ω:;.:...

GoogleカレンダーのMyカレンダー一覧を取得…できますん

湿気と気温で熱くてとろける

ComicAlarmで書籍の発売日をGoogleカレンダーに登録する際に
「登録先のMyカレンダーを設定」できるようにしたのだけれども
どうもHTC製のもの(特にDesire)だとMyカレンダーの一覧を取得できないらしくてうぐぐ…
というかイベントの登録もできない、らしいので∩(・x・)∩オテアゲ!

自分のGalaxySだと普通に取得できるのでさらにうぐぐ
なんなんだろUriが行けないのか属性名がいけないのか…な?

と思って

ちょっと関連のある記事を見つけ、その著者のrmiyaさんに質問させていただいたのですが、
Uriはそのままでも大丈夫とのことでした
http://d.hatena.ne.jp/rmiya/20100725#1280028729

少し話は変わりますが、
rmiyaさんの記事や外国サイトをぐるぐる回ってると
カレンダーのUri持ってくるときの処理はだいたい以下の2つかなぁという結論に至りました

android.os.Build.VERSION.SDK_INTを使わない方法
http://stackoverflow.com/questions/3571818/calendar-contentprovider-url-on-android-phones-with-sense-ui

private String getCalendarUriBase() {

String calendarUriBase = null;
Uri calendars = Uri.parse("content://calendar/calendars");
Cursor managedCursor = null;
try {
managedCursor = managedQuery(calendars, null, null, null, null);
} catch (Exception e) {
}
if (managedCursor != null) {
calendarUriBase = "content://calendar/";
} else {
calendars = Uri.parse("content://com.android.calendar/calendars");
try {
managedCursor = managedQuery(calendars, null, null, null, null);
} catch (Exception e) {
}
if (managedCursor != null) {
calendarUriBase = "content://com.android.calendar/";
}
}


android.os.Build.VERSION.SDK_INTを使う方法は
rmiyaさんの記事
http://d.hatena.ne.jp/rmiya/20100725#1280028729
をご参照ください

最終的に上の二つの方法を併せて

if (android.os.Build.VERSION.SDK_INT <= 7 )
{
//the old way
calUri = Uri.parse("content://calendar/calendars");
eventUri = Uri.parse("content://calendar/events");
}
else
{
//the new way
String uriBase = getCalendarUriBase();
calUri = Uri.parse(uriBase+"calendars");
eventUri = Uri.parse(uriBase+"events");
}

な感じにしてます
たぶん片方の方法だけで十分なのですが、
HTC製の端末のときでも何とかしてくれるかもしれん、
という根拠の無い期待からわざわざOSのAPIレベルで区別した後に、再度チェックしてます

でまぁ、Uriが悪くないとなると属性名がいけないのか?
と思って程度の低い英語読解力で色々サイトを見てみたけどクリティカルな記事は見つからなかった(´・ω・`)

今は下のような感じで一覧を持ってこようとしています

String projection = new String { "_id", "name" };//idはプライマリーキー、nameはカレンダー名
String selection = "access_level" + "=?";
String selectionArgs = new String { "700" };
Cursor managedCursor = managedQuery(calUri, projection, selection,
selectionArgs, null);
if(managedCursor.moveToFirst()){
int idColumnIndex = managedCursor.getColumnIndex("_id");
int nameColumnIndex = managedCursor.getColumnIndex("name");
Log.d(LOG_TITLE,"calendarNum -> "+managedCursor.getColumnCount());
Log.d(LOG_TITLE,"id column index: "+idColumnIndex+" / name column index:"+nameColumnIndex);
do{
Log.d(LOG_TITLE,"calender info ["+managedCursor.getInt(idColumnIndex)+","+managedCursor.getString(nameColumnIndex)+"]");
}while(managedCursor.moveToNext());
}else{
Log.d(LOG_TITLE,"calendarNum -> 0");
}

この方法だとどうもHTC製の一部端末では何も取得できないとのことorz

ううn、どっかに落ちてないかなDesire
明日学校でDesire持ってる友人にちょっとデバッグさせて、ってお願いしてみるかなー

メモメモ

カリカリAndroidのコードを書いている間だけ研究しなきゃいけない不安を忘れられるよ

やったね!教授!研究がどんどん滞るよ!


とりあえず問題が発生したときに色々調べてお世話になったサイトさんが沢山あるので、メモ替わりにここに記しておきます


WebのGoogleCalendarと同期できない問題 - Gen-Sukeの日記
http://d.hatena.ne.jp/gen-suke/20101222/p1


Android: 画面に合わせて画像を縮小して読み込む | 自転車で通勤しましょ♪ブログ
http://319ring.net/blog/archives/1276


Matsuhiro Log: WebViewで画像を表示
http://matsuhilog.blogspot.com/2011/01/webview.html


Re: [android-group-japan: 10040] アプリから撮 影した写真画像が、ギャラリーから見れない

http://www.mailinglistarchive.com/html/android-group-japan@googlegroups.com/2011-03/msg00278.html


ListViewに入れるViewのbackgroundに色を直接指定しちゃだめ - gae+eyoの日記
http://d.hatena.ne.jp/gae+eyo/20101023/1287793566


AndroidでWebのログイン処理とか - しっくはっく
http://d.hatena.ne.jp/sick_hack/20101015/1287106845


こんなとこ。
基本的にWebに資料が転がっていないと何もプログラム書けない自分が嫌だよまったく。


そういえば漫画の発売日を知らせてくれるアプリケーション『ComicAlarm』を作ってマーケットに公開しました。
https://market.android.com/details?id=jp.bobmk2.comicalarm


あと何故か2chのまとめサイトに取り上げられてました
2chニュー速クオリティ:【神アプリまとめ】Androidのスマホ買ったけど、とりあえずどのアプリ入れべき?
http://news4vip.livedoor.biz/archives/51774499.html#more


急にがつんと利用者増えたから何事かと思ったけど
良かった良かった

嬉しいね、周りの神アプリのラインナップ見ると凹むけど(´・ω:;.:...

忙しい

んだけどそういうときに限って他のことやりたくなるのは,
あれだよね


掃除してると昔の漫画読んじゃって捗らないのに通じるよね


今回はメモ替わりに投稿


昨日Androidのアプリ開発してて,見慣れないエラーが急にでたので,その解決策的なことが書いてあるサイトさんを紹介


エラー内容は,
Installation error: INSTALL_PARSE_FAILED_NO_CERTIFICATES
おい,これ認証されてねぇアプリだぞおいって感じのエラーだと思う


アプリ開発しててこんなん出たの初めてで,しかもなんの前触れもなく出たから焦った


そもそも実機デバッグしてるときは認証もクソもない(マーケットにあげるときは必須,野良アプリとしても配布するときもたぶん必要)のでハァ?となるんだけど


原因は下のサイト様におおよそ書いてあります

<困ったこと解決メモ>INSTALL_PARSE_FAILED_NO_CERTIFICATES|じゅんすけのブログ
http://ameblo.jp/junsuke149/entry-10610746932.html


ザッと言うと,ようは「既に他の認証され済みのアプリのファイル(pngとかxmlとかたぶん全部)をコピーして認証されてないアプリのdrawableやlayoutフォルダにぶち込むな」ってこと,だと思う


「確かに,このエラーがでる前に,既に認証され済みのアプリからいくつか素材の画像ファイルをコピーしてきたが…」と思い出して,そいつら削除して,新しく保存した画像ファイルをぶち込んだらエラーがでなくなりました


良かった良かった,死ぬかと思ったよ


たぶん中々起きないことだと思うけどみなさんも気をつけて下さい

海外版対応のアプリを出してみて

結局秋なんて無かったんじゃよ
即冬じゃないですかーやだー!


で,MTGPhotoっていう
ギャザ(Magic the Gathering)っぽいカードを作ってくれるアプリを作って世に出してみました
http://jp.androlib.com/android.application.jp-bobmk2-mtgphoto-qwAqx.aspx


パッと見はただのアプリで実際そこらへんにあるアプリなんですが
今回は日本用と海外用の2種類の言語に対応して世に出してます


例えば,「カードテキスト」って文字列も
日本語版だとそのまま「カードテキスト」で
海外版だとちゃんと「CardText」ってなってくれるようにしてます


Androidアプリは,このためにわざわざ二つアプリ作る必要はなくて
文字列とか記述するstrings.xmlってファイルを言語ごとのフォルダに入れると
勝手にやってくれます
参考サイト:https://groups.google.com/group/android-group-japan/browse_thread/thread/7738df948e3a4bf5?hl=ja


便利な世の中になったもんだね


で,当然だけどそれぞれ日本語と英語で文字列出力するところは設定しなきゃいかんので作るときは面倒なことこの上ないのです
レイアウト考える時も文字列の横幅が言語で変わるのを想定して作るべきだなって思った
(まぁ今はISシリーズとか独自の綺麗なフォント使ってて横幅(layout_width)とかを指定すると思いっきりレイアウトが崩れる場合があるので,基本的にsinglelineはtrueにしとくと安心だね)


で,世に出してみて思ったことがあるのでとりあえず今日はそれについてさらっと言うよ


海外版もいっしょにだすとフィードバックが凄く貰えます


MayViewerは完全に国内仕様だから日本人しか基本的にダウンロードしないけど,MTGPhotoは海外の人もダウンロードすることがある
てか遊んでる人の数の比率考えると海外版をダウンロードする人のほうが多いはず


んで,今400ぐらいのダウンロードがあったんだけど
なんかバグ報告とかレイアウトの提案とかのメールが結構来るなーって印象
MayViewerは1通しかきたことないけどMTGPhotoは5通来てる


ここらへんはお国柄とかあるのかもしれないけど
作る側からするとバグ報告とかは実際にメールしてくれると直しやすくてとっても助かる


というわけでたぶん皆英語版出すこと前提でアプリとか作ってるんだろうけど
なるべく海外の人にも使ってくれるように英語版を作ると色々と良いことがあるかもね

AdMobとAdMakerについて

九月だってのに暑いね
秋の神様は休暇中ですか?
ああ,人間に秋モノなんて買わせないために節約させてくれてるのですね


ところで作ってたやつをAndroid Marketにやっとこさ登録しました
http://jp.androlib.com/android.application.jp-bobmk2-mayviewerlite-free-jEEnt.aspx

なんかアンドロイダーにも取り上げられててちょっとビックリ
http://androider.jp/?p=20838

え?なに?エラーばっかでる?ごめんよくわかんない
もっさりは宿命というか,もう正直これ以上もっさり感直すのは辛い
でも精進するよ


んで,今日はAdMobとAdMakerについて軽く比較しとく
本当に軽くね

ああ,なんか実装方法とかは他の解説サイトをご参照ください
僕もそれ見てやったので


んで,AdMobとAdMakerについてだけど今回は
広告掲載率ってのと登録期間ってのに注目して話を進めようと思う


AdMobはとにかく登録するとすぐ使えるってのが便利
AdMakerはだいたい10日ぐらいかかるのかな
9/10に登録して今日(9/21)に使えるようになった


まぁAdMaker使う場合は前もって登録しておくと便利かもね


んで,問題は広告掲載率
AdMobはけっこう「広告ください!」って言っても掲載されないケースが起こる
AdMakerは今のところ100%くらいもらえてるんじゃないかなぁ,よくわかんないけど


で,この広告掲載率ってのはAdMobだとだいたい巷では60%ぐらい叩きだせるらしいんだけど
僕の場合だいたい25%ぐらいしか出ません
原因も特に調べてませんが,勝手に推測すると
3G回線使って画像をゴリゴリ貰ってきているうちにタイムアウトしてんじゃねぇの?って感じ


いやまぁ全く未調査なんでそんなことないかもしれないけど
でも回線使ってゴリゴリアクセスするようなアプリはちょっと注意が必要かもね


まとめ
AdMobは対応が早いし,いろいろ詳細なデータ(掲載率や依頼数,クリック率など)も見れるのでいいかもね
AdMakerは掲載率の安定感が魅力的,ワンクリック当たりの単価も確かこっちのが高いのかな?
でもまぁ広告内容はAdMobのが魅力的なのが多いから結局どっこいどっこいなんじゃないかなぁ


ちなみに僕は後者を選びました,どっちでもいいんだけどね
25%だとちょっと相性悪いかなって感じでね


そうそう後AdMakerはちゃんと電話してきてくれるから何か身近に感じたりする
日本の会社だったんだね知らなかった
わざわざ電話してきてくれるのはとても丁寧だと思った


雇ってくれないかな,僕就職活動したくないんだけど…

Dialogについて

しばらく考えてたけど分からなかったDialogについて書く
てか,誰か書いてたら書かないんだけど書いて無さそうだからメモ替わりに書いておく


いや,分からなかったってのはダイアログのタイトルの消し方なんだけどね
意外と検索に引っかからない,またはあっても「違う!」ってのが多くて困った


タイトルを消すためには,AlertDialog.Builderを使って,Layoutをぶち込めばおk(下みたいに)

AlertDialog.Builder dia = new AlertDialog.Builder(this);
LayoutInflater inflater = LayoutInflater.from(this);
final View actionAlertView = inflater.inflate(R.layout.dialog_layout, null);
dia.setView(actionAlertView);

AlertDialog aDia = dia.create();
aDia.show();

ってのは少しgoogleで調べれば分かるんですが,これ実は欠点がある
ってか調べててその解決策が見つからなくてすげー困った


この方法,一見するとタイトルがないように見えるんだけど,dialog_layout.xmlのサイズが画面いっぱいになるようにレイアウトを組むと何か変な隙間が目立つ
下の画像はdialog_layout.xmlのheightを1000dp(画面サイズを大きく上回る値)に設定して実行した場合の画面


f:id:Silent-Bob:20100824221930p:image


で,setTitleして,タイトルを設定して実行した場合の画面が下の画像のようになる


f:id:Silent-Bob:20100824221931p:image


つまり,別にタイトルが見えなくなっているだけで,決してそのスペースが消滅しているわけではないってのがスクショから分かると思うの

普通にDialogにsetTitleしなくても同じ現象みたいになるけど,あの無駄なスペースがただ透過されているだけみたいな現象になってるんだよね


ちょっと使ったことある人なら分かると思うけど,タイトルを設定せずにAlertDialogを使うとやや画面中央下に配置される経験があるはず

その原因はタイトルのこの空白スペースもレイアウトの高さに加わっているためだと,勝手に推測しとく(原因よくわかんないし)


で,結局下のようにすると治ります.
ようはタイトル部分にレイアウトを置いちまえば良いんじゃないの!?って感じ
これで良いのかどうかは知らんけど,まぁ全画面に表示されてるからおっけー

AlertDialog.Builder dia = new AlertDialog.Builder(this);
LayoutInflater inflater = LayoutInflater.from(this);
final View actionAlertView = inflater.inflate(R.layout.dialog_layout, null);
//dia.setView(actionAlertView);
dia.setCustomTitle(actionAlertView);

AlertDialog aDia = dia.create();
aDia.show();

上のように変えて実行した結果が下の画像みたいになる
ちゃんと全画面に表示されてて良いね!


f:id:Silent-Bob:20100824221932p:image


結構このテクニックは他のアプリで見かけたけど全然方法がわからなくてグギギってなってたけどやっと解決した
よかったよかった


んじゃおうちに帰ろう



11月8日追記
このテクニックは便利ですが,セットするViewの中にEditTextとかあるとそこ押してもキーボードがでてこない感じがするので,そういったときは普通にsetViewしないといけないかもしれません
なのでもし強制的にキーボードを表示する方法があったら教えて下さいorz


あと誰かDialogにWebView入れた時にキーボードでない問題解決した人いたら教えて下さいor2

あつい

ぼちぼち作ってます


今日はListViewとGridViewとかについて触れようかな
たぶんこいつらは触ってる感じだと


画面の向き(縦とか横とか)が変わった時に中身が初期状態になります


え?当然じゃん?って思うかもしれませんが
TextViewなんかはやってみると分かりますが,中身は入りっぱなしになっています


なにそれズルい!ズルい!って思うけど


画面の向きが変わったときには(たぶん)
onSaveInstanceState→onDestroyと処理が流れるので
onSaveInstanceStateのとこでListViewとかの適当な情報を記憶しておきましょう


そんで,違う画面の向きで新しくActivityが起動したときに
onCreate→onRestoreInstanceStateとなるので
記憶しておいたものをonRestoreInstanceStateでうまく拾って,ListViewの中身を入れましょう


めんどいよね


あれ?でもこれListViewやGridViewのどっかいじれば解決すんのかな

8/20追記
って思ってちょっと調べたらあった
http://d.hatena.ne.jp/hyoromo/20090712/1247385249


AndroidManifest.xmlのActivityのところに

android:configChanges="orientation|keyboardHidden"

って書き加えるだけでいいみたい


なんだよめちゃめちゃ簡単じゃないですかー!やだー!