版本:2.0
系统自带的SeekBar样式是水平的,如果需求一个垂直方向的效果就需要自定义了。原理很简单,即定义一个类继承于SeekBar,并在OnDraw方法里面旋转一下视图。
代码如下:
package android.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
public class VerticalSeekBar extends SeekBar {
public VerticalSeekBar(Context context) {
super(context);
}
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public VerticalSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(h, w, oldh, oldw);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
protected void onDraw(Canvas c) {
//将SeekBar转转90度
c.rotate(-90);
//将旋转后的视图移动回来
c.translate(-getHeight(),0);
Log.i("getHeight()",getHeight()+"");
super.onDraw(c);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
int i=0;
//获取滑动的距离
i=getMax() - (int) (getMax() * event.getY() / getHeight());
//设置进度
setProgress(i);
Log.i("Progress",getProgress()+"");
//每次拖动SeekBar都会调用
onSizeChanged(getWidth(), getHeight(), 0, 0);
Log.i("getWidth()",getWidth()+"");
Log.i("getHeight()",getHeight()+"");
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
}
xml布局文件:
/*
* Copyright (C) 2011 The MusicMod Project
*
* Licensed under the Apache 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 or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.joggingTunes.android.activities;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.StateListDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.shapes.Shape;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewParent;
import android.widget.RemoteViews.RemoteView;
import android.os.Parcel;
import android.os.Parcelable;
@RemoteView
public class VerticalProgressBar extends View {
private static final int MAX_LEVEL = 10000;
int mMinWidth;
int mMaxWidth;
int mMinHeight;
int mMaxHeight;
private int mProgress;
private int mSecondaryProgress;
private int mMax;
private Drawable mProgressDrawable;
private Drawable mCurrentDrawable;
Bitmap mSampleTile;
private boolean mNoInvalidate;
private RefreshProgressRunnable mRefreshProgressRunnable;
private long mUiThreadId;
private boolean mInDrawing;
protected int mScrollX;
protected int mScrollY;
protected int mPaddingLeft;
protected int mPaddingRight;
protected int mPaddingTop;
protected int mPaddingBottom;
protected ViewParent mParent;
/**
* Create a new progress bar with range 0...100 and initial progress of 0.
*
* @param context
* the application environment
*/
public VerticalProgressBar(Context context) {
this(context, null);
}
public VerticalProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.progressBarStyle);
}
public VerticalProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mUiThreadId = Thread.currentThread().getId();
initProgressBar();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ProgressBar, defStyle, 0);
mNoInvalidate = true;
Drawable drawable = a
.getDrawable(R.styleable.ProgressBar_android_progressDrawable);
if (drawable != null) {
drawable = tileify(drawable, false);
// Calling this method can set mMaxHeight, make sure the
// corresponding
// XML attribute for mMaxHeight is read after calling this method
setProgressDrawable(drawable);
}
mMinWidth = a.getDimensionPixelSize(
R.styleable.ProgressBar_android_minWidth, mMinWidth);
mMaxWidth = a.getDimensionPixelSize(
R.styleable.ProgressBar_android_maxWidth, mMaxWidth);
mMinHeight = a.getDimensionPixelSize(
R.styleable.ProgressBar_android_minHeight, mMinHeight);
mMaxHeight = a.getDimensionPixelSize(
R.styleable.ProgressBar_android_maxHeight, mMaxHeight);
setMax(a.getInt(R.styleable.ProgressBar_android_max, mMax));
setProgress(a.getInt(R.styleable.ProgressBar_android_progress,
mProgress));
setSecondaryProgress(a.getInt(
R.styleable.ProgressBar_android_secondaryProgress,
mSecondaryProgress));
mNoInvalidate = false;
a.recycle();
}
/**
* Converts a drawable to a tiled version of itself. It will recursively
* traverse layer and state list drawables.
*/
private Drawable tileify(Drawable drawable, boolean clip) {
if (drawable instanceof LayerDrawable) {
LayerDrawable background = (LayerDrawable) drawable;
final int N = background.getNumberOfLayers();
Drawable[] outDrawables = new Drawable[N];
for (int i = 0; i < N; i++) {
int id = background.getId(i);
outDrawables[i] = tileify(
background.getDrawable(i),
(id == android.R.id.progress || id == android.R.id.secondaryProgress));
}
LayerDrawable newBg = new LayerDrawable(outDrawables);
for (int i = 0; i < N; i++) {
newBg.setId(i, background.getId(i));
}
return newBg;
} else if (drawable instanceof StateListDrawable) {
StateListDrawable out = new StateListDrawable();
return out;
} else if (drawable instanceof BitmapDrawable) {
final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
if (mSampleTile == null) {
mSampleTile = tileBitmap;
}
final ShapeDrawable shapeDrawable = new ShapeDrawable(
getDrawableShape());
return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
ClipDrawable.HORIZONTAL) : shapeDrawable;
}
return drawable;
}
Shape getDrawableShape() {
final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
return new RoundRectShape(roundedCorners, null, null);
}
/**
*
* Initialize the progress bar's default values:
*
*
* - progress = 0
* - max = 100
*
*/
private void initProgressBar() {
mMax = 100;
mProgress = 0;
mSecondaryProgress = 0;
mMinWidth = 24;
mMaxWidth = 48;
mMinHeight = 24;
mMaxHeight = 48;
}
/**
*
* Get the drawable used to draw the progress bar in progress mode.
*
*
* @return a {@link android.graphics.drawable.Drawable} instance
*
* @see #setProgressDrawable(android.graphics.drawable.Drawable)
*/
public Drawable getProgressDrawable() {
return mProgressDrawable;
}
/**
*
* Define the drawable used to draw the progress bar in progress mode.
*
*
* @param d
* the new drawable
*
* @see #getProgressDrawable()
*/
public void setProgressDrawable(Drawable d) {
if (d != null) {
d.setCallback(this);
// Make sure the ProgressBar is always tall enough
int drawableHeight = d.getMinimumHeight();
if (mMaxHeight < drawableHeight) {
mMaxHeight = drawableHeight;
requestLayout();
}
}
mProgressDrawable = d;
mCurrentDrawable = d;
postInvalidate();
}
/**
* @return The drawable currently used to draw the progress bar
*/
Drawable getCurrentDrawable() {
return mCurrentDrawable;
}
@Override
protected boolean verifyDrawable(Drawable who) {
return who == mProgressDrawable || super.verifyDrawable(who);
}
@Override
public void postInvalidate() {
if (!mNoInvalidate) {
super.postInvalidate();
}
}
private class RefreshProgressRunnable implements Runnable {
private int mId;
private int mProgress;
private boolean mFromUser;
RefreshProgressRunnable(int id, int progress, boolean fromUser) {
mId = id;
mProgress = progress;
mFromUser = fromUser;
}
public void run() {
doRefreshProgress(mId, mProgress, mFromUser);
// Put ourselves back in the cache when we are done
mRefreshProgressRunnable = this;
}
public void setup(int id, int progress, boolean fromUser) {
mId = id;
mProgress = progress;
mFromUser = fromUser;
}
}
private synchronized void doRefreshProgress(int id, int progress,
boolean fromUser) {
float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
final Drawable d = mCurrentDrawable;
if (d != null) {
Drawable progressDrawable = null;
if (d instanceof LayerDrawable) {
progressDrawable = ((LayerDrawable) d)
.findDrawableByLayerId(id);
}
final int level = (int) (scale * MAX_LEVEL);
(progressDrawable != null ? progressDrawable : d).setLevel(level);
} else {
invalidate();
}
if (id == android.R.id.progress) {
onProgressRefresh(scale, fromUser);
}
}
void onProgressRefresh(float scale, boolean fromUser) {
}
private synchronized void refreshProgress(int id, int progress,
boolean fromUser) {
if (mUiThreadId == Thread.currentThread().getId()) {
doRefreshProgress(id, progress, fromUser);
} else {
RefreshProgressRunnable r;
if (mRefreshProgressRunnable != null) {
// Use cached RefreshProgressRunnable if available
r = mRefreshProgressRunnable;
// Uncache it
mRefreshProgressRunnable = null;
r.setup(id, progress, fromUser);
} else {
// Make a new one
r = new RefreshProgressRunnable(id, progress, fromUser);
}
post(r);
}
}
/**
*
* Set the current progress to the specified value.
*
*
* @param progress
* the new progress, between 0 and {@link #getMax()}
*
* @see #getProgress()
* @see #incrementProgressBy(int)
*/
public synchronized void setProgress(int progress) {
setProgress(progress, false);
}
synchronized void setProgress(int progress, boolean fromUser) {
if (progress < 0) {
progress = 0;
}
if (progress > mMax) {
progress = mMax;
}
if (progress != mProgress) {
mProgress = progress;
refreshProgress(android.R.id.progress, mProgress, fromUser);
}
}
/**
*
* Set the current secondary progress to the specified value.
*
*
* @param secondaryProgress
* the new secondary progress, between 0 and {@link #getMax()}
* @see #getSecondaryProgress()
* @see #incrementSecondaryProgressBy(int)
*/
public synchronized void setSecondaryProgress(int secondaryProgress) {
if (secondaryProgress < 0) {
secondaryProgress = 0;
}
if (secondaryProgress > mMax) {
secondaryProgress = mMax;
}
if (secondaryProgress != mSecondaryProgress) {
mSecondaryProgress = secondaryProgress;
refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress,
false);
}
}
/**
*
* Get the progress bar's current level of progress.
*
*
* @return the current progress, between 0 and {@link #getMax()}
*
* @see #setProgress(int)
* @see #setMax(int)
* @see #getMax()
*/
@ViewDebug.ExportedProperty
public synchronized int getProgress() {
return mProgress;
}
/**
*
* Get the progress bar's current level of secondary progress.
*
*
* @return the current secondary progress, between 0 and {@link #getMax()}
*
* @see #setSecondaryProgress(int)
* @see #setMax(int)
* @see #getMax()
*/
@ViewDebug.ExportedProperty
public synchronized int getSecondaryProgress() {
return mSecondaryProgress;
}
/**
*
* Return the upper limit of this progress bar's range.
*
*
* @return a positive integer
*
* @see #setMax(int)
* @see #getProgress()
* @see #getSecondaryProgress()
*/
@ViewDebug.ExportedProperty
public synchronized int getMax() {
return mMax;
}
/**
*
* Set the range of the progress bar to 0...max.
*
*
* @param max
* the upper range of this progress bar
*
* @see #getMax()
* @see #setProgress(int)
* @see #setSecondaryProgress(int)
*/
public synchronized void setMax(int max) {
if (max < 0) {
max = 0;
}
if (max != mMax) {
mMax = max;
postInvalidate();
if (mProgress > max) {
mProgress = max;
refreshProgress(android.R.id.progress, mProgress, false);
}
}
}
/**
*
* Increase the progress bar's progress by the specified amount.
*
*
* @param diff
* the amount by which the progress must be increased
*
* @see #setProgress(int)
*/
public synchronized final void incrementProgressBy(int diff) {
setProgress(mProgress + diff);
}
/**
*
* Increase the progress bar's secondary progress by the specified amount.
*
*
* @param diff
* the amount by which the secondary progress must be increased
*
* @see #setSecondaryProgress(int)
*/
public synchronized final void incrementSecondaryProgressBy(int diff) {
setSecondaryProgress(mSecondaryProgress + diff);
}
@Override
public void setVisibility(int v) {
if (getVisibility() != v) {
super.setVisibility(v);
}
}
@Override
public void invalidateDrawable(Drawable dr) {
if (!mInDrawing) {
if (verifyDrawable(dr)) {
final Rect dirty = dr.getBounds();
final int scrollX = mScrollX + mPaddingLeft;
final int scrollY = mScrollY + mPaddingTop;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
} else {
super.invalidateDrawable(dr);
}
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// onDraw will translate the canvas so we draw starting at 0,0
int right = w - mPaddingRight - mPaddingLeft;
int bottom = h - mPaddingBottom - mPaddingTop;
if (mProgressDrawable != null) {
mProgressDrawable.setBounds(0, 0, right, bottom);
}
}
@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
Drawable d = mCurrentDrawable;
if (d != null) {
// Translate canvas so a indeterminate circular progress bar with
// padding
// rotates properly in its animation
canvas.save();
canvas.translate(mPaddingLeft, mPaddingTop);
d.draw(canvas);
canvas.restore();
}
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
Drawable d = mCurrentDrawable;
int dw = 0;
int dh = 0;
if (d != null) {
dw = Math
.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
dh = Math.max(mMinHeight, Math.min(mMaxHeight, d
.getIntrinsicHeight()));
}
dw += mPaddingLeft + mPaddingRight;
dh += mPaddingTop + mPaddingBottom;
setMeasuredDimension(resolveSize(dw, widthMeasureSpec), resolveSize(dh,
heightMeasureSpec));
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
int[] state = getDrawableState();
if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
mProgressDrawable.setState(state);
}
}
static class SavedState extends BaseSavedState {
int progress;
int secondaryProgress;
/**
* Constructor called from {@link ProgressBar#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
progress = in.readInt();
secondaryProgress = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(progress);
out.writeInt(secondaryProgress);
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public Parcelable onSaveInstanceState() {
// Force our ancestor class to save its state
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.progress = mProgress;
ss.secondaryProgress = mSecondaryProgress;
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setProgress(ss.progress);
setSecondaryProgress(ss.secondaryProgress);
}
}
-
为什么shape外面要包一层clip呢,官方文档解释是clipdrawable是可以自我复制的,来看看定义
android:clipOrientation有两个属性,默认为horizontal,android:gravity有两个属性,默认为left。那我们试试改成vertical和bottom会有什么效果,新建一个progress_vertical.xml,把源码progress_horizontal.xml的内容复制过来,这里去掉了secondaryProgress,修改了clip,shape的渐变中心centerY改为centerX
-
布局中android:progressDrawable="@drawable/progress_vertical",ok,搞定。