2013年7月17日水曜日

実行時にSeekBarの色を変える

実行時にSeekBarの色を変える

XMLにおいて、色を変える方法については、次のサイトで紹介されています。
AndroidのProgressBar / SeekBarに画像を使う
Cool android SeekBar, custom made by me

これらを参考にさせて頂いて、下記のとおり、実行時に色を変えるプログラムを作ってみました。

        sb = (SeekBar)llMain.findViewById(R.id.SeekBar_Volume);
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB){
        /*
        * LightではSeekBarが見えにくいので、色を着ける。
        * HONEYCOMB未満でこれを実行すると、即時の描画が行われない。
        * ClipDrawableは、描き変えや、読み出しを行えないため、新規に作成する。
        */
       LayerDrawable ld = (LayerDrawable)sb.getProgressDrawable();
       ColorDrawable dBlue = new ColorDrawable(0xff4f4fdf);
       ClipDrawable cd = new ClipDrawable(dBlue, 0x03, ClipDrawable.HORIZONTAL);
    ld.setDrawableByLayerId(android.R.id.progress, cd);
        }

Android3.0以上から、ホロというテーマが使われています。私はテーマをLightに変えました。すると、何故か、SeekBarに色が付かなくなりました。そこで、色を着けてやろうということです。

本来なら、テーマにおいて上記と同等の修正をすべきでしょう。テーマの技法を使って色を変える方法が分かる人は、各自のブログにでも載せておきましょう。

2013年7月5日金曜日

onNavigationItemSelected()

ActionBar.OnNavigationListener#onNavigationItemSelected()

第ゼロ番目のアイテムが(利用者の操作によって選択されたのではなく)自動的に選択され、このメソッドが呼ばれる。
ActionBar#setSelectedNavigationItem()の引数にマイナス1を代入しましたが、第ゼロ番目が選択されてしまいます。引数に、任意の項目のIDを代入すると、その代入した値の項目が選択された状態になります。
この不都合を無くすため、第ゼロ番目の項目をダミーにしました。
これでいいのか?(ここで笑う)

Spinnerを使う場合、初期化(初回)の時点で、アイテムが自動的に選択されてしまうという問題があります。
この問題の解決は次のサイトに書かれています。
Android Spinner : Avoid onItemSelected calls during initialization
spinner1に連動して変化するspinner2の初期値設定

boolean型変数を大域変数として設けておき、onItemSelected()において、初期化(初回)時点とそうでない時点を切り分けるという方法ですね。
他に方法は無いものかと考えましたが、分かりませんでした。

ちなみに、onItemSelected()は、選択が変更された場合に呼ばれます。逆に言えば、既に選択済みの項目をクリックしても呼ばれません。
つまり、上記方策を講じて、初回における自動選択問題を回避したとして、利用者の要求が自動選択済みの項目を選択したいということである場合には、その項目をクリックしても、onItemSelected()は呼ばれないということです。一旦別の項目を選択してから、要求する項目を再度クリックし直さねばならないのです。
こんなアホなアプリを作成することはできません。

この不都合を無くすため、第ゼロ番目の項目をダミーにします。
これでいいのか?(ここで再度笑う)

私は(不幸なことに)ArrayAdapter.NO_SELECTIONというのを見つけてしまいました。で、(何かを示唆するかのように)Adapterには、何の解説もありません。(無駄な)期待をして、これをActionBar#setSelectedNavigationItem()の引数に与えて実行してみました。結果、(予期したとおり)第ゼロ番目の項目が選択されたという趣旨でonNavigationItemSelected()が呼ばれました。
こんなんでいいのか?(ここで、もう一度笑う)

第ゼロ番目の項目をダミーにした場合、その必要ではない項目もドロップダウンリストに表示されてしまって不細工である。このため、ダミーをリストに表示しないようにしたいと思い、次のとおりアダプターをカスタマイズしてみました。

    public class Adapter_SR extends ArrayAdapter<String>{
        public Adapter_SR(Context context, int textViewResourceId, List<String> objects) {
            super(context, textViewResourceId, objects);
        }

        @Override
        public View getView(int iID, View v, ViewGroup vg) {
            if(iID==0){
                LayoutInflater inf = getLayoutInflater();
                TextView tv = (TextView)v;
                tv = (TextView)inf.inflate(R.layout.bar_sr, vg, false);
                tv.setText((String)getItem(iID));
                return tv;
            }
            else return super.getView(iID, v, vg);
        }

        @Override
        public View getDropDownView(int iID, View v, ViewGroup vg) {
            LayoutInflater inf = getLayoutInflater();
            TextView tv = (TextView)v;
            if(tv==null){
                if(iID==0){
                    tv = (TextView)inf.inflate(R.layout.bar_sr, vg, false);
                    tv.setTag("Zero");
                }
                else{
                    tv = (TextView)inf.inflate(android.R.layout.simple_spinner_dropdown_item, vg, false);
                    tv.setText((String)getItem(iID));
                }
            }
            else{
                if(iID==0){
                    tv = (TextView)inf.inflate(R.layout.bar_sr, vg, false);
                    tv.setTag("Zero");
                }
                else{
                    String s = (String)tv.getTag();
                    if(s!=null && s.equals("Zero")==true){
                        tv = (TextView)inf.inflate(android.R.layout.simple_spinner_dropdown_item, vg, false);
                        tv.setText((String)getItem(iID));
                    }
                }
            }
            return tv;
        }
    }


2013年7月1日月曜日

ClassNotFoundException

RuntimeException: Unable to instantiate activity ComponentInfo  java.lang.ClassNotFoundException in loader dalvik.system.PathClassLoader

ActionBar.OnNavigationListener(Added in API level 11, HONEYCOMB, Android 3.0)の実装について、これはinterfaceであるからして、Activityにinterfaceとしてimplementsしていました。
Android4.2で稼働させるには問題はありませんでした。
ところが、このアプリをAndroid2.3で実行させると、onCreate()が実行されることなく、上記エラーが発生します。

この実装をanonymousにしたところ、Android2.3でも稼働することができました。
こんなふうなanonymousの使い方ってあるのですね。

(参考)
Action Bar

2013年6月26日水曜日

Buttonの背景色

Buttonの背景色

Buttonの色が背景色と混合されてしまって、不細工な色になってしまっている。

Android2.3の場合は、背景の色とButtonの画像とは別々であった。
しかし、少なくともAndroid4.2では、背景色とButtonの画像が混じり合って表示されるため、不細工になっている(主観の問題ですが)。背景色が真っ白の場合は問題は無いです。
この現象が発生するViewの構成
<LinearLayout>...上位にあるView。これに赤い色等の背景色を付ける
    <Button>...画像関係は何も操作しない。
背景の赤い色とButtonの灰色とが混じり合って、赤灰色のButtonが出来上がります。

解決策の模索
1.背景色を表示するViewとButtonとの間に、背景色が真っ白なViewを配置する。
<LinearLayout>...これに赤い色等の背景色を付ける
    <LinearLayout>...これに真っ白な背景色を付ける。
        <Button>...画像関係は何も操作しない。
しかし、LinearLayoutとButtonとは形状が異なるため、白色がハミ出てしまいます。ある種の御仁はハミ出た白色に魅力を感じるでしょう。

2.ThemeをLightにする。
res/values-v11やres/values-v14には、styles.xmlファイルがあるので、この中を次のようにする。
    <style name="AppBaseTheme" parent="android:Theme.Light">
    </style>
このようにすることで、Android2.3の場合と同じ表示になります。
Holoというのが新しいデザインです。Lightは昔のです。何か時代の波に残された感じがしますが、昔の方が良かったなとも感じます。

3.ボタンをShapeDrawableで描画する
これはまだ実施していないので、問題解決になるかどうかは知りません。

参考にしたサイトを掲示しておきます。

余談
画像処理においてsetBackgroundという名前が用いられているが、この言葉使いは誤解を生じる。私はズ~っとforegroundという画像処理の何かが存在するものだと思い込んでいたのだ(アホ)。なので、背景の画像と混じり合わないようにするため、setBackgroundColor()の引数にアルファ値を0xffにした値を代入したりしていた。そんなことをした結果、Buttonが真っ白や真っ黒になってしまい、ボタンの画像が消失した意味が分からなかった。ボタンの画像は、foregroundに描かれているに違いないという思い込みがあったのだ。(そんな思い込みをするのはアンタだけや!)
テキストはsetText()で処理をするのであるから、テキストがforegroundである訳はない。もし、テキストがforegroundであるならば、setTextという名前を用いずに、setForeground(String text)という名前にすべきだ。
反意語に相当するものが存在しないのに、一方の反意語だけを使って表現するのはやめて欲しい。
正確に表現しようとするならば、setGraphicなのではないかと思う。

余談に対するツッコミ
Buttonの内部にはViewとTextViewがあって、backgroundはViewに存在します。TextViewにはdrawable top, drawable left等が存在します。
たぶん、Viewが基底部分にあって、その画像であるため、backgroundという用語を使用しているのだろうと思います。

ちなみに、SeekBarは、ViewとProgressBarから構成されています。Viewにおけるbackgroundで何か画像を設定して、ProgressBarにおけるdrawableで何か画像を設定した場合、Viewの画像が背景に見えて、ProgressBarにおける画像が手前に見えるようになります。

2013年6月11日火曜日

ClassCastException android.widget.* cannot be cast to android.widget.*

ClassCastException android.widget.* cannot be cast to android.widget.*

下記のエラーが出た。
具体例1.java.lang.ClassCastException: android.widget.ImageView cannot be cast to android.widget.Button

具体例2.java.lang.ClassCastException: android.widget.LinearLayout cannot be cast to android.widget.Button

私が遭遇した上記エラーのViewの階層構造の概念図は次のとおりである。
<LinearLayout> parent
    <LinearLayout> child
        <Button> has it's original ID.

<LinearLayout> parentの場所から、ButtonのIDをfindViewById()を使って指定して、そのButtonを使うことはできるし、エラーは発生しなかった。アプリに1個しか存在しないIDの値を使うのであるから問題はない。

この方法で以前は問題なく動作していたが、原因は不明であるが、ある時点以降、ButtonのIDを指定すると、「それはImageViewなのでButtonにcastすることはできない」という趣旨のエラーが出て、アプリがクラッシュするようになった。エラー発生箇所によっては、ImageViewではなく、LinearLayoutであったりTextViewであったりした。XMLやIDを調べたが、ButtonはButtonであった。しかしながら、ImageViewであるとされるIDの値は、ButtonのIDの値と同一なのである。このImageViewは私には身に覚えのないViewだ。変だね。

そこで、日本人が得意とするゲーム感覚で、<LinearLayout> childの場所から、つまり、Buttonの直接上のViewから、ButtonのIDを指定して使ったところ、問題無く動作するようになった。
非論理的不都合には、非論理的対応をするのが良いのかもしれない。むむむ!

なお、<LinearLayout> childでは、addView()やremoveAllViews()を使って、その内部にViewを実行時に加えたり削除したりしていた。

leaked ServiceConnection

Activity <App Name> has leaked ServiceConnection <ServiceConnection Name>@ that was originally bound here

このエラーに関しては次のサイトで議論されている。

実行順序は次のとおりである。
  1. onDestroy()....前のActivityが実行したonDestroy()
  2. unbindService()
  3. 上記エラーログ
  4. onUnbind()....Service
  5. onDestroy()....Service
  6. onCreate()....次のActivityが実行したonCreate()
現象の原因は、Answersの<4>が参考になりそうである。
対策としてはAnswersの<3>と<8>に書かれてあるが、この対策が真実であるのか私は疑問に感じた。ので、他の方法を模索した。が、成功しなかった。
結局、対策は、Answersの<3>と<8>に書かれてあるように、unbindService()をonDestroy()で行っているのであれば、そのbindService()はonCreate()で行わねばならない、ということであった。
このような対応策が非論理的であると感じてしまうのは、果たして私だけだろうか。。。。(あんた、だけや!)。。。むむむ!

2013年6月10日月曜日

Adding Recent Query Suggestions

Adding Recent Query Suggestions

前回の記事Action Barに検索Viewを設けるでは、入力欄を設けただけだった。ここで作業を終わってしまうのであれば、単なる、マニアである。SearchManagerはSuggestionsを利用してこそ有益になる。

Adding Recent Query Suggestionsに書かれているとおり下記のとおりサンプルプログラムを作ってみた。この記事で実現されるのは、利用者が過去に入力した文字列をそのまま候補として掲示することである。

public class MainActivity extends Activity{
    private final String TAG = "MainActivity";

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

    @Override
    public boolean onCreateOptionsMenu(Menu menu){
        getMenuInflater().inflate(R.menu.main, menu);
        SearchView sV;
        SearchManager sM = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
        ComponentName c = getComponentName();
        SearchableInfo si = sM.getSearchableInfo(c);
        MenuItem mi = menu.findItem(R.id.action_Search);
        sV = (SearchView)mi.getActionView();
        sV.setSearchableInfo(si);
        sV.setIconifiedByDefault(false);
     
        /*
         * setSubmitButtonEnabled()について
         * trueは、送信ボタンが右端に表示される。
         * falseは、表示されない。
         */
        sV.setSubmitButtonEnabled(true);
     
        /*
         * setMaxWidth()について
         * これを設定しない場合、幅が狭い。このため設定した方が良い。
         * 引数には、物理画面の横幅の大きさを超える値を設定しても問題は無い。
         * 超えた値であっても、送信ボタンやアイコンは表示される。タイトル文字列は見えない。
         */
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        sV.setMaxWidth(metrics.widthPixels);
//        setTitle("");これは不要です。
        return true;
    }

    @Override
    protected void onNewIntent(Intent intent){
        setIntent(intent);
        if(Intent.ACTION_SEARCH.equals(intent.getAction())==true){
            String q = intent.getStringExtra(SearchManager.QUERY);
            SearchRecentSuggestions srs;
            srs = new SearchRecentSuggestions(this,
                MySuggestionProvider.AUTHORITY,
                MySuggestionProvider.MODE
                );
            srs.saveRecentQuery(q, null);
            Log.d(TAG, "onNewIntent() : query==" + q);
        }
    }
}

MySuggestionProvider.java
public class MySuggestionProvider extends SearchRecentSuggestionsProvider {
    public final static String AUTHORITY = "com.example.searchable.MySuggestionProvider";
    public final static int MODE = DATABASE_MODE_QUERIES;

    public MySuggestionProvider() {
        setupSuggestions(AUTHORITY, MODE);
    }
}


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.searchable"
    android:versionCode="1"
    android:versionName="1.0"
    >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17"
        >
    </uses-sdk>
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        >
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
            <meta-data
                android:name="android.app.searchable"
                android:resource="@xml/searchable"
                />
        </activity>
        <provider
            android:name=".MySuggestionProvider"
            android:authorities="com.example.searchable.MySuggestionProvider"
            >
        </provider>
    </application>
</manifest>


res/xml/searchable.xml
<?xml version="1.0" encoding="utf-8"?>
<searchable
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:hint="@string/SearchHint"
    android:searchSuggestAuthority="com.example.searchable.MySuggestionProvider"
    android:searchSuggestSelection=" ?"
    >
</searchable>