深度定製雙按鈕調節的SeekBar控件

1.首先在工程values文件夾下定義attrs.xml文件,用來定義控件所需要的屬性,並在其中定義屬性如下:

	
	<declare-styleable name="SeekBarPressure">
		<attr name="minValue" format="float" />
		<attr name="maxValue" format="float" />
		<attr name="duration" format="float" />
		<attr name="width" format="dimension">
			<enum name="fill_parent" value="-1" />
			<enum name="match_parent" value="-1" />
			<enum name="wrap_content" value="-2" />
		</attr>
		<attr name="height" format="dimension">
			<enum name="fill_parent" value="-1" />
			<enum name="match_parent" value="-1" />
			<enum name="wrap_content" value="-2" />
		</attr>
	</declare-styleable>
這些設置的屬性就可以如Android原生的android:layout_width等屬性直接在layout的xml文件中設置

2.在用到此控件的layout的xml文件中需要引入當前自定義控件所在的命名空間,然後在xml中就可以直接用上面定義的屬性。比如我的工程包名是com.android.anndy,那麼命名空間定義以及控件使用方法如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tyre="http://schemas.android.com/apk/res/com.android.anndy"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content">
		
		<com.android.anndy.widget.SeekBarPressure
			android:id="@+id/SBP_ValueSet_Pressure"
			android:layout_width="fill_parent"
			android:layout_height="wrap_content"
			android:layout_centerInParent="true"
			tyre:minValue="1.0"
			tyre:maxValue="4.0"
			tyre:duration="0.5"
			tyre:width="175dip"
			tyre:height="12dip">
		</com.android.anndy.widget.SeekBarPressure>

</LinearLayout>
注意,命名控件的名字(本例是tyre)和屬性設置中的一定要一致(如tyre:minValue)

3.在java文件中定義此控件,完成onDraw的揮之,以及接口的調用

public class SeekBarPressure extends View {
    private static final String TAG = "SeekBarPressure";

    private static final int CLICK_ON_LOW = 1;

    private static final int CLICK_ON_HIGH = 2;

    private static final int CLICK_IN_LOW_AREA = 3;

    private static final int CLICK_IN_HIGH_AREA = 4;

    private static final int CLICK_OUT_AREA = 5;

    private static final int CLICK_INVAILD = 0;

    /*
     * private static final int[] PRESSED_STATE_SET = {
     * android.R.attr.state_focused, android.R.attr.state_pressed,
     * android.R.attr.state_selected, android.R.attr.state_window_focused, };
     */

    private static final int[] STATE_NORMAL = {};

    private static final int[] STATE_PRESSED = {
            android.R.attr.state_pressed, android.R.attr.state_window_focused,
    };

    private Drawable mScrollBarBgNormal;

    private Drawable mScrollBarProgress;

    private Drawable mThumbLow;

    private Drawable mThumbHigh;

    private int mScollBarWidth;

    private int mScollBarHeight;

    private int mThumbWidth;

    private int mThumbHeight;

    private int mHalfScollBarHeight;

    private int mHalfThumbHeight;

    private int mOffsetLow = 0;

    private int mOffsetHigh = 0;

    private int mDistance = 0;

    private float mMin = 0;

    private float mMax = 0;

    private int mDuration = 0;

    private int mFlag = CLICK_INVAILD;

    private OnSeekBarChangeListener mBarChangeListener;

    public SeekBarPressure(Context context) {
        this(context, null);
    }

    public SeekBarPressure(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SeekBarPressure(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        init(context);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SeekBarPressure, defStyle,
                0);

        mMin = a.getFloat(R.styleable.SeekBarPressure_minValue, mMin);
        mMax = a.getFloat(R.styleable.SeekBarPressure_maxValue, mMax);
        mScollBarWidth = a.getLayoutDimension(R.styleable.SeekBarPressure_width, "layout_width");
        mScollBarHeight = a.getLayoutDimension(R.styleable.SeekBarPressure_height, "layout_height");

        mDistance = mScollBarWidth - mThumbWidth;

        mDuration = (int)Math.rint(a.getFloat(R.styleable.SeekBarPressure_duration, mDuration)
                * mDistance / (mMax - mMin));

        if (mMin == 0) {
            throw new RuntimeException(a.getPositionDescription()
                    + ": You must supply a minValue attribute.");
        }
        if (mMax == 0) {
            throw new RuntimeException(a.getPositionDescription()
                    + ": You must supply a maxValue attribute.");
        }
        if (mMin > mMax) {
            throw new RuntimeException(a.getPositionDescription()
                    + ": The minValue attribute must be smaller than the maxValue attribute.");
        }
        if (mDuration == 0) {
            throw new RuntimeException(a.getPositionDescription()
                    + ": You must supply a duration attribute.");
        }

        if (mScollBarWidth == 0 || mScollBarWidth == -1 || mScollBarWidth == -2) {
            throw new RuntimeException(a.getPositionDescription()
                    + ": You must supply a width attribute.");
        }
        if (mScollBarHeight == 0 || mScollBarHeight == -1 || mScollBarHeight == -2) {
            throw new RuntimeException(a.getPositionDescription()
                    + ": You must supply a height attribute.");
        }

        a.recycle();

        TyreLog.d(TAG, "Constructor-->mMinDuration: " + mDuration + "  mScollBarWidth: "
                + mScollBarWidth + "  mScollBarHeight: " + mScollBarHeight + " mDistance: "
                + mDistance + " mMax: " + mMax);

        // mOffsetHigh = mOffsetLow + mDuration;
        // TyreLog.d(TAG, "constructor-->mOffsetHigh: " + mOffsetHigh +
        // "  mOffsetLow: " + mOffsetLow
        // + "  mDuration: " + mDuration);
    }

    public static double formatDouble(double pDouble) {
        BigDecimal bd = new BigDecimal(pDouble);
        BigDecimal bd1 = bd.setScale(3, BigDecimal.ROUND_HALF_UP);
        pDouble = bd1.doubleValue();
        return pDouble;
    }

    public void init(Context context) {
        Resources resources = getResources();

        mScrollBarBgNormal = resources.getDrawable(R.drawable.seekbarpressure_bg_normal);
        mScrollBarProgress = resources.getDrawable(R.drawable.seekbarpressure_bg_progress);
        mThumbLow = resources.getDrawable(R.drawable.seekbarpressure_thumb);
        mThumbHigh = resources.getDrawable(R.drawable.seekbarpressure_thumb);

        // setDrawableState(mThumbLow);
        // setDrawableState(mThumbHigh);
        mThumbLow.setState(STATE_NORMAL);
        mThumbHigh.setState(STATE_NORMAL);

        // mScollBarWidth = mScrollBarBgNormal.getIntrinsicWidth();
        // mScollBarHeight = mScrollBarBgNormal.getIntrinsicHeight();

        mThumbWidth = mThumbLow.getIntrinsicWidth();
        mThumbHeight = mThumbLow.getIntrinsicHeight();
        TyreLog.d(TAG, "init-->mThumbWidth: " + mThumbWidth + "  mThumbHeight: " + mThumbHeight);
    }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        /*
         * TyreLog.d(TAG, "widthMeasureSpec: " + getDefaultSize(mScollBarWidth,
         * widthMeasureSpec) + " heightMeasureSpec" +
         * getDefaultSize(mScollBarHeight, heightMeasureSpec));
         */
        int width = mScollBarWidth;
        int height = mScollBarHeight;
        setMeasuredDimension(width, height);
    }

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        TyreLog.d(TAG, "changed: " + changed + "l:" + l + "t:" + t + "r:" + r + "b:" + b);
        // mScollBarWidth = r - l;
        // mScollBarHeight = b - t;
    }

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mHalfScollBarHeight = mScollBarHeight >> 1;
        mHalfThumbHeight = mThumbHeight >> 1;

        mScrollBarBgNormal.setBounds(0, 0, mScollBarWidth, mScollBarHeight);
        mScrollBarBgNormal.draw(canvas);

        mThumbLow.setBounds(mOffsetLow, mHalfScollBarHeight - mHalfThumbHeight, mOffsetLow
                + mThumbWidth, mHalfScollBarHeight + mHalfThumbHeight);
        mThumbLow.draw(canvas);

        mScrollBarProgress.setBounds(mOffsetLow + mThumbWidth - 2, 0, mOffsetHigh + 3,
                mScollBarHeight);
        mScrollBarProgress.draw(canvas);

        mThumbHigh.setBounds(mOffsetHigh, mHalfScollBarHeight - mHalfThumbHeight, mOffsetHigh
                + mThumbWidth, mHalfScollBarHeight + mHalfThumbHeight);
        mThumbHigh.draw(canvas);

        if (mBarChangeListener != null) {

            double progressLow = formatDouble(mOffsetLow * (mMax - mMin) / mDistance + mMin);
            double progressHigh = formatDouble(mOffsetHigh * (mMax - mMin) / mDistance + mMin);
            TyreLog.d(TAG, "onDraw-->mOffsetLow: " + mOffsetLow + "  mOffsetHigh: " + mOffsetHigh
                    + "  progressLow: " + progressLow + "  progressHigh: " + progressHigh);
            mBarChangeListener.onProgressChanged(this, progressLow, progressHigh, mMax, mMin);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        if (e.getAction() == MotionEvent.ACTION_DOWN) {
            mFlag = getAreaFlag(e);
            TyreLog.d(TAG, "e.getX: " + e.getX() + "mFlag: " + mFlag);
            if (mFlag == CLICK_ON_LOW) {
                mThumbLow.setState(STATE_PRESSED);
            } else if (mFlag == CLICK_ON_HIGH) {
                mThumbHigh.setState(STATE_PRESSED);
            } else if (mFlag == CLICK_IN_LOW_AREA) {
                mThumbLow.setState(STATE_PRESSED);
                if (e.getX() < 0 || e.getX() <= mThumbWidth) {
                    mOffsetLow = 0;
                } else if (e.getX() > mScollBarWidth - mThumbWidth) {
                    mOffsetLow = mDistance - mDuration;
                } else {
                    mOffsetLow = formatInt(e.getX() - (double)mThumbWidth / 2);
                    if (mOffsetHigh - mDuration <= mOffsetLow) {
                        mOffsetHigh = (mOffsetLow + mDuration <= mDistance) ? (mOffsetLow + mDuration)
                                : mDistance;
                        mOffsetLow = mOffsetHigh - mDuration;
                    }
                }
            } else if (mFlag == CLICK_IN_HIGH_AREA) {
                mThumbHigh.setState(STATE_PRESSED);
                if (e.getX() < mDuration) {
                    mOffsetHigh = mDuration;
                    mOffsetLow = mOffsetHigh - mDuration;
                } else if (e.getX() >= mScollBarWidth - mThumbWidth) {
                    mOffsetHigh = mDistance;
                } else {
                    mOffsetHigh = formatInt(e.getX() - (double)mThumbWidth / 2);
                    if (mOffsetHigh - mDuration <= mOffsetLow) {
                        mOffsetLow = (mOffsetHigh - mDuration >= 0) ? (mOffsetHigh - mDuration) : 0;
                        mOffsetHigh = mOffsetLow + mDuration;
                    }
                }
            }
        } else if (e.getAction() == MotionEvent.ACTION_MOVE) {
            if (mFlag == CLICK_ON_LOW) {
                if (e.getX() < 0 || e.getX() <= mThumbWidth) {
                    mOffsetLow = 0;
                } else if (e.getX() > mScollBarWidth - mThumbWidth - mDuration) {
                    mOffsetLow = mDistance - mDuration;
                    mOffsetHigh = mOffsetLow + mDuration;
                } else {
                    mOffsetLow = formatInt(e.getX() - (double)mThumbWidth / 2);
                    if (mOffsetHigh - mOffsetLow <= mDuration) {
                        mOffsetHigh = (mOffsetLow + mDuration <= mDistance) ? (mOffsetLow + mDuration)
                                : mDistance;
                    }
                }
            } else if (mFlag == CLICK_ON_HIGH) {
                if (e.getX() < mDuration + mThumbWidth) {
                    mOffsetHigh = mDuration;
                    mOffsetLow = 0;
                } else if (e.getX() > mScollBarWidth - mThumbWidth) {
                    mOffsetHigh = mDistance;
                } else {
                    mOffsetHigh = formatInt(e.getX() - (double)mThumbWidth / 2);
                    if (mOffsetHigh - mOffsetLow <= mDuration) {
                        mOffsetLow = (mOffsetHigh - mDuration >= 0) ? (mOffsetHigh - mDuration) : 0;
                    }
                }
            }
        } else if (e.getAction() == MotionEvent.ACTION_UP) {
            mThumbLow.setState(STATE_NORMAL);
            mThumbHigh.setState(STATE_NORMAL);
        }

        setProgressLow(formatDouble(mOffsetLow * (mMax - mMin) / mDistance + mMin));
        setProgressHigh(formatDouble(mOffsetHigh * (mMax - mMin) / mDistance + mMin));

        return true;
    }

    public int getAreaFlag(MotionEvent e) {
        int top = mHalfScollBarHeight - mHalfThumbHeight;
        int bottom = mHalfScollBarHeight + mHalfThumbHeight;

        if (e.getY() >= top && e.getY() <= bottom && e.getX() >= mOffsetLow
                && e.getX() <= mOffsetLow + mThumbWidth) {
            return CLICK_ON_LOW;
        } else if (e.getY() >= top && e.getY() <= bottom && e.getX() >= mOffsetHigh
                && e.getX() <= mOffsetHigh + mThumbWidth) {
            return CLICK_ON_HIGH;
        } else if (e.getY() >= top
                && e.getY() <= bottom
                && ((e.getX() >= 0 && e.getX() < mOffsetLow) || ((e.getX() > mOffsetLow
                        + mThumbWidth) && e.getX() <= ((double)mOffsetHigh + mOffsetLow + mThumbWidth) / 2))) {
            return CLICK_IN_LOW_AREA;
        } else if (e.getY() >= top
                && e.getY() <= bottom
                && (((e.getX() > ((double)mOffsetHigh + mOffsetLow + mThumbWidth) / 2) && e.getX() < mOffsetHigh) || (e
                        .getX() > mOffsetHigh + mThumbWidth && e.getX() <= mScollBarWidth))) {
            return CLICK_IN_HIGH_AREA;
        } else if (!(e.getX() >= 0 && e.getX() <= mScollBarWidth && e.getY() >= top && e.getY() <= bottom)) {
            return CLICK_OUT_AREA;
        } else {
            return CLICK_INVAILD;
        }
    }

    public void setMax(double max) {
        this.mMax = (float)max;
    }

    public double getMax() {
        return mMax;
    }

    public void setMin(double min) {
        this.mMin = (float)min;
    }

    public double getMin() {
        return mMin;
    }

    public void setProgressLow(double progressLow) {
        mOffsetLow = formatInt((progressLow - mMin) / (mMax - mMin) * mDistance);
        invalidate();
    }

    public void setProgressHigh(double progressHigh) {
        mOffsetHigh = formatInt((progressHigh - mMin) / (mMax - mMin) * mDistance);
        invalidate();
    }

    public double getProgressLow() {
        return formatDouble(mOffsetLow * (mMax - mMin) / mDistance + mMin);
    }

    public double getProgressHigh() {
        return formatDouble(mOffsetHigh * (mMax - mMin) / mDistance + mMin);
    }

    public void setOnSeekBarChangeListener(OnSeekBarChangeListener mListener) {
        this.mBarChangeListener = mListener;
    }

    public interface OnSeekBarChangeListener {
        public void onProgressChanged(SeekBarPressure seekBar, double progressLow,
                double progressHigh, double max, double min);
    }

    /*
     * public void setDrawableState(Drawable dr) {
     * dr.setState(PRESSED_STATE_SET); invalidate(); }
     * @Override protected void drawableStateChanged() {
     * super.drawableStateChanged(); TyreLog.d(TAG, "drawableStateChanged");
     * mThumbLow.setState(PRESSED_STATE_SET);
     * mThumbHigh.setState(PRESSED_STATE_SET); invalidate(); }
     */

    private int formatInt(double value) {
        BigDecimal bd = new BigDecimal(value);
        BigDecimal bd1 = bd.setScale(0, BigDecimal.ROUND_HALF_UP);
        return bd1.intValue();
    }
}

好了,到此控件的定製已完成,可以像使用Android原生SeekBar那樣在Java文件中使用,或者如上面我說的那樣在xml中直接佈局此控件。

其中使用到的資源文件如下:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章