從一個(gè)簡(jiǎn)潔的進(jìn)度刻度繪制中了解自定義View的思路流程

circleloading

先看效果(原諒我的渣像素),進(jìn)度的刻度、寬度圆到、顏色可以隨意設(shè)定:

circleloading

circleloading

【項(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)了:

Shader
SweepGradient sweepGradient = new SweepGradient(getWidth()/2, getHeight()/2, colors, null);
  p.setShader(sweepGradient);

到這里為止龄砰,我們的圓環(huán)已經(jīng)繪制好了,包括中間的文字以及圓環(huán)的漸變效果都已經(jīng)實(shí)現(xiàn)了讨衣,就是這樣的:

進(jì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+"%";
            }
        });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市埂蕊,隨后出現(xiàn)的幾起案子往弓,更是在濱河造成了極大的恐慌,老刑警劉巖蓄氧,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件函似,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡喉童,警方通過(guò)查閱死者的電腦和手機(jī)撇寞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)泄朴,“玉大人重抖,你說(shuō)我怎么就攤上這事∽婊遥” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵畔规,是天一觀的道長(zhǎng)局扶。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么三妈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任畜埋,我火速辦了婚禮,結(jié)果婚禮上畴蒲,老公的妹妹穿的比我還像新娘悠鞍。我一直安慰自己,他們只是感情好模燥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布咖祭。 她就那樣靜靜地躺著,像睡著了一般蔫骂。 火紅的嫁衣襯著肌膚如雪么翰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天辽旋,我揣著相機(jī)與錄音浩嫌,去河邊找鬼。 笑死补胚,一個(gè)胖子當(dāng)著我的面吹牛码耐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播溶其,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼骚腥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了握联?” 一聲冷哼從身側(cè)響起桦沉,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎金闽,沒(méi)想到半個(gè)月后纯露,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡代芜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年埠褪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挤庇。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钞速,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嫡秕,到底是詐尸還是另有隱情渴语,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布昆咽,位于F島的核電站驾凶,受9級(jí)特大地震影響牙甫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜调违,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一窟哺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧技肩,春花似錦且轨、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至雳锋,卻和暖如春黄绩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玷过。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工爽丹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辛蚊。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓粤蝎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親袋马。 傳聞我的和親對(duì)象是個(gè)殘疾皇子初澎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容