在做Android车载系统蓝牙音乐的交互界面时,有一个交互界面(音乐播放的音效界面)是需要做一个竖直的SeekBar。谷歌给出的SeekBar是水平的,而且美观上也不是很理想。因此UI做了一套新的界面,这就需要重新写SeekBar了。
参考了谷歌源码里对SeekBar的处理。给出Android里的相关SeekBar源码如下:
packageandroid.widget;
importandroid.content.Context;
importandroid.util.AttributeSet;
publicclassSeekBarextendsAbsSeekBar{
publicinterfaceOnSeekBarChangeListener {
voidonProgressChanged(SeekBar seekBar,intprogress,booleanfromUser);
voidonStartTrackingTouch(SeekBar seekBar);
voidonStopTrackingTouch(SeekBar seekBar);
}
privateOnSeekBarChangeListener mOnSeekBarChangeListener;
publicSeekBar(Context context) {
this(context,null);
}
publicSeekBar(Context context, AttributeSet attrs) {
this(context,attrs,com.android.internal.R.attr.seekBarStyle);
}
publicSeekBar(Context context, AttributeSet attrs, intdefStyle) {
super(context,attrs, defStyle);
}
@Override
voidonProgressRefresh(floatscale,booleanfromUser) {
super.onProgressRefresh(scale,fromUser);
if(mOnSeekBarChangeListener !=null){
mOnSeekBarChangeListener.onProgressChanged(this,getProgress(), fromUser);
}
}
publicvoidsetOnSeekBarChangeListener(OnSeekBarChangeListener l) {
mOnSeekBarChangeListener= l;
}
@Override
voidonStartTrackingTouch(){
if(mOnSeekBarChangeListener !=null){
mOnSeekBarChangeListener.onStartTrackingTouch(this);
}
}
@Override
voidonStopTrackingTouch(){
if(mOnSeekBarChangeListener !=null){
mOnSeekBarChangeListener.onStopTrackingTouch(this);
}
}
}
在seekBar源码里我们看到,首先是定义了一个接口,这个结构有三个方法:
voidonProgressChanged(SeekBar seekBar, intprogress,booleanfromUser);
voidonStartTrackingTouch(SeekBar seekBar);
voidonStopTrackingTouch(SeekBar seekBar);
这三个方法是用来监听SeekBar的拖动监听用的,非常有用。在我们进行SeekBar的监听时要对这三个方法进行重写。从方法的名字就可以看出这三个方法的用处。第一个很明显是在SeekBar拖动时会被调用的,第二个是在开始拖动时进行调用的,第三个是拖动结束时进行调用的。我会在下面具体的例子进行详细的说明。
接下来需要考虑的就是让SeekBar竖起来,让Thumb可以上下拖动,这里我们需要对AbsSeekBar的源码进行分析。如下是源码:
/*
*Copyright (C) 2007 TheAndroid Open Source Project
*
*Licensed under theApache License, Version 2.0 (the"License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS"BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/
packageandroid.widget;
importandroid.content.Context;
importandroid.content.res.TypedArray;
importandroid.graphics.Canvas;
importandroid.graphics.Rect;
importandroid.graphics.drawable.Drawable;
importandroid.util.AttributeSet;
importandroid.view.KeyEvent;
importandroid.view.MotionEvent;
publicabstractclassAbsSeekBarextendsProgressBar{
privateDrawable mThumb;
privateintmThumbOffset;
/**
*On touch, this offset plus the scaled value from the position of the
*touch will form the progress value. Usually 0.
*/
floatmTouchProgressOffset;
/**
*Whether this is userseekable.
*/
booleanmIsUserSeekable =true;
/**
*On key presses (right or left), the amount to increment/decrement the
*progress.
*/
privateintmKeyProgressIncrement = 1;
privatestaticfinalintNO_ALPHA = 0xFF;
privatefloatmDisabledAlpha;
publicAbsSeekBar(Context context) {
super(context);
}
publicAbsSeekBar(Context context, AttributeSet attrs) {
super(context,attrs);
}
publicAbsSeekBar(Context context, AttributeSet attrs, intdefStyle) {
super(context,attrs, defStyle);
TypedArray a =context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.SeekBar,defStyle, 0);
Drawable thumb =a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
setThumb(thumb);
intthumbOffset =
a.getDimensionPixelOffset(com.android.internal.R.styleable.SeekBar_thumbOffset,0);
setThumbOffset(thumbOffset);
a.recycle();
a =context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.Theme,0, 0);
mDisabledAlpha =a.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha,0.5f);
a.recycle();
}
/**
*Sets the thumb that will be drawn at the end of the progress meterwithin the SeekBar
*
*@paramthumbDrawablerepresenting the thumb
*/
publicvoidsetThumb(Drawable thumb) {
if(thumb !=null){
thumb.setCallback(this);
}
mThumb = thumb;
invalidate();
}
/**
*@see#setThumbOffset(int)
*/
publicintgetThumbOffset() {
returnmThumbOffset;
}
/**
*Sets the thumb offset that allows the thumb to extend out of therange of
*the track.
*
*@paramthumbOffset The offset amount in pixels.
*/
publicvoidsetThumbOffset(intthumbOffset) {
mThumbOffset = thumbOffset;
invalidate();
}
/**
*Sets the amount of progress changed via the arrow keys.
*
*@paramincrement The amount to increment or decrement when the user
* presses the arrow keys.
*/
publicvoidsetKeyProgressIncrement(intincrement) {
mKeyProgressIncrement =increment < 0 ? -increment : increment;
}
/**
*Returns the amount of progress changed via the arrow keys.
*
*By default, this will be a value that is derived from the maxprogress.
*
*@returnThe amount to increment or decrement when the user presses the
* arrow keys. This will be positive.
*/
publicintgetKeyProgressIncrement() {
returnmKeyProgressIncrement;
}
@Override
publicsynchronizedvoidsetMax(intmax) {
super.setMax(max);
if((mKeyProgressIncrement == 0) || (getMax()/ mKeyProgressIncrement > 20)) {
//It will take the user too long to change this via keys, change it
//to something more reasonable
setKeyProgressIncrement(Math.max(1,Math.round((float)getMax()/ 20)));
}
}
@Override
protectedbooleanverifyDrawable(Drawable who){
returnwho == mThumb ||super.verifyDrawable(who);
}
@Override
protectedvoiddrawableStateChanged(){
super.drawableStateChanged();
Drawable progressDrawable =getProgressDrawable();
if(progressDrawable !=null){
progressDrawable.setAlpha(isEnabled()? NO_ALPHA : (int)(NO_ALPHA * mDisabledAlpha));
}
if(mThumb !=null&& mThumb.isStateful()) {
int[]state =getDrawableState();
mThumb.setState(state);
}
}
@Override
voidonProgressRefresh(floatscale,booleanfromUser) {
Drawable thumb = mThumb;
if(thumb !=null){
setThumbPos(getWidth(),thumb, scale, Integer.MIN_VALUE);
/*
* Since we draw translated,the drawable's bounds that it signals
* for invalidation won't bethe actual bounds we want invalidated,
* so just invalidate thiswhole view.
*/
invalidate();
}
}
@Override
protectedvoidonSizeChanged(intw,inth,intoldw,intoldh) {
Drawable d =getCurrentDrawable();
Drawable thumb = mThumb;
intthumbHeight = thumb ==null? 0 : thumb.getIntrinsicHeight();
//The max height does not incorporate padding, whereas the height
//parameter does
inttrackHeight = Math.min(mMaxHeight,h - mPaddingTop-mPaddingBottom);
intmax =getMax();
floatscale = max > 0 ? (float)getProgress()/ (float)max : 0;
if(thumbHeight > trackHeight) {
if(thumb !=null){
setThumbPos(w, thumb,scale, 0);
}
intgapForCenteringTrack = (thumbHeight - trackHeight) / 2;
if(d !=null){
//Canvas will be translated by the padding, so 0,0 is where we startdrawing
d.setBounds(0,gapForCenteringTrack,
w -mPaddingRight -mPaddingLeft, h - mPaddingBottom- gapForCenteringTrack
-mPaddingTop);
}
}else{
if(d !=null){
//Canvas will be translated by the padding, so 0,0 is where we startdrawing
d.setBounds(0, 0, w -mPaddingRight -mPaddingLeft, h - mPaddingBottom
-mPaddingTop);
}
intgap = (trackHeight - thumbHeight) / 2;
if(thumb !=null){
setThumbPos(w, thumb,scale, gap);
}
}
}
/**
*@paramgap If set to{@link Integer#MIN_VALUE},this will be ignored and
*/
privatevoidsetThumbPos(intw, Drawable thumb, floatscale,intgap) {
intavailable = w -mPaddingLeft-mPaddingRight;
intthumbWidth = thumb.getIntrinsicWidth();
intthumbHeight = thumb.getIntrinsicHeight();
available -= thumbWidth;
//The extra space for the thumb to move on the track
available += mThumbOffset * 2;
intthumbPos = (int)(scale * available);
inttopBound, bottomBound;
if(gap == Integer.MIN_VALUE) {
Rect oldBounds =thumb.getBounds();
topBound = oldBounds.top;
bottomBound =oldBounds.bottom;
}else{
topBound = gap;
bottomBound = gap +thumbHeight;
}
//Canvas will be translated, so 0,0 is where we start drawing
thumb.setBounds(thumbPos,topBound, thumbPos + thumbWidth, bottomBound);
}
@Override
protectedsynchronizedvoidonDraw(Canvas canvas){
super.onDraw(canvas);
if(mThumb !=null){
canvas.save();
//Translate the padding. For the x, we need to allow the thumb to
//draw in its extra space
canvas.translate(mPaddingLeft- mThumbOffset,mPaddingTop);
mThumb.draw(canvas);
canvas.restore();
}
}
@Override
protectedsynchronizedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec) {
Drawable d =getCurrentDrawable();
intthumbHeight = mThumb ==null? 0 : mThumb.getIntrinsicHeight();
intdw = 0;
intdh = 0;
if(d !=null){
dw = Math.max(mMinWidth,Math.min(mMaxWidth, d.getIntrinsicWidth()));
dh = Math.max(mMinHeight,Math.min(mMaxHeight, d.getIntrinsicHeight()));
dh = Math.max(thumbHeight,dh);
}
dw +=mPaddingLeft +mPaddingRight;
dh +=mPaddingTop +mPaddingBottom;
setMeasuredDimension(resolveSize(dw,widthMeasureSpec),
resolveSize(dh,heightMeasureSpec));
}
@Override
publicbooleanonTouchEvent(MotionEvent event){
if(!mIsUserSeekable || !isEnabled()){
returnfalse;
}
switch(event.getAction()) {
caseMotionEvent.ACTION_DOWN:
setPressed(true);
onStartTrackingTouch();
trackTouchEvent(event);
break;
caseMotionEvent.ACTION_MOVE:
trackTouchEvent(event);
attemptClaimDrag();
break;
caseMotionEvent.ACTION_UP:
trackTouchEvent(event);
onStopTrackingTouch();
setPressed(false);
break;
caseMotionEvent.ACTION_CANCEL:
onStopTrackingTouch();
setPressed(false);
break;
}
returntrue;
}
privatevoidtrackTouchEvent(MotionEvent event) {
finalintwidth = getWidth();
finalintavailable = width - mPaddingLeft-mPaddingRight;
intx = (int)event.getX();
floatscale;
floatprogress = 0;
if(x <mPaddingLeft){
scale = 0.0f;
}elseif(x > width -mPaddingRight){
scale = 1.0f;
}else{
scale = (float)(x-mPaddingLeft)/ (float)available;
progress =mTouchProgressOffset;
}
finalintmax = getMax();
progress += scale * max;
setProgress((int)progress,true);
}
/**
*Tries to claim the user's drag motion, and requests disallowing any
*ancestors from stealing events in the drag.
*/
privatevoidattemptClaimDrag() {
if(mParent!=null){
mParent.requestDisallowInterceptTouchEvent(true);
}
}
/**
*This is called when the user has started touching this widget.
*/
voidonStartTrackingTouch() {
}
/**
*This is called when the user either releases his touch or the touchis
*canceled.
*/
voidonStopTrackingTouch() {
}
/**
*Called when the user changes the seekbar's progress by using a keyevent.
*/
voidonKeyChange() {
}
@Override
publicbooleanonKeyDown(intkeyCode, KeyEvent event) {
intprogress =getProgress();
switch(keyCode) {
caseKeyEvent.KEYCODE_DPAD_LEFT:
if(progress <= 0)break;
setProgress(progress- mKeyProgressIncrement,true);
onKeyChange();
returntrue;
caseKeyEvent.KEYCODE_DPAD_RIGHT:
if(progress >=getMax())break;
setProgress(progress+ mKeyProgressIncrement,true);
onKeyChange();
returntrue;
}
returnsuper.onKeyDown(keyCode,event);
}
}
我们需要重写的自然包括构造方法,这个不再说。其次还有onProgressRefresh、setThumbPos、
onDraw、onMeasure、setThumb、onSizeChanged、onTouchEvent、trackTouchEvent、
dispatchKeyEvent。大概是这几个方法。在重写onDraw时我们可以考虑让画布旋转90度,这样就可以修改seekbar的方向了。我是这样重写这个函数的:
protectedvoidonDraw(Canvas c)
{
c.rotate(-90);
c.translate(-height,0);
super.onDraw(c);
}
这样得到的seekbar是向上的,如果你想做一个向下的seekbar,自然也很简单:
protectedvoid onDraw(Canvasc)
{
c.rotate(90);
c.translate(0,width);
super.onDraw(c);
}
其中height、width分别是你的竖直seekbar的高和宽。
onMeasure方法是自然要重写的,这个使用来画你的seekbar的大小,我重写的是这样的:
protectedsynchronizedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec)
{
width= 40;
height= 255;
//height =View.MeasureSpec.getSize(heightMeasureSpec);
//width =View.MeasureSpec.getSize(widthMeasureSpec);
this.setMeasuredDimension(width,height);
}
setThumbPos是在设置seekbar的thumb位置,这个方法我是这样重写的:
privatevoidsetThumbPos(intw, Drawable thumb, floatscale,intgap) {
intavailable = w+getPaddingLeft()-getPaddingRight();
intthumbWidth = thumb.getIntrinsicWidth();
intthumbHeight = thumb.getIntrinsicHeight();
available -= thumbWidth;
//The extra space for the thumb to move on the track
available +=getThumbOffset() * 2;
intthumbPos = (int)(scale * available);
inttopBound, bottomBound;
if(gap == Integer.MIN_VALUE){
Rect oldBounds =thumb.getBounds();
topBound =oldBounds.top;
bottomBound =oldBounds.bottom;
}else{
topBound = gap;
bottomBound = gap +thumbHeight;
}
//thumb.setBounds(thumbPos,topBound, thumbPos + thumbWidth, bottomBound);
thumb.setBounds(thumbPos,topBound, thumbPos+thumbWidth , bottomBound);
}
这个方法只是某一个瞬间的seekbar里thumb的位置,因此我们需要重写:
voidonProgressRefresh(floatscale,booleanfromUser) {
Drawable thumb =mThumb;
if(thumb !=null){
setThumbPos(getHeight(),thumb, scale, Integer.MIN_VALUE);
invalidate();
}
if(mOnSeekBarChangeListener!=null){
mOnSeekBarChangeListener.onProgressChanged(this,getProgress(), fromUser);
}
}
这样基本上就把一个竖直的seekbar给画出来了。我们就可以在配置文件里进行使用了。以下给出我在配置文件里的使用:
<cs2c.CarBT.VerticalSeekBar
android:id="@+id/bt_music_vseekbar2"
android:layout_toRightOf="@+id/bt_music_vseekbar1"
android:layout_marginLeft="80px"
android:layout_marginTop="15px"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:progressDrawable="@drawable/bt_music_seekbar_style"
android:thumb="@drawable/sound_style_db"
android:maxWidth="40px"
android:minWidth="40px"
android:progress="10"
android:secondaryProgress="0"
android:max="100"
/>
其中cs2c.CarBT是竖直seekbar所在包。在配置文件里使用自定义控件的时候,自己写的view一定要记得带上包名,不然系统没法解析,就会抛出异常。
android:progressDrawable="@drawable/bt_music_seekbar_style"
android:thumb="@drawable/sound_style_db"
其中这两行很重要,第一行主要是对seekbar的thumb前后的条进行绘制。下面我给出bt_music_seekbar_style的配置:
xmlversion="1.0"encoding="utf-8"?>
<layer-listxmlns:android="http://schemas.android.com/apk/res/android">
<itemandroid:id="@android:id/background"
android:drawable="@drawable/sound_style_scrool_bg"/>
<itemandroid:id="@+android:id/SecondaryProgress"
android:drawable="@drawable/sound_style_scrool_bg"/>
<itemandroid:id="@android:id/progress"
android:drawable="@drawable/sound_style_scrool_db"/>
layer-list>
这样就很清晰明了了。第二行主要是thumb的图像,可以换成自己喜欢的图片。
对android:maxWidth="40px"android:minWidth="40px"这两行主要是用来调整thumb和滑动条不在一条中心上的。
最后再给大家分享下我在开发过程中遇到一个小问题,当我对seekbar进行setOnSeekBarChangeListener(this);处理时,刚开始我是在onProgressChanged(VerticalSeekBarVerticalSeekBar,intprogress,booleanfromUser)
对滑动监听进行处理的,结果发现会截获onclick的监听。因为在拖动时候会涉及到点击事件,因为我的程序里有关于点击的监听处理,因此我将拖动处理放到onStopTrackingTouch(VerticalSeekBarVerticalSeekBar)进行处理,使用一个static类型的int变量来存储intprogress的值。这样解决了这个问题。