先看效果(原諒我的渣像素),進(jìn)度的刻度、寬度圆到、顏色可以隨意設(shè)定:
【項(xiàng)目github地址: https://github.com/zhangke3016/CircleLoading]
實(shí)現(xiàn)起來(lái)并不難怎抛,通過(guò)本文,我們可以學(xué)到:
1芽淡、自定義屬性的使用马绝。
2、shader的使用
3挣菲、自定義View中對(duì)onmeasure的處理
4富稻、增深對(duì)PathMeasure工具類(lèi)的了解
5、最主要的是對(duì)自定義View有個(gè)比較清晰的思路認(rèn)識(shí)
一白胀、原理介紹
做這樣一個(gè)進(jìn)度效果椭赋,我們可以拆分如下步驟來(lái)實(shí)現(xiàn):
1、從外部圓環(huán)開(kāi)始測(cè)量繪制哪怔;
2、再加入刻度條效果向抢;
3认境、再加入刻度隨進(jìn)度增加而增加效果;
4挟鸠、增加自定義屬性增加可定制性叉信;
5、控件使用方法介紹
csdn地址:http://blog.csdn.net/zhangke3016/article/details/52035641
OK,有了這個(gè)思路艘希,那我們開(kāi)始吧:
1硼身、測(cè)量繪制外部圓環(huán)
首先我們要開(kāi)始繪制外部的圓環(huán),這步很簡(jiǎn)單枢冤,主要是使用canvas的drawArc()方法鸠姨,
/*
* @param oval 畫(huà)弧線矩形區(qū)域
* @param startAngle 開(kāi)始的角度
* @param sweepAngle 劃過(guò)的角度
* @param useCenter 如果為true 為實(shí)心圓弧
* @param paint 畫(huà)筆
* /
public void drawArc(RectF oval, float startAngle, float sweepAngle,boolean useCenter,Paint paint)
這個(gè)相對(duì)簡(jiǎn)單,主要是確定開(kāi)始角度淹真,并不斷增加繪制劃過(guò)角度,圓弧就出現(xiàn)在界面中了连茧,這里需要注意的是RectF oval的大小確定:
在確定RectF oval之前核蘸,我們要先測(cè)量確定當(dāng)前控件的寬高,根據(jù)當(dāng)前控件的寬高來(lái)確定oval的合適大小啸驯。
測(cè)量當(dāng)前控件的大小一般我們?cè)趏nmeasure()方法中處理客扎,resolveMeasured()方法傳遞兩個(gè)參數(shù),第一各參數(shù)為widthMeasureSpec或者h(yuǎn)eightMeasureSpec罚斗,第二個(gè)參數(shù)為期望值也就是默認(rèn)值徙鱼。
@Override
protected 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-content
result = Math.min(specSize, desired);
break;
case MeasureSpec.EXACTLY: //match-content
default:
result = specSize;
}
return result;
}
在測(cè)量之后我們就可以來(lái)求oval的具體大小 ,我把這步操作放在了onSizeChanged方法中,求出最小一邊的一半減去圓環(huán)的寬度袱吆,得到圓弧的半徑厌衙,然后根據(jù)半徑以控件中心為中心繪制我們所需要的矩形區(qū)域。
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;
這樣下來(lái)绞绒,基本上就可以繪制比較理想的弧線了婶希,在這里也把繪制中心文字也說(shuō)下吧,主要通過(guò)getTextBounds()方法獲取文字區(qū)域的寬高蓬衡,然后在drawText()方法中將坐標(biāo)進(jìn)行適當(dāng)偏移以使文字居中顯示喻杈。
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);
最后還有一個(gè)小點(diǎn)就是漸變色的繪制,用的SweepGradient狰晚,我們可以看下Shader的子類(lèi),shader類(lèi)是很強(qiáng)大的筒饰,類(lèi)似與圓形圖片、漸變效果都可以用它來(lái)實(shí)現(xiàn)壁晒,這里就不過(guò)多展開(kāi)了:
SweepGradient sweepGradient = new SweepGradient(getWidth()/2, getHeight()/2, colors, null);
p.setShader(sweepGradient);
到這里為止龄砰,我們的圓環(huán)已經(jīng)繪制好了,包括中間的文字以及圓環(huán)的漸變效果都已經(jīng)實(shí)現(xiàn)了讨衣,就是這樣的:
2换棚、加入刻度效果
接下來(lái)要加入刻度效果,實(shí)現(xiàn)思路是這樣的反镇,我先默認(rèn)實(shí)現(xiàn)兩個(gè)圓弧(注意這兩個(gè)圓弧只是我們假定添加的固蚤,并不是真正加在控件中顯示),然后獲取相同角度歹茶,根據(jù)相對(duì)位置獲取兩個(gè)圓環(huán)上的點(diǎn)進(jìn)行連線夕玩,將這兩個(gè)點(diǎn)連起的刻度線封裝成對(duì)象添加在集合中,最后在onDraw方法中遍歷集合惊豺,進(jìn)行繪制燎孟。
oval2 = new RectF();//內(nèi)環(huán)
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();//外環(huán)
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;
//然后初始化數(shù)據(jù)
/**
* 初始化數(shù)據(jù)
*/
private void initData() {
mLinesList.clear();
Path path = new Path();
Path path1 = new Path();
//從startAngle開(kāi)始 繪制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這個(gè)輔助工具,這里簡(jiǎn)單講一下:
public PathMeasure()
//path:需要測(cè)量的path forceClosed:是否關(guān)閉path
public PathMeasure(Path path, boolean forceClosed)
//指定需要測(cè)量的path
public void setPath(Path path, boolean forceClosed)
//返回當(dāng)前path的總長(zhǎng)度尸昧。
getLength()
//返回值是boolean揩页,如過(guò)path為空,則返回false 傳入?yún)?shù)有三個(gè):
//distance:傳入距離起點(diǎn)的距離烹俗。
//pos[]:意思是position爆侣,分別對(duì)應(yīng)點(diǎn)的x,y坐標(biāo)
//tan[]:獲取切線值幢妄,不常用兔仰。
public boolean getPosTan(float distance, float pos[], float tan[])
//返回一個(gè)處理好的matrix,但是這個(gè)matrix是以左上角作為旋轉(zhuǎn)點(diǎn)蕉鸳,所以需要將這個(gè)點(diǎn)移動(dòng)到中心點(diǎn)乎赴。 其中一個(gè)參數(shù)flags,指這個(gè)martrix需要什么信息。flags的值有如下兩個(gè)
PathMeasure.POSITION_MATRIX_FLAG:位置信息
pathMeasure.TANGENT_MATRIX_FLAG:切邊信息榕吼,方位角信息
public boolean getMatrix(float distance, Matrix matrix, int flags)
//這個(gè)方法返回boolean饿序,如果截取的長(zhǎng)度為0則返回false,否則為true友题。參數(shù)如下
//startD:起始距離
//stopD:終點(diǎn)距離
//dst:接收截取的path
//startWithMoveTo:是否把截取的path嗤堰,moveto到起始點(diǎn)。
public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
3度宦、加入刻度隨進(jìn)度增加而增加效果,并增加進(jìn)度變化回調(diào)方便操作
加入刻度隨進(jìn)度增加而增加踢匣,我們可以這樣想,首先我總的刻度數(shù)是一定的戈抄,判斷劃過(guò)的角度占圓周的百分比离唬,隨之就可以得到劃過(guò)刻度數(shù)占總刻度數(shù)的百分比,進(jìn)而就求出劃過(guò)的刻度數(shù)了划鸽。
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);
}
}
將劃過(guò)的刻度數(shù)用畫(huà)筆再繪制一次输莺,隨進(jìn)度增加的刻度效果就出現(xiàn)啦!
/**
* 設(shè)置進(jìn)度監(jiān)聽(tīng)
* @param mOnProgressListener
*/
public void setOnProgressListener(OnProgressListener mOnProgressListener) {
this.mOnProgressListener = mOnProgressListener;
}
/**
* 用于外部判斷當(dāng)前進(jìn)度狀態(tài)
*/
interface OnProgressListener{
/**
* 返回中間部分文字內(nèi)容
* @param max
* @param progress
* @return
*/
String OnProgress(int max,int progress);
}
設(shè)置回調(diào)監(jiān)聽(tīng)裸诽,這樣在每次進(jìn)度變化的時(shí)候嫂用,可以隨意變化中間部分文字顯示的內(nèi)容。
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"/><!-- 圓環(huán)大小 -->
<attr name="isShowGraduationBackground" format="boolean"/><!-- 是否顯示背景刻度 -->
<attr name="isShowOutRoll" format="boolean"/><!-- 是否顯示外部進(jìn)度框 -->
<attr name="startAngle" format="integer|reference"/><!-- 開(kāi)始的角度 -->
<attr name="max" format="integer|reference"/><!-- 最大值 -->
<attr name="progress" format="integer|reference"/><!-- 默認(rèn)進(jìn)度值 -->
<attr name="graduationBackgroundColor" format="color|reference"/><!-- 刻度的背景顏色 -->
<attr name="graduationWidth" format="dimension|reference"/><!-- 刻度的寬度 -->
<attr name="graduationCount" format="integer|reference"/><!-- 刻度的個(gè)數(shù) -->
</declare-styleable>
layout文件:
xmlns:app="http://schemas.android.com/apk/res-auto"<!--設(shè)置命名空間 -->
<com.mrzk.circleloadinglibrary.CircleLoadingView
android: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);//設(shè)置中心文字顏色
lv_loading.setMax(500);//設(shè)置最大進(jìn)度
lv_loading.setShowGraduationBackgroundEnable(true);//是否顯示刻度背景
lv_loading.setGraduationBackgroundColor(Color.GRAY);//刻度的背景顏色
lv_loading.setStartAngle(180);//設(shè)置開(kāi)始旋轉(zhuǎn)角度
lv_loading.setGraduationCount(10);//設(shè)置刻度數(shù)
lv_loading.setGraduationWidth(5);//設(shè)置刻度的寬度
lv_loading.setOutColors(colors);//設(shè)置外部圓環(huán)顏色
lv_loading.setInnerGraduationColors(colors);//設(shè)置內(nèi)部刻度進(jìn)度顏色
lv_loading.setTextSize(35);//設(shè)置內(nèi)部文字字體大小
lv_loading.setShowOutRollEnable(false);//設(shè)置是否顯示外部進(jìn)度框
lv_loading.setOnProgressListener(new OnProgressListener() {
@Override
public String OnProgress(int max, int progress) {
return progress*100f/max+"%";
}
});