在學習自定義控件時需要一些例子來練練手,本文這個控件就是在這種環境下產生的(可能有BUG);
這個控件設計的特點:
1,可以任意修改星星數量
2,可以星星大小會隨控件大小而縮小,在控件足夠大的情況可以任意設置星星大小
3,滑動監聽,根據滑動距離選擇星級
4,可以設置星星之間的間距和左右間距
第一步:
初始化星星圖片,隨便設置星星的默認寬高
private void init() {
mPaint = new Paint();
star = BitmapFactory.decodeResource(getResources(), R.drawable.icon_evaluate_star);
starPressed = BitmapFactory.decodeResource(getResources(), R.drawable.icon_evaluate_star_pressed);
starWidth = star.getWidth();
starHeight = star.getHeight();
}
第二步:
重寫onMeasure方法,在這裏說一下onMeasure方法的兩個參數:
widthMeasureSpec和heightMeasureSpec:分別 代表了View寬高的:大小模式和大小數值
一個int 類型怎麼能代表兩個東西呢, 系統時這樣規定的,採用最高兩位表示模式,如下圖:
最高位00表示:MeasureSpec.UNSPECIFIED : 表示在XML 中使用wrap_centent
最高位01表示:MeasureSpec.EXACTLY: 表示在XML 中使用 xxdp
最高位11表示:MeasureSpec.AT_MOST:表示在XML 中使用 match_parent
然後代碼中的邏輯: 計算使用默認值時需要的實際寬高,在判斷控件是否指定寬高 是的再判斷是否大於實際需要寬高 小於就按比例縮小,大於就按居中顯示 把多餘的寬高都加左右/上下間距裏具體代碼,代碼備註的已經很詳細了
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
//
// 實際所需要的寬 = 星星的寬 * 星星數量 + 星星之間的間距 * 間距數 + 左右間距
float totalWidthSpacing = (starCount - 1) * spacing + leftSpacing + rightSpacing; // 總的間距
float width = starWidth * starCount + totalWidthSpacing;
switch (widthMode) {
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
// 當實際所需的寬 大於控件所設定的寬時 應該按比例縮小實際所需要寬來滿足控件所給寬
if (width > mWidth) {
// 計算比例
float scale = mWidth / width;
starWidth = starWidth * scale;
spacing = spacing * scale;
leftSpacing = leftSpacing * scale;
rightSpacing = rightSpacing * scale;
} else {
// 如果實際所需寬小於 控件所給寬 那就加大左右間距 儘量保持居中效果
float diff = width - mWidth;
leftSpacing = leftSpacing + diff / 2;
rightSpacing = rightSpacing + diff / 2;
}
// 重新計算
totalWidthSpacing = (starCount - 1) * spacing + leftSpacing + rightSpacing; // 總的間距
width = starWidth * starCount + totalWidthSpacing;
mWidth = (int) (width + totalWidthSpacing);
break;
case MeasureSpec.UNSPECIFIED:
// 未指定的情況下 我就安實際所需寬高來 做控件寬高
mWidth = (int) width;
break;
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
// 實際所需高
float height = starHeight + topSpacing + bottomSpacing;
switch (heightMode) {
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
// 當控件指定高時 儘可能滿足指定的高
if (height > mHeight) {
// 當實際所需高大於指定高時 按比例縮小實際所需高
float scale = mHeight / height;
starHeight = starHeight * scale;
topSpacing = topSpacing * scale;
bottomSpacing = bottomSpacing * scale;
} else {
// 實際所需高小於指定高時 將多餘的都加到 上下間距
float diff = mHeight - height;
topSpacing = topSpacing + diff / 2;
bottomSpacing = bottomSpacing + diff / 2;
}
// 重新計算高
mHeight = (int) (starHeight + topSpacing + bottomSpacing);
break;
case MeasureSpec.UNSPECIFIED:
// 未指定的情況下 我就安實際所需寬高來 做控件寬高
mHeight = (int) height;
break;
}
// 設置寬高
setMeasuredDimension(mWidth, mHeight);
}
第三步 畫星星 在上面我已經初始化星星的Bitmap了
重寫onDraw 方法 有starCount 來決定畫星星的數量 再由星級來決定畫什麼樣的星星。
再計算星星該畫在什麼位置 計算方式都在代碼裏裏 也有詳細的備註
這個主要說明一下 canvas.drawBitmap(bitmap, src, dst , mPaint); 這方法
第一個參數: 表示需要畫的圖
第二個參數:表示圖片需要繪製的區域,可以參數可以爲空, 表示繪製這張圖片
第三個參數:表示圖片應該被繪製在畫布的什麼區域,不能爲空
第四個蠶食:畫筆, 可以爲空。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < starCount; i++) {
Bitmap bitmap = star;
if (i < level) {
bitmap = starPressed;
}
// 表示圖片需要繪製區域
Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
// 表示圖片應該被繪製在的區域
RectF dst = new RectF();
dst.top = topSpacing ;
dst.left = leftSpacing + (starWidth + spacing) * i;
dst.right = dst.left + starWidth;
dst.bottom = dst.top + starWidth;
canvas.drawBitmap(bitmap, src, dst, mPaint);
}
}
第四步 重寫onTouchEvent() 方法
這個說明一下 當我們手指觸摸屏幕時有三種情況:
手指按下:MotionEvent.ACTION_DOWN.
手指滑動:MotionEvent.ACTION_MOVE.
手指帶起:MotionEvent.ACTION_UP.
這就是我們點擊屏幕時三種動作。
在這裏我先劃分點擊有效區域,在有效距離內再根據x值除於星星的寬加星星之間的間距來知道點擊了那個星星
最後再加個判斷只有當星級發生改變的時候才重繪控件。因爲在onTouchEvent方法執行次數太多,避免沒必要的重繪
@Override
public boolean onTouchEvent(MotionEvent event) {
int oldLevel = level;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: // 手指按下
break;
case MotionEvent.ACTION_MOVE: // 手指滑動
float x = event.getX();
float y = event.getY();
// 當點擊區域在 星星所在區域時 才點擊有效
if (y > topSpacing && y < topSpacing + starHeight) {
// 根據點擊位置確實星級
if (x < leftSpacing ) {
// 小於左邊距 表示沒有點到一個星星
level = 0;
} else {
// 只要左邊距肯定已經點到星星了 除於星星寬個間距即知道點擊了那個星星
level = (int) ((x - leftSpacing) / (starWidth + spacing)) + 1;
Log.e("AAA—>", "onTouchEvent: " + level );
if (oldLevel != level) {
// 只有當星級發生改變時纔去刷新佈局 不做沒必要刷新
if (onLevelChangeListener != null) {
onLevelChangeListener.levelChange(level);
}
postInvalidate();
}
}
}
break;
case MotionEvent.ACTION_UP: // 手指擡起
break;
}
return true;
}
由於本人水平有限,文筆也比較糙,不喜勿噴。