Android 自定義控制元件-星級評分
在學習自定義控制元件時需要一些例子來練練手,本文這個控制元件就是在這種環境下產生的(可能有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;
}
由於本人水平有限,文筆也比較糙,不喜勿噴。