<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在前面的學習中,我們基本瞭解了一些 Canvas 的繪製,那麼這一章我們一起復習一下圖片的繪製幾種方式,和事件的簡單互動方式。
我們從易到難,作為基礎的進階控制元件,我們從最簡單的互動開始,那就自定義一個星星評分的控制元件吧。
一個 App 必不可少的評論系統打分的控制元件,可以展示評分,可以點選評分,可以滑動評分。它的實現總體上可以分為以下的步驟:
思路我們已經有了,下面一步一步的來實現吧。
話不多說,Let's go
我們需要繪製幾個星星,那麼我們必須要設定的幾個屬性:
當前的評分值,總共有幾個星星,每一個星星的間距和大小,選中和未選中的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,和自定義圓角容器的文章,裡面都有用到過,主要是方便一些圖片的裁剪和縮放等。
這裡並沒有涉及到什麼事件巢狀,攔截之類的複雜處理,只需要處理自身的 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 值來得到對應的動畫效果:
到此效果的實現算是結束了,但是我們還有一些收尾工作沒做,如何監聽進度的回撥,如何控制整數與浮點數的顯示,是否支援觸控等等。然後對其做一些自定義屬性的抽取,就可以在應用中比較廣泛的使用了。
自定義屬性:
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星星評分內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45