自定义竖直Seekbar

在做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);

}

}

我们需要重写的自然包括构造方法,这个不再说。其次还有onProgressRefreshsetThumbPos

onDrawonMeasuresetThumbonSizeChangedonTouchEventtrackTouchEvent

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);

}

其中heightwidth分别是你的竖直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是在设置seekbarthumb位置,这个方法我是这样重写的:

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);

}

这个方法只是某一个瞬间的seekbarthumb的位置,因此我们需要重写:

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"

其中这两行很重要,第一行主要是对seekbarthumb前后的条进行绘制。下面我给出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的值。这样解决了这个问题。







你可能感兴趣的:(自定义竖直Seekbar)