从一个简洁的进度刻度绘制中了解自定义 View 的思路流程
先看效果(原諒我的渣像素),進度的刻度、寬度、顏色可以隨意設定:
項目github地址: https://github.com/zhangke301...
實現起來并不難,通過本文,我們可以學到:
1、自定義屬性的使用。
2、shader的使用
3、自定義View中對onmeasure的處理
4、增深對PathMeasure工具類的了解
5、最主要的是對自定義View有個比較清晰的思路認識
做這樣一個進度效果,我們可以拆分如下步驟來實現:
從外部圓環開始測量繪制;
再加入刻度條效果;
再加入刻度隨進度增加而增加效果;
增加自定義屬性增加可定制性;
控件使用方法介紹
OK,有了這個思路,那我們開始吧:
1、測量繪制外部圓環
首先我們要開始繪制外部的圓環,這步很簡單,主要是使用canvas的drawArc()方法,
/* * @param oval 畫弧線矩形區域 * @param startAngle 開始的角度* @param sweepAngle 劃過的角度 * @param useCenter 如果為true 為實心圓弧* @param paint 畫筆* /public void drawArc(RectF oval, float startAngle, float sweepAngle,boolean useCenter,Paint paint)這個相對簡單,主要是確定開始角度,并不斷增加繪制劃過角度,圓弧就出現在界面中了,這里需要注意的是RectF oval的大小確定:
在確定RectF oval之前,我們要先測量確定當前控件的寬高,根據當前控件的寬高來確定oval的合適大小。
測量當前控件的大小一般我們在onmeasure()方法中處理,resolveMeasured()方法傳遞兩個參數,第一各參數為widthMeasureSpec或者heightMeasureSpec,第二個參數為期望值也就是默認值。
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(resolveMeasured(widthMeasureSpec, nDesired), resolveMeasured(heightMeasureSpec, nDesired));}/*** * @param measureSpec * @param desired * @return*/private int resolveMeasured(int measureSpec, int desired) { int result = 0; int specSize = MeasureSpec.getSize(measureSpec); switch (MeasureSpec.getMode(measureSpec)) { case MeasureSpec.UNSPECIFIED: // result = desired; break; case MeasureSpec.AT_MOST: //wrap-contentresult = Math.min(specSize, desired); break; case MeasureSpec.EXACTLY: //match-contentdefault: result = specSize; } return result; }在測量之后我們就可以來求oval的具體大小 ,我把這步操作放在了onSizeChanged方法中,求出最小一邊的一半減去圓環的寬度,得到圓弧的半徑,然后根據半徑以控件中心為中心繪制我們所需要的矩形區域。
radiu = (int) ((Math.min(getWidth(), getHeight()))/2-mPaint.getStrokeWidth());oval.left = getWidth()/2-radiu;oval.top = getHeight()/2-radiu;oval.right = getWidth()/2+radiu;oval.bottom = getHeight()/2+radiu;這樣下來,基本上就可以繪制比較理想的弧線了,在這里也把繪制中心文字也說下吧,主要通過getTextBounds()方法獲取文字區域的寬高,然后在drawText()方法中將坐標進行適當偏移以使文字居中顯示。
String strProgressText = "";if(mOnProgressListener !=null){//如果不為空 則為接口返回的值strProgressText = mOnProgressListener.OnProgress(mMax, mProgress);}else{strProgressText = mProgress+"/"+mMax;}mTextPaint.getTextBounds(strProgressText, 0, strProgressText.length(), bounds);canvas.drawText(strProgressText, oval.centerX()-bounds.width()/2, oval.centerY()+bounds.height()/2, mTextPaint);最后還有一個小點就是漸變色的繪制,用的SweepGradient,我們可以看下Shader的子類,shader類是很強大的,類似與圓形圖片、漸變效果都可以用它來實現,這里就不過多展開了:
SweepGradient sweepGradient = new SweepGradient(getWidth()/2, getHeight()/2, colors, null);p.setShader(sweepGradient);
到這里為止,我們的圓環已經繪制好了,包括中間的文字以及圓環的漸變效果都已經實現了,就是這樣的:
2、加入刻度效果
接下來要加入刻度效果,實現思路是這樣的,我先默認實現兩個圓弧(注意這兩個圓弧只是我們假定添加的,并不是真正加在控件中顯示),然后獲取相同角度,根據相對位置獲取兩個圓環上的點進行連線,將這兩個點連起的刻度線封裝成對象添加在集合中,最后在onDraw方法中遍歷集合,進行繪制。
oval2 = new RectF();//內環oval2.left = getWidth()/2-radiu/4f*3;oval2.top = getHeight()/2-radiu/4f*3;oval2.right = getWidth()/2+radiu/4f*3;oval2.bottom = getHeight()/2+radiu/4f*3;oval3 = new RectF();//外環oval3.left = getWidth()/2-radiu/8f*7;oval3.top = getHeight()/2-radiu/8f*7;oval3.right = getWidth()/2+radiu/8f*7;oval3.bottom = getHeight()/2+radiu/8f*7;//然后初始化數據/*** 初始化數據*/private void initData() {mLinesList.clear();Path path = new Path();Path path1 = new Path();//從startAngle開始 繪制180角度path.addArc(oval2, mStartAngle, mGraduationSweepAngle);path1.addArc(oval3, mStartAngle, mGraduationSweepAngle);PathMeasure pm = new PathMeasure(path, false);float itemLength = pm.getLength()/(nGraduationCount-1);PathMeasure pm1 = new PathMeasure(path1, false);float[] pos = new float[2];float[] postemp = new float[2];for (int i = 0; i < nGraduationCount; i++) {pm.getPosTan(itemLength*i, pos , null );pm1.getPosTan(itemLength*i/pm.getLength()*pm1.getLength(), postemp , null);Line line = new Line();line.p1.x = pos[0];line.p1.y = pos[1];line.p2.x = postemp[0];line.p2.y = postemp[1];mLinesList.add(line);}}//ondraw方法: for (int i = 0; i < mLinesList.size(); i++) {Line line = mLinesList.get(i);canvas.drawLine(line.p1.x, line.p1.y, line.p2.x, line.p2.y, mRollPaint);}這里用到了PathMeasure這個輔助工具,這里簡單講一下:
public PathMeasure()//path:需要測量的path forceClosed:是否關閉path public PathMeasure(Path path, boolean forceClosed)//指定需要測量的path public void setPath(Path path, boolean forceClosed)//返回當前path的總長度。 getLength()//返回值是boolean,如過path為空,則返回false 傳入參數有三個: //distance:傳入距離起點的距離。 //pos[]:意思是position,分別對應點的x,y坐標 //tan[]:獲取切線值,不常用。 public boolean getPosTan(float distance, float pos[], float tan[])//返回一個處理好的matrix,但是這個matrix是以左上角作為旋轉點,所以需要將這個點移動到中心點。 其中一個參數flags,指這個martrix需要什么信息。flags的值有如下兩個 PathMeasure.POSITION_MATRIX_FLAG:位置信息 pathMeasure.TANGENT_MATRIX_FLAG:切邊信息,方位角信息 public boolean getMatrix(float distance, Matrix matrix, int flags)//這個方法返回boolean,如果截取的長度為0則返回false,否則為true。參數如下 //startD:起始距離 //stopD:終點距離 //dst:接收截取的path //startWithMoveTo:是否把截取的path,moveto到起始點。 public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)3、加入刻度隨進度增加而增加效果,并增加進度變化回調方便操作
加入刻度隨進度增加而增加,我們可以這樣想,首先我總的刻度數是一定的,判斷劃過的角度占圓周的百分比,隨之就可以得到劃過刻度數占總刻度數的百分比,進而就求出劃過的刻度數了。
for (int i = 0; i < Math.round(mSweepAngle*nGraduationCount/360f); i++) {if(i<mLinesList.size()){Line line = mLinesList.get(i);canvas.drawLine(line.p1.x, line.p1.y, line.p2.x, line.p2.y, mRollDrawPaint);}}將劃過的刻度數用畫筆再繪制一次,隨進度增加的刻度效果就出現啦!
/*** 設置進度監聽* @param mOnProgressListener*/public void setOnProgressListener(OnProgressListener mOnProgressListener) {this.mOnProgressListener = mOnProgressListener;}/*** 用于外部判斷當前進度狀態*/interface OnProgressListener{/*** 返回中間部分文字內容* @param max* @param progress* @return */String OnProgress(int max,int progress);}設置回調監聽,這樣在每次進度變化的時候,可以隨意變化中間部分文字顯示的內容。
4、增加自定義屬性增加可定制性
attrs.xml:
<declare-styleable name="LoadingStyle"><attr name="textSize" format="dimension|reference"/><!-- 字體大小 --><attr name="textColor" format="color|reference"/><!-- 字體顏色 --><attr name="strokeWidth" format="dimension|reference"/><!-- 圓環大小 --><attr name="isShowGraduationBackground" format="boolean"/><!-- 是否顯示背景刻度 --><attr name="isShowOutRoll" format="boolean"/><!-- 是否顯示外部進度框 --><attr name="startAngle" format="integer|reference"/><!-- 開始的角度 --><attr name="max" format="integer|reference"/><!-- 最大值 --><attr name="progress" format="integer|reference"/><!-- 默認進度值 --><attr name="graduationBackgroundColor" format="color|reference"/><!-- 刻度的背景顏色 --><attr name="graduationWidth" format="dimension|reference"/><!-- 刻度的寬度 --><attr name="graduationCount" format="integer|reference"/><!-- 刻度的個數 --></declare-styleable>layout文件:
xmlns:app="http://schemas.android.com/apk/res-auto"<!--設置命名空間 --><com.mrzk.circleloadinglibrary.CircleLoadingViewandroid:id="@+id/lv_loading"android:layout_width="250dp"android:layout_height="250dp"android:layout_centerInParent="true" app:textSize="35sp"app:textColor="#f60"app:strokeWidth="10dp"app:isShowGraduationBackground="true"app:startAngle="0"app:max="300"app:progress="100"app:graduationBackgroundColor="#ccc"app:graduationWidth="5dp"app:graduationCount="10"app:isShowOutRoll="false"/>獲取自定義屬性值:
TypedArray typedArray = getResources().obtainAttributes(attrs, R.styleable.LoadingStyle);mTextSize = (int) typedArray.getDimension(R.styleable.LoadingStyle_textSize, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));mStrokeWidth = (int) typedArray.getDimension(R.styleable.LoadingStyle_strokeWidth, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics()));mGraduationWidth = (int) typedArray.getDimension(R.styleable.LoadingStyle_graduationWidth, mStrokeWidth/2);mTextColor = (int) typedArray.getColor(R.styleable.LoadingStyle_textColor, Color.BLACK);mGraduationBackgroundColor = (int) typedArray.getColor(R.styleable.LoadingStyle_graduationBackgroundColor, Color.BLACK);mStartAngle = (int) typedArray.getInt(R.styleable.LoadingStyle_startAngle, 180);mMax = (int) typedArray.getInt(R.styleable.LoadingStyle_max, 0);mProgress = (int) typedArray.getInt(R.styleable.LoadingStyle_progress, 0);nGraduationCount = (int) typedArray.getInt(R.styleable.LoadingStyle_graduationCount, 35);isShowGraduationBackground = typedArray.getBoolean(R.styleable.LoadingStyle_isShowGraduationBackground, true);isShowOutRoll = typedArray.getBoolean(R.styleable.LoadingStyle_isShowOutRoll, true);typedArray.recycle();5、使用方法
int[] colors = {0xFFE5BD7D, 0xFFFAAA64,0xFFFFFFFF, 0xFF6AE2FD,0xFF8CD0E5, 0xFFA3CBCB,0xFFBDC7B3, 0xFFD1C299, 0xFFE5BD7D};lv_loading.setTextColor(Color.BLACK);//設置中心文字顏色lv_loading.setMax(500);//設置最大進度lv_loading.setShowGraduationBackgroundEnable(true);//是否顯示刻度背景lv_loading.setGraduationBackgroundColor(Color.GRAY);//刻度的背景顏色lv_loading.setStartAngle(180);//設置開始旋轉角度lv_loading.setGraduationCount(10);//設置刻度數lv_loading.setGraduationWidth(5);//設置刻度的寬度lv_loading.setOutColors(colors);//設置外部圓環顏色lv_loading.setInnerGraduationColors(colors);//設置內部刻度進度顏色lv_loading.setTextSize(35);//設置內部文字字體大小lv_loading.setShowOutRollEnable(false);//設置是否顯示外部進度框lv_loading.setOnProgressListener(new OnProgressListener() {@Overridepublic String OnProgress(int max, int progress) {return progress*100f/max+"%";}});項目github地址: https://github.com/zhangke301...
總結
以上是生活随笔為你收集整理的从一个简洁的进度刻度绘制中了解自定义 View 的思路流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows学习总结(6)——Mind
- 下一篇: 上古卷轴5负重代码怎么使用