先看效果圖:
在我們畫這個(gè)控件之前浑吟,我們想想一下怎么實(shí)現(xiàn)這個(gè)奉件,顯示對(duì)星星的處理,我們是自己繪制還是使用圖片痴鳄?其實(shí)都是可以的辟狈,但是我們?yōu)榱烁涌焖俚耐瓿蛇@個(gè),我們選擇了使用圖片來(lái)完成夏跷。先自定義屬性,在這個(gè)例子中我定義如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RatingBar">
<!--未選中引用-->
<attr name="starNormal" format="reference" />
<!--選中引用-->
<attr name="starFocus" format="reference" />
<!--最大的分?jǐn)?shù)-->
<attr name="gradeNumber" format="integer" />
<!--當(dāng)前的分?jǐn)?shù)-->
<attr name="currentGrade" format="integer" />
<!--星星之間的間距-->
<attr name="statPadding" format="dimension" />
</declare-styleable>
</resources>
我們?cè)诔跏蓟臅r(shí)候獲取相關(guān)數(shù)據(jù):
private Bitmap mStarFocusBitmap, mStarNormalBitmap;
private int mGradeNumber;//最大分?jǐn)?shù)
private int mCurrentGrade;//當(dāng)前分?jǐn)?shù) 1開始的 最少為1分
private int mStarPadding;//間距
public RatingBar(Context context) {
this(context, null);
}
public RatingBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RatingBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RatingBar);
int starNormalId = array.getResourceId(R.styleable.RatingBar_starNormal, 0);
if (starNormalId == 0) {
throw new RuntimeException("請(qǐng)?jiān)O(shè)置屬性 starNormal ");
}
mStarNormalBitmap = BitmapFactory.decodeResource(getResources(), starNormalId);
int starFocusId = array.getResourceId(R.styleable.RatingBar_starFocus, 0);
if (starFocusId == 0) {
throw new RuntimeException("請(qǐng)?jiān)O(shè)置屬性 starFocus ");
}
mStarFocusBitmap = BitmapFactory.decodeResource(getResources(), starFocusId);
mGradeNumber = array.getInt(R.styleable.RatingBar_gradeNumber, 0);
mStarPadding = array.getDimensionPixelOffset(R.styleable.RatingBar_statPadding, DisplayUtil.dip2px(context, 5));
mCurrentGrade = array.getInt(R.styleable.RatingBar_currentGrade, 1);
array.recycle();
}
}
接下來(lái)我們?cè)O(shè)置寬高明未,本例中就直接寫了槽华,在實(shí)際開發(fā)中如有需要在修改
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 高度 一張圖片的高度
int height = mStarFocusBitmap.getHeight() + getPaddingTop() + getPaddingBottom();
int width = mStarFocusBitmap.getWidth() * mGradeNumber
+ getPaddingLeft() + getPaddingRight()
+ mStarPadding * (mGradeNumber - 1);
setMeasuredDimension(width, height);
}
接下來(lái)調(diào)用onDraw
函數(shù)繪制:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mGradeNumber; i++) {
if (i < mCurrentGrade) {
canvas.drawBitmap(mStarFocusBitmap,
getPaddingLeft() + i * mStarFocusBitmap.getWidth() + i * mStarPadding, getPaddingTop(), null);
} else {
canvas.drawBitmap(mStarNormalBitmap,
getPaddingLeft() + i * mStarFocusBitmap.getWidth() + i * mStarPadding, getPaddingTop(), null);
}
}
}
我們進(jìn)行測(cè)試:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.zzw.customview.view.RatingBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
app:currentGrade="2"
app:gradeNumber="5"
app:statPadding="10dp"
app:starFocus="@mipmap/star_selected"
app:starNormal="@mipmap/star_normal"
/>
</RelativeLayout>
效果圖如下:
這時(shí)候只是一個(gè)靜態(tài)的,點(diǎn)擊的時(shí)候并沒(méi)有變化趟妥,我們接下來(lái)進(jìn)行onTouchEvent
監(jiān)聽猫态,然后進(jìn)行改變值重繪:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
//計(jì)算下標(biāo)
int pos = (int) (x / (mStarFocusBitmap.getWidth() + mStarPadding));
if (pos < 0) pos = 0;
if (pos >= mGradeNumber) pos = mGradeNumber - 1;
mCurrentGrade = pos + 1;
invalidate();
break;
}
return true;
}
我們?yōu)槭裁匆?code>1呢?因?yàn)槲覀?code>mCurrentGrade表示的是評(píng)分的分?jǐn)?shù)披摄,所以用下標(biāo)加1
亲雪,這個(gè)時(shí)候我們效果就達(dá)到了,但是我們不要忘記優(yōu)化疚膊。
因?yàn)槲覀兎祷亓?code>true,所以在滑動(dòng)的時(shí)候一直接受到move
的事件义辕,就一直進(jìn)行調(diào)用了invalidate
重繪 ,我們上次也說(shuō)過(guò)invalidate
會(huì)做很多事情寓盗,所以我們就要減少重繪的此時(shí)灌砖,我們應(yīng)該加上判斷:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
//計(jì)算下標(biāo)
int pos = (int) (x / (mStarFocusBitmap.getWidth() + mStarPadding));
if (pos < 0) pos = 0;
if (pos >= mGradeNumber) pos = mGradeNumber - 1;
if (pos != mCurrentGrade - 1) {//分?jǐn)?shù)-1 等于 坐標(biāo)pos
mCurrentGrade = pos + 1;
invalidate();
}
break;
}
return true;
}
這樣的畫就優(yōu)雅了很多璧函,當(dāng)然也不要忘記bitmap
的回收,我們將暴露出函數(shù)來(lái)給使用者在activity
的onDestory
生命周期調(diào)用基显。
/**
* 回收
*/
public void recycle() {
if (mStarFocusBitmap != null)
mStarFocusBitmap.recycle();
if (mStarNormalBitmap != null)
mStarNormalBitmap.recycle();
mStarFocusBitmap=null;
mStarNormalBitmap=null;
}
在activity
里面調(diào)用:
@Override
protected void onDestroy() {
super.onDestroy();
mRatingBar.recycle();
}
這樣的話對(duì)于強(qiáng)迫癥患者心里就舒坦多了蘸吓。當(dāng)然,我也是一個(gè)強(qiáng)迫癥換患者撩幽。但是有時(shí)候我們也不能一定要追求極致库继,比如在獲取位置的時(shí)候,我有一個(gè)想法窜醉,就是根據(jù)星星的范圍精確的獲取當(dāng)前的位置,比如下面這樣:
當(dāng)時(shí)我就寫了一個(gè)小算法:
/**
* 計(jì)算位置
*
* @param x
* @return
*/
private int comPos(float x) {
int startX = getPaddingLeft();
if (x < startX) {
return 0;
}
if (x > getWidth() - getPaddingRight()) {
return mGradeNumber;
}
int pos = 0;
for (int i = 0; i < mGradeNumber; i++) {
int endX;
if (i == 0 || i == mGradeNumber - 1) {//第0個(gè)位置的后面的x
endX = startX + mStarFocusBitmap.getWidth() + mStarPadding / 2;
} else {
endX = startX + mStarFocusBitmap.getWidth() + mStarPadding;
}
if (x < endX) {
pos = i;
break;
}
startX = endX;
}
return pos;
}
這樣的畫就能夠準(zhǔn)確的拿到星星的位置宪萄,但是這樣的弊端就是一個(gè)for
循環(huán),將會(huì)耗費(fèi)更多的時(shí)間酱虎,所以最后還是改為了一個(gè)模糊的計(jì)算位置的方式雨膨。所以有時(shí)候也不能太強(qiáng)迫自己,不要鉆牛角尖读串。
這篇文章結(jié)束了聊记,希望對(duì)大家有所幫助!