Android自定義View實現星星評分效果
前言
在前面的學習中,我們基本瞭解瞭一些 Canvas 的繪制,那麼這一章我們一起復習一下圖片的繪制幾種方式,和事件的簡單交互方式。
我們從易到難,作為基礎的進階控件,我們從最簡單的交互開始,那就自定義一個星星評分的控件吧。
一個 App 必不可少的評論系統打分的控件,可以展示評分,可以點擊評分,可以滑動評分。它的實現總體上可以分為以下的步驟:
- 強制測量大小為我們指定的大小
- 先繪制Drawable未評分的圖片
- 在繪制Bitmap已評分的圖片
- 在onTouch中點擊和移動的事件中動態計算當前的評分,進而刷新佈局
- 回調的處理與屬性的抽取
思路我們已經有瞭,下面一步一步的來實現吧。
話不多說,Let's go
1、測量與圖片的繪制
我們需要繪制幾個星星,那麼我們必須要設置的幾個屬性:
當前的評分值,總共有幾個星星,每一個星星的間距和大小,選中和未選中的Drawable圖片:
private int mStarDistance = 0; private int mStarCount = 5; private int mStarSize = 20; //每一個星星的寬度和高度是一致的 private float mScoreNum = 0.0F; //當前的評分值 private Drawable mStarScoredDrawable; //已經評分的星星圖片 private Drawable mStarUnscoredDrawable; //還未評分的星星圖片 private void init(Context context, AttributeSet attrs) { mScoreNum = 2.1f; mStarSize = context.getResources().getDimensionPixelSize(R.dimen.d_20dp); mStarDistance = context.getResources().getDimensionPixelSize(R.dimen.d_5dp); mStarScoredDrawable = context.getResources().getDrawable(R.drawable.iv_normal_star_yellow); mStarUnscoredDrawable = context.getResources().getDrawable(R.drawable.iv_normal_star_gray); }
測量佈局的時候,我們就不能根據xml設置的 match_parent 或 wrap_content 來設置寬高,我們需要根據星星的大小與間距來動態的計算,所以不管xml中如何設置,我們都強制性的使用我們自己的測量。
星星的數量 * 星星的寬度再加上中間的間距 * 數量-1,就是我們的控件寬度,控件高度則是星星的高度。
具體的確定測量我們再上一篇已經詳細的復習過瞭,這裡直接貼代碼:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(mStarSize * mStarCount + mStarDistance * (mStarCount - 1), mStarSize); }
這樣就可以得到對應的測量寬高 (加一個背景方便看效果):
如何繪制星星?直接繪制Drawable即可,默認的Drawable的繪制為:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (int i = 0; i < mStarCount; i++) { mStarUnscoredDrawable.setBounds((mStarDistance + mStarSize) * i, 0, (mStarDistance + mStarSize) * i + mStarSize, mStarSize); mStarUnscoredDrawable.draw(canvas); } }
如果有5個星星圖片,那麼就為每一個星星定好位置:
那麼已經選中的圖片也需要使用這種方法繪制嗎?
計算當前的評分,然後計算計算需要繪制多少星星,那麼就是這樣做:
int score = (int) Math.ceil(mScoreNum); for (int i = 0; i < score; i++) { mStarScoredDrawable.setBounds((mStarDistance + mStarSize) * i, 0, (mStarDistance + mStarSize) * i + mStarSize, mStarSize); mStarScoredDrawable.draw(canvas); }
可是這麼做不符合我們的要求啊 ,我們是需要是可以顯示評分為2.5之類值,那麼我們怎麼能繪制半顆星呢?Drawable.draw(canvas) 的方式滿足不瞭,那我們可以使用 BitmapShader 的方式來繪制。
初始化一個 BitmapShader 設置給 Paint 畫筆,通過畫筆就可以畫出對應的形狀。
比如此時的場景,我們如果想隻畫0.5個星星,那麼我們就可以
paint = new Paint(); paint.setAntiAlias(true); paint.setShader(new BitmapShader(drawableToBitmap(mStarScoredDrawable), BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP)); @Override protected void onDraw(Canvas canvas) { for (int i = 0; i < mStarCount; i++) { mStarUnscoredDrawable.setBounds((mStarDistance + mStarSize) * i, 0, (mStarDistance + mStarSize) * i + mStarSize, mStarSize); mStarUnscoredDrawable.draw(canvas); } canvas.drawRect(0, 0, mStarSize * mScoreNum, mStarSize, paint); }
那麼如果是大於一個星星之後的小數點就可以用公式計算
if (mScoreNum > 1) { canvas.drawRect(0, 0, mStarSize, mStarSize, paint); if (mScoreNum - (int) (mScoreNum) == 0) { //如果評分是3.0之類的整數,那麼直接按正常的rect繪制 for (int i = 1; i < mScoreNum; i++) { canvas.translate(mStarDistance + mStarSize, 0); canvas.drawRect(0, 0, mStarSize, mStarSize, paint); } } else { //如果是小數例如3.5,先繪制之前的3個,再繪制後面的0.5 for (int i = 1; i < mScoreNum - 1; i++) { canvas.translate(mStarDistance + mStarSize, 0); canvas.drawRect(0, 0, mStarSize, mStarSize, paint); } canvas.translate(mStarDistance + mStarSize, 0); canvas.drawRect(0, 0, mStarSize * (Math.round((mScoreNum - (int) (mScoreNum)) * 10) * 1.0f / 10), mStarSize, paint); } } else { canvas.drawRect(0, 0, mStarSize * mScoreNum, mStarSize, paint); }
效果:
關於 BitmapShader 的其他用法,可以翻看我之前的自定義圓角圓形View,和自定義圓角容器的文章,裡面都有用到過,主要是方便一些圖片的裁剪和縮放等。
2、事件的交互與計算
這裡並沒有涉及到什麼事件嵌套,攔截之類的復雜處理,隻需要處理自身的 onTouch 即可。而我們需要處理的就是按下的時候和移動的時候評分值的變化。
在onDraw方法中,我們使用 mScoreNum 變量來繪制的已評分的 Bitmap 繪制。所以這裡我們隻需要在 onTouch 中計算出對應的 mScoreNum 值,讓其重繪即可。
@Override public boolean onTouchEvent(MotionEvent event) { //x軸的寬度做一下最大最小的限制 int x = (int) event.getX(); if (x < 0) { x = 0; } if (x > mMeasuredWidth) { x = mMeasuredWidth; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: { mScoreNum = x * 1.0f / (mMeasuredWidth * 1.0f / mStarCount); invalidate(); break; } case MotionEvent.ACTION_UP: { break; } } return super.onTouchEvent(event); }
計算出一顆星的長度,然後計算當前x軸的長度,就可以計算出當前有幾顆星,我們默認處理的是 float 類型。就可以根據計算出的 mScoreNum 值來得到對應的動畫效果:
3. 回調處理與自定義屬性抽取
到此效果的實現算是結束瞭,但是我們還有一些收尾工作沒做,如何監聽進度的回調,如何控制整數與浮點數的顯示,是否支持觸摸等等。然後對其做一些自定義屬性的抽取,就可以在應用中比較廣泛的使用瞭。
自定義屬性:
private int mStarDistance = 5; private int mStarCount = 5; private int mStarSize = 20; //每一個星星的寬度和高度是一致的 private float mScoreNum = 0.0F; //當前的評分值 private Drawable mStarScoredDrawable; //已經評分的星星圖片 private Drawable mStarUnscoredDrawable; //還未評分的星星圖片 private boolean isOnlyIntegerScore = false; //默認顯示小數類型 private boolean isCanTouch = true; //默認支持控件的點擊 private OnStarChangeListener onStarChangeListener;
自定義屬性的賦值與初始化操作:
private void init(Context context, AttributeSet attrs) { setClickable(true); TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.StarScoreView); this.mStarDistance = mTypedArray.getDimensionPixelSize(R.styleable.StarScoreView_starDistance, 0); this.mStarSize = mTypedArray.getDimensionPixelSize(R.styleable.StarScoreView_starSize, 20); this.mStarCount = mTypedArray.getInteger(R.styleable.StarScoreView_starCount, 5); this.mStarUnscoredDrawable = mTypedArray.getDrawable(R.styleable.StarScoreView_starUnscoredDrawable); this.mStarScoredDrawable = mTypedArray.getDrawable(R.styleable.StarScoreView_starScoredDrawable); this.isOnlyIntegerScore = mTypedArray.getBoolean(R.styleable.StarScoreView_starIsTouchEnable, true); this.isOnlyIntegerScore = mTypedArray.getBoolean(R.styleable.StarScoreView_starIsOnlyIntegerScore, false); mTypedArray.recycle(); paint = new Paint(); paint.setAntiAlias(true); paint.setShader(new BitmapShader(drawableToBitmap(mStarScoredDrawable), BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP)); }
自定義屬性的定義xml文件:
<!-- 評分星星控件 --> <declare-styleable name="StarScoreView"> <!--星星間距--> <attr name="starDistance" format="dimension" /> <!--星星大小--> <attr name="starSize" format="dimension" /> <!--星星個數--> <attr name="starCount" format="integer" /> <!--星星已評分圖片--> <attr name="starScoredDrawable" format="reference" /> <!--星星未評分圖片--> <attr name="starUnscoredDrawable" format="reference" /> <!--是否可以點擊--> <attr name="starIsTouchEnable" format="boolean" /> <!--是否顯示整數--> <attr name="starIsOnlyIntegerScore" format="boolean" /> </declare-styleable>
在OnTouch的時候就可以判斷是否能觸摸
@Override public boolean onTouchEvent(MotionEvent event) { if (isCanTouch) { //x軸的寬度做一下最大最小的限制 int x = (int) event.getX(); if (x < 0) { x = 0; } if (x > mMeasuredWidth) { x = mMeasuredWidth; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: { setStarMark(x * 1.0f / (getMeasuredWidth() * 1.0f / mStarCount)); break; } case MotionEvent.ACTION_UP: { break; } } return super.onTouchEvent(event); } else { //如果設置不能點擊,直接不觸發事件 return false; } }
而 setStarMark 則是設置入口的方法,內部判斷是否支持小數點和設置對於的監聽,並調用重繪。
public void setStarMark(float mark) { if (isOnlyIntegerScore) { mScoreNum = (int) Math.ceil(mark); } else { mScoreNum = Math.round(mark * 10) * 1.0f / 10; } if (this.onStarChangeListener != null) { this.onStarChangeListener.onStarChange(mScoreNum); //調用監聽接口 } invalidate(); }
一個簡單的圖片繪制和事件觸摸的控件就完成啦,使用起來也是超級方便。
<com.guadou.kt_demo.demo.demo18_customview.star.StarScoreView android:id="@+id/star_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="@dimen/d_40dp" android:background="#f1f1f1" app:starCount="5" app:starDistance="@dimen/d_5dp" app:starIsOnlyIntegerScore="false" app:starIsTouchEnable="true" app:starScoredDrawable="@drawable/iv_normal_star_yellow" app:starSize="@dimen/d_35dp" app:starUnscoredDrawable="@drawable/iv_normal_star_gray" />
Activity中可以設置評分和設置監聽:
override fun init() { val starView = findViewById<StarScoreView>(R.id.star_view) starView.setOnStarChangeListener { YYLogUtils.w("當前選中的Star:$it") } findViewById<View>(R.id.set_progress).click { starView.setStarMark(3.5f) } }
效果:
後記
整個流程走下來是不是很簡單呢,此控件不止用於星星類型的評分,任何圖片資源都可以使用,現在我們思路打開擴展一下,相似的場景和效果我們可以實現一些圖片進度,觸摸進度條,圓環的SeekBar,等等類似的控制都是相似的思路。
到此這篇關於Android自定義View實現星星評分效果的文章就介紹到這瞭,更多相關Android自定義View星星評分內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Android自定義view仿QQ的Tab按鈕動畫效果(示例代碼)
- Android自定義view貝塞爾曲線
- Android自定義view實現圓環進度條效果
- Android實現微信朋友圈圖片和視頻播放
- Android自定義View仿大眾點評星星評分控件