ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 세로 프로그래스바(Vertical SeekBar)
    안드로이드/학습&강좌 2011. 7. 30. 01:10

    가로로 커스터 마이징한 프로그래스바(SeekBar)에 대해서는 전 강좌에서 설명한 적이 있다.

    가로에 대한 설명은

    http://ememomo.tistory.com/entry/커스톰RatingBarProgressBar

    이부분을 참고하시기 바란다.

    이번에 소개할 내용은 이 프로그래스바를 세로로 커스터 마이징 하는 것이다. 필자도 인터넷에 떠도는 소스를 가지고 튜닝해서
    사용했다. 만드는 방법은 어렵지만 사용법은 간단하므로 따라해 보길 바란다.

    직접적으로 컨트롤할 부분은 사용할 Activity 내에서만 잘 정의해 주면 되므로 어려울 것이 없다.

    실행화면을 먼저 보여 주자면



    이런 모습으로 만들 수 있다.

    만든 모습을 보면 총 3개의 Java 파일을 이용한다. 먼저 View를 상속한 클래스 두개를 이용하여 만든다. 1. VerticalProgressBar.Java
    package com.ememomo.tistory;
    
    import com.tokaracamara.android.verticalslidevar.R;
    
    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.util.Log;
    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 in = (StateListDrawable) drawable;
                StateListDrawable out = new StateListDrawable();
                /*int numStates = in.getStateCount();
                for (int i = 0; i < numStates; i++) {
                    out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
                }*/
                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); } }

    리스너를 정의한 클래스이다. VerticalSeekBar.Java
    package com.ememomo.tistory;
    
    import android.content.Context;
    import android.util.AttributeSet;
    
    
    
    /**
     * A SeekBar is an extension of ProgressBar that adds a draggable thumb. The user can touch
     * the thumb and drag left or right to set the current progress level or use the arrow keys.
     * Placing focusable widgets to the left or right of a SeekBar is discouraged.
     * 

    * Clients of the SeekBar can attach a {@link SeekBar.OnSeekBarChangeListener} to * be notified of the user's actions. * * @attr ref android.R.styleable#SeekBar_thumb */ public class VerticalSeekBar extends AbsVerticalSeekBar { /** * A callback that notifies clients when the progress level has been * changed. This includes changes that were initiated by the user through a * touch gesture or arrow key/trackball as well as changes that were initiated * programmatically. */ public interface OnSeekBarChangeListener { /** * Notification that the progress level has changed. Clients can use the fromUser parameter * to distinguish user-initiated changes from those that occurred programmatically. * * @param seekBar The SeekBar whose progress has changed * @param progress The current progress level. This will be in the range 0..max where max * was set by {@link ProgressBar#setMax(int)}. (The default value for max is 100.) * @param fromUser True if the progress change was initiated by the user. */ void onProgressChanged(VerticalSeekBar seekBar, int progress, boolean fromUser); /** * Notification that the user has started a touch gesture. Clients may want to use this * to disable advancing the seekbar. * @param seekBar The SeekBar in which the touch gesture began */ void onStartTrackingTouch(VerticalSeekBar seekBar); /** * Notification that the user has finished a touch gesture. Clients may want to use this * to re-enable advancing the seekbar. * @param seekBar The SeekBar in which the touch gesture began */ void onStopTrackingTouch(VerticalSeekBar seekBar); } private OnSeekBarChangeListener mOnSeekBarChangeListener; public VerticalSeekBar(Context context) { this(context, null); } public VerticalSeekBar(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.seekBarStyle); } public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override void onProgressRefresh(float scale, boolean fromUser) { super.onProgressRefresh(scale, fromUser); if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser); } } /** * Sets a listener to receive notifications of changes to the SeekBar's progress level. Also * provides notifications of when the user starts and stops a touch gesture within the SeekBar. * * @param l The seek bar notification listener * * @see SeekBar.OnSeekBarChangeListener */ public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) { mOnSeekBarChangeListener = l; } @Override void onStartTrackingTouch() { if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onStartTrackingTouch(this); } } @Override void onStopTrackingTouch() { if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onStopTrackingTouch(this); } } }

    두개의 클래스를 이용하여 기능을 정의하는 클래스 AbsVerticalSeekBar.Java
    package com.ememomo.tistory;
    
    import com.tokaracamara.android.verticalslidevar.R;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Rect;
    import android.graphics.drawable.Drawable;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.KeyEvent;
    import android.view.MotionEvent;
    
    public class AbsVerticalSeekBar extends VerticalProgressBar{
    
        private Drawable mThumb;
        private int mThumbOffset;
    
        /**
         * On touch, this offset plus the scaled value from the position of the
         * touch will form the progress value. Usually 0.
         */
        float mTouchProgressOffset;
    
        /**
         * Whether this is user seekable.
         */
        boolean mIsUserSeekable = true;
    
        /**
         * On key presses (right or left), the amount to increment/decrement the
         * progress.
         */
        private int mKeyProgressIncrement = 1;
    
        private static final int NO_ALPHA = 0xFF;
        private float mDisabledAlpha;
    
        public AbsVerticalSeekBar(Context context) {
        	super(context);
        }
    
        public AbsVerticalSeekBar(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public AbsVerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
    
            TypedArray a = context.obtainStyledAttributes(attrs,
                    R.styleable.SeekBar, defStyle, 0);
            Drawable thumb = a.getDrawable(R.styleable.SeekBar_android_thumb);
            setThumb(thumb); // will guess mThumbOffset if thumb != null...
            // ...but allow layout to override this
            int thumbOffset =
                    a.getDimensionPixelOffset(R.styleable.SeekBar_android_thumbOffset, getThumbOffset());
            setThumbOffset(thumbOffset);
            a.recycle();
    
            a = context.obtainStyledAttributes(attrs,
                    R.styleable.Theme, 0, 0);
            mDisabledAlpha = a.getFloat(R.styleable.Theme_android_disabledAlpha, 0.5f);
            a.recycle();
        }
    
        /**
         * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar.
         * 

    * If the thumb is a valid drawable (i.e. not null), half its width will be * used as the new thumb offset (@see #setThumbOffset(int)). * * @param thumb Drawable representing the thumb */ public void setThumb(Drawable thumb) { if (thumb != null) { thumb.setCallback(this); // Assuming the thumb drawable is symmetric, set the thumb offset // such that the thumb will hang halfway off either edge of the // progress bar. mThumbOffset = (int)thumb.getIntrinsicHeight() / 2; } mThumb = thumb; invalidate(); } /** * @see #setThumbOffset(int) */ public int getThumbOffset() { return mThumbOffset; } /** * Sets the thumb offset that allows the thumb to extend out of the range of * the track. * * @param thumbOffset The offset amount in pixels. */ public void setThumbOffset(int thumbOffset) { mThumbOffset = thumbOffset; invalidate(); } /** * Sets the amount of progress changed via the arrow keys. * * @param increment The amount to increment or decrement when the user * presses the arrow keys. */ public void setKeyProgressIncrement(int increment) { 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 max progress. * * @return The amount to increment or decrement when the user presses the * arrow keys. This will be positive. */ public int getKeyProgressIncrement() { return mKeyProgressIncrement; } @Override public synchronized void setMax(int max) { 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 protected boolean verifyDrawable(Drawable who) { return who == mThumb || super.verifyDrawable(who); } @Override protected void drawableStateChanged() { 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 void onProgressRefresh(float scale, boolean fromUser) { Drawable thumb = mThumb; if (thumb != null) { setThumbPos(getHeight(), thumb, scale, Integer.MIN_VALUE); /* * Since we draw translated, the drawable's bounds that it signals * for invalidation won't be the actual bounds we want invalidated, * so just invalidate this whole view. */ invalidate(); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { Drawable d = getCurrentDrawable(); Drawable thumb = mThumb; int thumbWidth = thumb == null ? 0 : thumb.getIntrinsicWidth(); // The max height does not incorporate padding, whereas the height // parameter does int trackWidth = Math.min(mMaxWidth, w - mPaddingRight - mPaddingLeft); int max = getMax(); float scale = max > 0 ? (float) getProgress() / (float) max : 0; if (thumbWidth > trackWidth) { int gapForCenteringTrack = (thumbWidth - trackWidth) / 2; if (thumb != null) { setThumbPos(h, thumb, scale, gapForCenteringTrack * -1); } if (d != null) { // Canvas will be translated by the padding, so 0,0 is where we start drawing d.setBounds(gapForCenteringTrack, 0, w - mPaddingRight - mPaddingLeft - gapForCenteringTrack, h - mPaddingBottom - mPaddingTop); } } else { if (d != null) { // Canvas will be translated by the padding, so 0,0 is where we start drawing d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, h - mPaddingBottom - mPaddingTop); } int gap = (trackWidth - thumbWidth) / 2; if (thumb != null) { setThumbPos(h, thumb, scale, gap); } } } /** * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and */ private void setThumbPos(int h, Drawable thumb, float scale, int gap) { int available = h - mPaddingTop - mPaddingBottom; int thumbWidth = thumb.getIntrinsicWidth(); int thumbHeight = thumb.getIntrinsicHeight(); available -= thumbHeight; // The extra space for the thumb to move on the track available += mThumbOffset * 2; int thumbPos = (int) ((1-scale) * available); int leftBound, rightBound; if (gap == Integer.MIN_VALUE) { Rect oldBounds = thumb.getBounds(); leftBound = oldBounds.left; rightBound = oldBounds.right; } else { leftBound = gap; rightBound = gap + thumbWidth; } // Canvas will be translated, so 0,0 is where we start drawing thumb.setBounds(leftBound, thumbPos, rightBound, thumbPos + thumbHeight); } @Override protected synchronized void onDraw(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, mPaddingTop - mThumbOffset); mThumb.draw(canvas); canvas.restore(); } } @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Drawable d = getCurrentDrawable(); int thumbWidth = mThumb == null ? 0 : mThumb.getIntrinsicWidth(); int dw = 0; int dh = 0; if (d != null) { dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); dw = Math.max(thumbWidth, dh); dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); } dw += mPaddingLeft + mPaddingRight; dh += mPaddingTop + mPaddingBottom; setMeasuredDimension(resolveSize(dw, widthMeasureSpec), resolveSize(dh, heightMeasureSpec)); } @Override public boolean onTouchEvent(MotionEvent event) { if (!mIsUserSeekable || !isEnabled()) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: setPressed(true); onStartTrackingTouch(); trackTouchEvent(event); break; case MotionEvent.ACTION_MOVE: trackTouchEvent(event); attemptClaimDrag(); break; case MotionEvent.ACTION_UP: trackTouchEvent(event); onStopTrackingTouch(); setPressed(false); // ProgressBar doesn't know to repaint the thumb drawable // in its inactive state when the touch stops (because the // value has not apparently changed) invalidate(); break; case MotionEvent.ACTION_CANCEL: onStopTrackingTouch(); setPressed(false); invalidate(); // see above explanation break; } return true; } private void trackTouchEvent(MotionEvent event) { final int height = getHeight(); final int available = height - mPaddingTop - mPaddingBottom; int y = height - (int)event.getY(); float scale; float progress = 0; if (y < mPaddingBottom) { scale = 0.0f; } else if (y > height - mPaddingTop) { scale = 1.0f; } else { scale = (float)(y - mPaddingBottom) / (float)available; progress = mTouchProgressOffset; } final int max = 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. */ private void attemptClaimDrag() { if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(true); } } /** * This is called when the user has started touching this widget. */ void onStartTrackingTouch() { } /** * This is called when the user either releases his touch or the touch is * canceled. */ void onStopTrackingTouch() { } /** * Called when the user changes the seekbar's progress by using a key event. */ void onKeyChange() { } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { int progress = getProgress(); switch (keyCode) { case KeyEvent.KEYCODE_DPAD_DOWN: if (progress <= 0) break; setProgress(progress - mKeyProgressIncrement, true); onKeyChange(); return true; case KeyEvent.KEYCODE_DPAD_UP: if (progress >= getMax()) break; setProgress(progress + mKeyProgressIncrement, true); onKeyChange(); return true; } return super.onKeyDown(keyCode, event); } }

    프로그래스바를 세로로 만들뿐인데 상당히 복잡하다.. 이 안에 내용에 대해선 각자 분석해 보길 바란다. 본인도 그저 가져다 쓸 뿐이므로. 그리 고민하지 않았다.. 기능에 대해 자세한 설명은 Comment 로 남겨 주면 매우 감사~~ 이제 위 화면을 띄우기 위한 Activity에 대해 설명하겠다. 먼저 세로로 그릴 Progress 내의 커스터 마이징은 xml로 하였다. progress_vertical.xml 이 XML을 이용하여 뒷부분의 색과 안에 채워질 Bar에 대한 컬러를 정의한다.
    
    
    
        
            
                
                
            
        
        
            
                
                    
                    
                
             
        
    
        
            
                
                    
                    
                
            
        
    
    
    View로 쓰일 XML 화면을 만든 XML이다. main.xml
    
    
    	android:layout_height="fill_parent">
    	
    	
    		
    	
    		
    		
    		
    
    
    
    마지막으로 이 뷰를 사용한 Activity 사용하 실 분들은 위의 클래스들을 만들어 놓고 이 Activity에 대해서만 수정하면 손쉽게 이용 할 수 잇다. VerticalSlidebarExampleActivity.Java
    package com.ememomo.tistory;
    
    import com.tokaracamara.android.verticalslidevar.R;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.widget.SeekBar;
    import android.widget.TextView;
    import android.widget.SeekBar.OnSeekBarChangeListener;
    
    public class VerticalSlidebarExampleActivity extends Activity {
    	
    	TextView 		txt_Progress;
    	TextView 		txt_HorizonProgress;
    	VerticalSeekBar vertical_SeekBar;
    	SeekBar			horizon_SeekBar;
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            txt_Progress 		= (TextView)findViewById(R.id.progress);
            vertical_SeekBar 	= (VerticalSeekBar)findViewById(R.id.vertical_seekbar);
            txt_HorizonProgress = (TextView)findViewById(R.id.horizon_progress);
            horizon_SeekBar     = (SeekBar)findViewById(R.id.horizontal_seekbar);
           
            vertical_SeekBar.setMax(100);
            vertical_SeekBar.setProgress(30);
            
            txt_Progress.setText("Progress is : " + vertical_SeekBar.getProgress());
            vertical_SeekBar.setOnSeekBarChangeListener(new VerticalSeekBar.OnSeekBarChangeListener() {
    
        		@Override
        		public void onStopTrackingTouch(VerticalSeekBar seekBar) {
        		}
    
        		@Override
        		public void onStartTrackingTouch(VerticalSeekBar seekBar) {
        			
        		}
    
        		@Override
        		public void onProgressChanged(VerticalSeekBar seekBar, int progress,
        				boolean fromUser) {
        			
        			txt_Progress.setText("Progress is : " + vertical_SeekBar.getProgress());
        		}
        	});
            
            horizon_SeekBar.setMax(100);
            horizon_SeekBar.setProgress(50);
            txt_HorizonProgress.setText("Progress is : " + horizon_SeekBar.getProgress());
            
            horizon_SeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
    			
    			@Override
    			public void onStopTrackingTouch(SeekBar arg0) {
    				// TODO Auto-generated method stub
    				
    			}
    			
    			@Override
    			public void onStartTrackingTouch(SeekBar arg0) {
    				// TODO Auto-generated method stub
    				
    			}
    			
    			@Override
    			public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
    				 txt_HorizonProgress.setText("Progress is : " + horizon_SeekBar.getProgress());
    				
    			}
    		});
    
        }
    }
    
    내용이 많긴 하지만 사용법은 간단하다. 압축파일은 별도 보관하고 있으니 필요하신분은 Mail을 남기시면 보내드리도록 하겠다.~

    소스 요청하시는 분들이 많아 소스 공개 하도록 하겠습니다.
    답변을 못드린점 죄송합니다~~.
    소스를 퍼가시는 분들은 댓글 매너를 지켜 주세요 ㅎ

    댓글

COPYRIGHT 2010 EpoNg. ALL RIGHTS RESERVED.