最近閑的很,如是寫下了本篇博客土全。
自定義控件基本步驟如下捎琐,反正基本上我覺得按我這個(gè)套路來寫会涎,基本上都能很快上手。
推薦按順序閱讀瑞凑。末秃。
第一部分,構(gòu)造函數(shù)籽御,我相信這個(gè)大家都應(yīng)該是明白的练慕。初始化和自定義屬性無關(guān)必要的成員變量,mDensity是屏幕密度技掏,將dp轉(zhuǎn)px時(shí)要用到铃将。
/**
* 第一部分 構(gòu)造函數(shù) 所有的自定義view都可以用下面這段代碼直接copy改下名字就行
**/
//當(dāng)不需要使用xml聲明或者不需要使用inflate動(dòng)態(tài)加載時(shí)候,實(shí)現(xiàn)此構(gòu)造函數(shù)即可
public RulerView(Context context) {
super(context, null);
}
//當(dāng)需要在xml中聲明此控件哑梳,則需要實(shí)現(xiàn)此構(gòu)造函數(shù)劲阎。并且在構(gòu)造函數(shù)中把自定義的屬性與控件的數(shù)據(jù)成員連接起來。
public RulerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
//接受一個(gè)style資源
public RulerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//所有的構(gòu)造函數(shù)都會(huì)匯總到這一步,API21的會(huì)走到一個(gè)包含4個(gè)參數(shù)的構(gòu)造函數(shù)涧衙。
mContext = context;
mDensity = mContext.getResources().getDisplayMetrics().density;
initRulerView(attrs);
}
第二部分哪工,獲取自定義屬性奥此,初始化需要獲取自定義屬性之后的畫筆等等之類的成員變量弧哎。
值得注意的是,這個(gè)地方獲取屬性值的時(shí)候有2種寫法稚虎,一種是我注釋了的撤嫩,一種是沒有注釋的,寫法的區(qū)別是:我注釋過的那段代碼蠢终,如果屬性在使用控件的時(shí)候沒有在xml中使用的話序攘,賦值的方法是不走的。那么就沒有默認(rèn)值寻拂。程奠。我沒注釋的那種寫法是肯定會(huì)走,獲取不到就給默認(rèn)值祭钉。推薦第二種瞄沙,以免出錯(cuò)。
<declare-styleable name="RulerView">
此處的name我看過很多博客慌核,都要么沒說距境,要么說這個(gè)名字隨便寫。其實(shí)不對垮卓,這個(gè)名字垫桂,
代表了在這個(gè)declare-styleable之間的屬性只能在RulerView中使用,其他的空間是看
不到這個(gè)屬性的粟按,同學(xué)們可以去試一下
關(guān)于format值的一些說明
1诬滩,reference :通過@dimen @color @drawable @layout 等等獲取屬性值
2叫搁,color : 顏色值 "#ff0" "#ff0000" "#0ff" "#00ff00000"
3乘寒,boolean :布爾值 true false
4, dimension : 尺寸 50dp 50px 50sp
5,float :浮點(diǎn)值 0.5
6,integer : 整型值 30
7禾锤,string : 字符串 abs
8,fraction :百分?jǐn)?shù) 25%
9科雳,enum :枚舉
10辩越,flag :位或運(yùn)算
/**
* 第二部分,獲取自定義屬性
*
* @param attrs
*/
private void initRulerView(AttributeSet attrs) {
if (null != attrs) {
TypedArray ta = mContext.obtainStyledAttributes(attrs, R.styleable.RulerView);
// int count = ta.getIndexCount();
//
// for (int i = 0; i < count; i++) {
// int attr = ta.getIndex(i);
// switch (attr) {
// case R.styleable.RulerView_cursorColor:
// mCursorColor = ta.getColor(attr, DEFAULT_CURSOR_COLOR);
// break;
//
// }
// }
mBorderWidth = ta.getDimension(R.styleable.RulerView_borderWidth, CustomViewUtil.dp2px(mContext, BORDER_WIDTH));
mCornerRadius = ta.getDimension(R.styleable.RulerView_cornerRadius, CustomViewUtil.dp2px(mContext, CORNER_RADIUS));
mBorderColor = ta.getColor(R.styleable.RulerView_borderColor, BORDER_COLOR);
mCursorWidth = ta.getDimension(R.styleable.RulerView_cursorWidth, CustomViewUtil.dp2px(mContext, DEFAULT_CURSOR_WIDTH));
if (ta.getDrawable(R.styleable.RulerView_cursorDrawable) == null) {
mCursorColor = ta.getColor(R.styleable.RulerView_cursorColor, DEFAULT_CURSOR_COLOR);
} else {
mCursorDrawable = ta.getDrawable(R.styleable.RulerView_cursorDrawable);
}
mCalibrationTailsLength = ta.getDimension(R.styleable.RulerView_calibrationTailsLength, CustomViewUtil.dp2px(mContext, CALIBRATION_TAILS_LENGTH));
mCalibrationTailsWidth = ta.getDimension(R.styleable.RulerView_calibrationTailsWidth, CustomViewUtil.dp2px(mContext, CALIBRATION_TAILS_WIDTH));
mCalibrationTailsColor = ta.getColor(R.styleable.RulerView_calibrationTailsColor, CALIBRATION_TAILS_COLOR);
mCalibrationTailsDistance = ta.getDimension(R.styleable.RulerView_calibrationTailsDistance, CustomViewUtil.dp2px(mContext, CALIBRATION_TAILS_DISTANCE));
mImportantTailsLength = ta.getDimension(R.styleable.RulerView_importantTailsLength, CustomViewUtil.dp2px(mContext, IMPORTANT_TAILS_LENGTH));
mImportantTailsWidth = ta.getDimension(R.styleable.RulerView_importantTailsWidth, CustomViewUtil.dp2px(mContext, IMPORTANT_TAILS_WIDTH));
mImportantTailsColor = ta.getColor(R.styleable.RulerView_importantTailsColor, IMPORTANT_TAILS_COLOR);
mImportantTailsGap = ta.getInteger(R.styleable.RulerView_importantTailsGap, IMPORTANT_TAILS_GAP);
maxValue = ta.getInteger(R.styleable.RulerView_maxValue, MAX_VALUE);
minValue = ta.getInteger(R.styleable.RulerView_minValue, MIN_VALUE);
currentValue = ta.getInteger(R.styleable.RulerView_currentValue, DEFAULT_VALUE);
mValueDistanceImportantTails = ta.getDimension(R.styleable.RulerView_valueDistanceImportantTails, CustomViewUtil.dp2px(mContext, VALUE_DISTANCE_IMPORTANT_TAILS));
mValueColor = ta.getColor(R.styleable.RulerView_valueColor, VALUE_COLOR);
mValueSize = ta.getDimension(R.styleable.RulerView_valueSize, CustomViewUtil.sp2px(mContext, VALUE_SIZE));
dampNumber = ta.getFloat(R.styleable.RulerView_dampNumber, DAMP_NUMBER);
ta.recycle();
}
if (mCalibrationTailsDistance <= Math.max(mImportantTailsWidth, mCalibrationTailsWidth)) {
throw new IllegalArgumentException("mCalibrationTailsDistance must bigger than Math.max(mImportantTailsWidth,mCalibrationTailsWidth)");
}
mCalibrationTailsPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
mImportantTailsPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCursorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCalibrationTailsPaint.setColor(mCalibrationTailsColor);
mCalibrationTailsPaint.setStrokeWidth(mCalibrationTailsWidth);
mImportantTailsPaint.setColor(mImportantTailsColor);
mImportantTailsPaint.setStrokeWidth(mImportantTailsWidth);
mCursorPaint.setColor(mCursorColor);
mCursorPaint.setStrokeWidth(mCursorWidth);
mTextPaint.setTextSize(mValueSize);
mTextPaint.setColor(mValueColor);
mTextPaint.setStyle(Paint.Style.FILL);
mTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
mScroller = new Scroller(mContext);
mMinVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();
rectOfBackGround = new Rect();
setColors(colors);
}
第三部分姑裂,測量寬高馋袜,有點(diǎn)同學(xué)這一部分很是艱難,但是不用怕舶斧,賦值一下代碼欣鳖,重寫其中的2個(gè)方法就行了
/**
* 第三部分,測量控件大小,一般情況下茴厉,前面的方法可以直接copy過去泽台,只需要自己重寫measureWrapWitdh,measureWrapHeight
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//這一部分,可寫可不寫矾缓,如若你不想重寫控件大小怀酷,直接用super就行。如果需要重寫的話套路也很簡單
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = measureWitdh(widthMeasureSpec);
int measureHeight = measureHeight(heightMeasureSpec);
setMeasuredDimension(measureWidth, measureHeight);
}
private int measureWitdh(int widthMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = measureWrapWitdh();
break;
case MeasureSpec.AT_MOST:
result = Math.min(measureWrapWitdh(), specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
private int measureWrapWitdh() {
//測量wrapContent時(shí)寬度,這個(gè)請根據(jù)自己的需求自己計(jì)算嗜闻,我這里默認(rèn)300dp
return CustomViewUtil.dp2px(mContext, 300);
}
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = measureWrapHeight();
break;
case MeasureSpec.AT_MOST:
result = Math.min(measureWrapHeight(), specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
private int measureWrapHeight() {
//測量wrapContent時(shí)高度,這個(gè)請根據(jù)自己的需求自己計(jì)算蜕依,我這里默認(rèn)50dp
return CustomViewUtil.dp2px(mContext, 50);
}
第四部分,在控件發(fā)生變化的時(shí)候 重新賦值寬高琉雳,以及重新為一些需要在控件大小發(fā)生變化時(shí)需要充值賦值的屬性賦值样眠。
/**
* 第四部分,在size發(fā)生變化時(shí)翠肘,重新賦值寬高
*
* @param w
* @param h
* @param oldw
* @param oldh
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
LayerDrawable background = (LayerDrawable) getBackground();
Drawable drawable = background.getDrawable(0);
drawable.getPadding(rectOfBackGround);
}
第五部分檐束,屬于核心以及難點(diǎn)吧,具體實(shí)現(xiàn)我就不說了束倍,自己去看代碼被丧。其實(shí)代碼也沒啥看的,就是通過計(jì)算位置繪制而已肌幽。但是這個(gè)計(jì)算位置是很精細(xì)晚碾,很容易出錯(cuò)的地方,得加倍小心
/**
* 第五部分喂急,繪制格嘁,繪制時(shí),建議分層重下往上繪制
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCalibrationTails(canvas);
drawCursor(canvas);
}
private void drawCalibrationTails(Canvas canvas) {
//計(jì)算需要畫多少刻度線
//1廊移,先計(jì)算可畫刻度線區(qū)域的總長度
int totalLengthCanDrawCalibrationTails = (int) (mWidth - rectOfBackGround.left - rectOfBackGround.right - 2 * mBorderWidth);
int count = (int) (totalLengthCanDrawCalibrationTails / mCalibrationTailsWidth);
canvas.save();
for (int i = 0; i < count; i++) {
int offset = i % 2 == 0 ? (i + 1) / 2 : -(i + 1) / 2;
if (currentValue + offset > maxValue || currentValue + offset < minValue) {
continue;
} else {
//畫刻度線
if (i == 0) {
if ((currentValue + offset) % mImportantTailsGap == 0) {
canvas.drawLine(mWidth / 2, rectOfBackGround.top + mBorderWidth, mWidth / 2, rectOfBackGround.top + mBorderWidth + mImportantTailsLength, mImportantTailsPaint);
} else {
canvas.drawLine(mWidth / 2, rectOfBackGround.top + mBorderWidth, mWidth / 2, rectOfBackGround.top + mBorderWidth + mCalibrationTailsLength, mCalibrationTailsPaint);
}
} else if (i % 2 == 0) {
if (mWidth / 2 + ((i + 1) / 2) * mCalibrationTailsDistance >= mWidth - rectOfBackGround.right - mBorderWidth) {
continue;
}
if ((currentValue + offset) % mImportantTailsGap == 0) {
canvas.drawLine(mWidth / 2 + ((i + 1) / 2) * mCalibrationTailsDistance, rectOfBackGround.top + mBorderWidth, mWidth / 2 + ((i + 1) / 2) * mCalibrationTailsDistance, rectOfBackGround.top + mBorderWidth + mImportantTailsLength, mImportantTailsPaint);
} else {
canvas.drawLine(mWidth / 2 + ((i + 1) / 2) * mCalibrationTailsDistance, rectOfBackGround.top + mBorderWidth, mWidth / 2 + ((i + 1) / 2) * mCalibrationTailsDistance, rectOfBackGround.top + mBorderWidth + mCalibrationTailsLength, mCalibrationTailsPaint);
}
} else {
if (mWidth / 2 - ((i + 1) / 2) * mCalibrationTailsDistance <= rectOfBackGround.left + mBorderWidth) {
continue;
}
if ((currentValue + offset) % mImportantTailsGap == 0) {
canvas.drawLine(mWidth / 2 - ((i + 1) / 2) * mCalibrationTailsDistance, rectOfBackGround.top + mBorderWidth, mWidth / 2 - ((i + 1) / 2) * mCalibrationTailsDistance, rectOfBackGround.top + mBorderWidth + mImportantTailsLength, mImportantTailsPaint);
} else {
canvas.drawLine(mWidth / 2 - ((i + 1) / 2) * mCalibrationTailsDistance, rectOfBackGround.top + mBorderWidth, mWidth / 2 - ((i + 1) / 2) * mCalibrationTailsDistance, rectOfBackGround.top + mBorderWidth + mCalibrationTailsLength, mCalibrationTailsPaint);
}
}
//畫刻度線下面的文字
if ((currentValue + offset) % mImportantTailsGap == 0) {
if ((mWidth / 2 + offset * mCalibrationTailsDistance + mTextPaint.measureText(String.valueOf(currentValue + offset)) / 2) > mWidth - rectOfBackGround.right - mBorderWidth) {
continue;
}
if ((mWidth / 2 + offset * mCalibrationTailsDistance - mTextPaint.measureText(String.valueOf(currentValue + offset)) / 2) < rectOfBackGround.left + mBorderWidth) {
continue;
}
canvas.drawText(String.valueOf(currentValue + offset), mWidth / 2 + offset * mCalibrationTailsDistance - mTextPaint.measureText(String.valueOf(currentValue + offset)) / 2, CustomViewUtil.drawTextFromTop(rectOfBackGround.top + mBorderWidth + Math.max(mCalibrationTailsLength, mImportantTailsLength) + mValueDistanceImportantTails, mTextPaint), mTextPaint);
}
}
}
canvas.restore();
}
private void drawCursor(Canvas canvas) {
canvas.save();
if (mCursorDrawable != null) {
mCursorDrawable.setBounds(mWidth / 2 - mCursorDrawable.getIntrinsicWidth() / 2, (int) (rectOfBackGround.top + mBorderWidth), mWidth / 2 + mCursorDrawable.getIntrinsicWidth() / 2, (int) (rectOfBackGround.top + mBorderWidth + mCursorDrawable.getIntrinsicHeight()));
mCursorDrawable.draw(canvas);
} else {
mCursorPaint.setColor(mCursorColor);
mCursorPaint.setStrokeWidth(mCursorWidth);
mCursorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawLine(mWidth / 2, 0 + rectOfBackGround.top + mBorderWidth, mWidth / 2, mHeight - rectOfBackGround.bottom - mBorderWidth, mCursorPaint);
}
canvas.restore();
}
第六部分:這個(gè)不解釋了
/**
* 第六部分
* 設(shè)置數(shù)據(jù)糕簿,用以第一次賦值探入,或者在listView recycleView復(fù)用的時(shí)候賦值,或者在交互中重新賦值等等
*/
public void setData(int currentValue) {
if (currentValue > maxValue || currentValue < minValue) {
throw new IllegalArgumentException("the value must between minValue and maxValue");
}
this.currentValue = currentValue;
invalidate();
if (mListener != null) {
mListener.onValueChange(currentValue);
}if (mListener != null) {
mListener.onValueChange(currentValue);
}
}
第七部分 重難點(diǎn)部分懂诗,但是也很簡單蜂嗽。注意true和false的返回,
/**
* 第七部分
* 處理觸摸事件
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mScroller.forceFinished(true);
mLastX = (int) event.getX();
mMove = 0;
isMove = false;
break;
case MotionEvent.ACTION_MOVE:
mCurrentX = (int) event.getX();
mMove += (int) ((mLastX - mCurrentX) * dampNumber);
if (mMove < mTouchSlop) {
isMove = true;
}
if (currentValue > maxValue) {
return false;
}
if (currentValue < minValue) {
return false;
}
if (isMove) {
changeMoveAndValue();
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
isMove = false;
countVelocityTracker(event);
break;
}
mLastX = (int) event.getX();
return true;
}
第八部分殃恒,控件滑動(dòng)植旧。Scroller和VelocityTracker的使用
/**
* 第八部分處理滑動(dòng)相關(guān)的事情
*/
private void changeMoveAndValue() {
int tValue = (int) (mMove / mCalibrationTailsDistance);
if (Math.abs(tValue) > 0) {
currentValue = currentValue + tValue;
mMove -= tValue * mCalibrationTailsDistance;
if (currentValue < minValue || currentValue > maxValue) {
currentValue = currentValue < minValue ? minValue : maxValue;
mMove = 0;
mScroller.forceFinished(true);
}
notifyValueChange(currentValue);
}
postInvalidate();
}
private void countMoveEnd() {
int roundMove = Math.round(mMove / mCalibrationTailsDistance);
currentValue = currentValue + roundMove;
if (currentValue < minValue) {
currentValue = minValue;
}
if (currentValue > maxValue) {
currentValue = maxValue;
}
mLastX = 0;
mMove = 0;
notifyValueChange(currentValue);
postInvalidate();
}
private int mLastScrollX;
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
mCurrentX = mScroller.getCurrX();
if (mScroller.getCurrX() == mScroller.getFinalX()) { // over
countMoveEnd();
} else {
mMove += (mLastScrollX - mCurrentX);
changeMoveAndValue();
}
mLastScrollX = mCurrentX;
}
}
private void countVelocityTracker(MotionEvent event) {
// mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(200);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) > mMinVelocity) {
mScroller.fling(0, 0, (int) xVelocity, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
}
postInvalidate();
}
最后
/**
* 定義相關(guān)的監(jiān)聽器
*/
private OnValueChangeListener mListener;
public void setOnValueChangeListener(OnValueChangeListener mListener) {
this.mListener = mListener;
}
public void notifyValueChange(int value) {
if (null != mListener) {
mListener.onValueChange(value);
}
}
public interface OnValueChangeListener {
void onValueChange(int value);
}
實(shí)際上還有一部分是動(dòng)畫,但是本例中未曾用到离唐,以后再說吧