seekBarをかいぞう
数値を選択させる時とかにseek barの使用を検討することは良くあると思う。seek barちうのは、progress bar の上にツマミが乗っかってて、そのツマミを弄くると数値が弄くれる具合の部品だ。ところがこの部品、標準のを使おうとすると結構ガッカリする。ちっちゃいbarだと押し辛いので、大きめのサイズを設定してやろうとすると…悲しい画面になる。
何故だかしらないが、ツマミの画像のサイズが追従してくれないので、非常にマヌケなことになっている。こいつをなんとかしたい。
といっても、まあ、ネットを調べれば散々既出の事項で、ツマミの画像を変更してやれることをすぐに判る。
…のだけど、ネットをみるとxmlを使って画面を作成するひとが多いみたいなんだな。つーか、javaのコードから設定するやり方が何処にも載ってねえ。。。…ってことで、全世界に2人は存在する、xml嫌い!コードで書く派のひとのために方法を書いておくよ!
答え!AbsSeekBar.setThumb(Drawable d)を使って画像を設定してあげればいいよ(seekBarはAbsSeekBarから派生している)!終わり。ついでに、下のprogress barの画像を変更したい場合はprogressBar.setProgressDrawable(Drawable d)を使えばOK。終わり。
ところがどうも調べてみると、Thumb(ツマミ)のサイズはView(seekBar)のサイズから決定されるのでは無く、画像のサイズから決定されるつくりになってるみたいなんだな。ちうことで、seek barの大きさを動的に変更したりすると、結局おんなじ問題がついてまわる宿命のようなんだな。。。なんてイケてねえ。。。
そんなわけで、じゃあViewの大きさから画像のサイズを返すようなDrawableを自作してやればいいんじゃね?という発想でやってみたら、それなりに動きましたのでご報告。
まあ、単純にandroid.graphics.drawable.Drawableを継承してクラスを作っただけなんだが…。Drawableはabstractなクラスなんで、幾つかオーバーライドしなくちゃならんメソッドが幾つかあるわけ。それが以下の4つ。
public void setColorFilter(ColorFilter cf) public void setAlpha(int alpha) public int getOpacity() public void draw(Canvas canvas)
使用しないのであれば、こいつらは空実装しておけばよさそうなのだが。。。目を引くのがdraw(Canvas)。まあ、想像通り、seekBarのonDrawの中で呼ばれる(確か)ので、こいつでツマミの絵を出すようにすればいいわけだな。つうか、Canvasを受け取った時点で、もういくらでも好きに出来ますよ!ってことなんで、ゲーム製作脳的には燃えてくる展開だよな!
で、無事に実装が終わってアプリを実行してみると…ツマミが表示されない!ガッカリ!
というのは、実は前述のように、seekBarが画像のサイズを問い合わせて、そこからThumbのサイズを決定してるので、そこんところも修正してやらないといけない。その問い合わせで使用されるメソッドが以下の2つ。
public int getIntrinsicWidth() public int getIntrinsicHeight()
まあ見たままなんだけど、getIntrinsicWidthとgetIntrinsicHeightがそれぞれViewのサイズから算出した幅と高さを返すようにオーバーライドしてやればいい。そうすっと、Thumbのサイズをテケトーに算出してくれて、getBounds()してやるとThumbの描画域をRectで返してくれる。それを使ってDraw(Canvas)してやれば、無事にThumbが表示されるようになる。やったー!
…それで終了でもいいのだが、標準のseekBarってフォーカスが当たると色が変わるんだよね。。。その辺も一応サポートしておこう。
フォーカスが当たってる、等々の情報はint[] getState()で取得できるので、そんなかに"state_focused"に相当するint値が入っているかを走査してやればよい。上のdraw(Canvas)にて、フォーカスのあたり具合で分岐してやればいいわけだね。
なお、Stateを使用する場合には以下のメソッドをオーバーライドしてやる必要がある。
public boolean isStateful() { return true; }
上のクラスでreturn falseしてるままだと、受け取ったステータスを破棄しちゃうので、トゥルっておかないとだめ。
これで望む通りにThumbが表示される。ついでにprogressBar部分のDrawableも置き換える時の注意点を記載しておこう。
特に注意するところは無いのだが、メーターのたまり具合に応じて表示してやるところが問題となるか。メータの溜まり具合はgetLevel()で取得できて、これは10000を100%とするint値なので、それを適当に加工してdraw(Canvas)してあげてください。
てなあたりを踏まえてつくったぼくのseek barはこれだ!
んー、それなりに満足してるけど、もっとcoolにならんもんかな。。。
まあ、ソースも載っけてみますので、暇なひとは見てみて。
import android.content.Context; import android.util.StateSet; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.widget.SeekBar; import android.graphics.drawable.Drawable; import android.graphics.ColorFilter; import android.graphics.PixelFormat; public class MySeekBar extends SeekBar{ private int color = 0xffff0000; public void setColor(int color){ this.color = color; } private final SeekBar seekBar = this; public MySeekBar(Context c){ super(c); this.setThumb(new Thumb()); this.setProgressDrawable(new Progress()); } private class Thumb extends Drawable{ @Override public void setColorFilter(ColorFilter cf){}; @Override public void setAlpha(int alpha){}; @Override public int getOpacity(){return PixelFormat.OPAQUE;}; @Override public boolean isStateful() { return true; } @Override public int getIntrinsicWidth(){ return getIntrinsicHeight()/3; } @Override public int getIntrinsicHeight(){ return seekBar.getHeight() - seekBar.getPaddingTop() - seekBar.getPaddingBottom(); }; @Override public void draw(Canvas canvas){ int[] STATE_FOCUSED = { android.R.attr.state_focused }; Rect rect = new Rect(); rect.set(getBounds()); { Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(0xffffffff); canvas.drawRect( rect, paint); paint.setColor(color); int delta = 4; Rect colorRect = new Rect(rect.left+delta, rect.top+delta, rect.right-delta, rect.bottom-delta); canvas.drawRect( colorRect, paint); if( StateSet.stateSetMatches(STATE_FOCUSED, getState())){ paint.setColor(0xffff4000); } else { paint.setColor(0xff000000); } paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(2); canvas.drawRect( rect, paint); } } } private class Progress extends Drawable{ @Override public void setColorFilter(ColorFilter cf){}; @Override public void setAlpha(int alpha){}; @Override public int getOpacity(){return PixelFormat.OPAQUE;}; @Override public void draw(Canvas canvas){ Paint paint = new Paint(); paint.setAntiAlias(true); Rect rect = new Rect(); rect.set(getBounds()); final int delta = 5; RectF baseRect = new RectF(rect.left+delta, rect.top+delta, rect.right+delta, rect.bottom+delta ); RectF progressRect = new RectF(rect.left, rect.top, rect.width()*getLevel()/10000, rect.bottom ); paint.setColor(0xffffffff); canvas.drawRoundRect(baseRect, 5, 5, paint); paint.setColor(0x80000000 + (0x00ffffff & color)); canvas.drawRoundRect(progressRect, 5, 5, paint); } } }