Android 實(shí)現(xiàn)自定義圓環(huán)

用途說(shuō)明:

這是一個(gè)自定義的圓環(huán)圖像怒详,支持動(dòng)畫展示卖毁,可以自定義圓環(huán)的顏色和占比毫目,主要用以展示一些數(shù)據(jù)占比方面展示的android圓環(huán)奸腺。

圓環(huán)實(shí)現(xiàn)思路:

android的自定義圓環(huán)實(shí)現(xiàn)有很多種方法侥衬,這里只介紹我實(shí)現(xiàn)的思路诗祸。主要思路是先畫一個(gè)大圓,然后再畫一個(gè)與大圓同圓心的小圓轴总,然后小圓的顏色可以設(shè)置為背景色直颅,這樣看上去就是一個(gè)圓環(huán)了。

實(shí)現(xiàn)效果:

動(dòng)畫效果

使用方法:

1.布局文件中直接使用自定義圓環(huán)(RingView)怀樟,控件的寬和高需要固定的尺寸

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"       
       android:layout_width="match_parent"    
       android:layout_height="match_parent"    
       android:background="@android:color/white"    
       android:orientation="vertical">    

       <com.wuden.zxingproject.widgets.RingView        
                android:id="@+id/rvRingView"        
                android:layout_gravity="center"        
                android:layout_width="300dp"        
                android:layout_height="300dp" />
</LinearLayout>

2.在對(duì)應(yīng)的activty中調(diào)用一些方法來(lái)實(shí)現(xiàn)你的需求即可

public class TestActivity extends AppCompatActivity {    
      @Bind(R.id.rvRingView)    
      RingView mRvRingView;    
      @Override    
      protected void onCreate(@Nullable Bundle savedInstanceState) {        
          super.onCreate(savedInstanceState);        
          setContentView(R.layout.layout_test1);       
          ButterKnife.bind(this);
          mRvRingView.setAnglesData("12.2","230","6799.01","1","111","200");//直接設(shè)置String類型的數(shù)據(jù)
//        mRvRingView.setAnglesData(12.2,230,6799.01,1,111,200);//直接設(shè)置double類型的數(shù)據(jù)
//        mRvRingView.setAngles(20, 40, 100, 180, 20);//設(shè)置的是角度
          
 //       mRvRingView.setRingStartAngle(-90);//設(shè)置圓環(huán)的開始角度功偿,不設(shè)置默認(rèn)是-90        
         //設(shè)置畫筆的顏色,支持字符串和資源文件可變參數(shù)往堡。          
          mRvRingView.initPaint("#123456", "#fea123", "#fefefe", "#78da10", "#1121de", "#aacc18");//支持字符串
//        mRvRingView.initPaint(R.color.color_first_part,R.color.color_second_part,
//                             R.color.color_third_part,R.color.color_fourth_part,
//                             R.color.color_fifth_part,R.color.color_sixth_part);
//        mRvRingView.setInnerCirclePaintColor("#ffffff");//內(nèi)圓的畫筆顏色械荷,默認(rèn)#ffffff    
          mRvRingView.setRingStrokeWidth(40);//圓環(huán)的環(huán)寬,默認(rèn)20
//        mRvRingView.showViewWithAnimation(1000);//自定義動(dòng)畫時(shí)長(zhǎng)展示圓環(huán)
//        mRvRingView.showViewWithoutAnimation();//展示圓環(huán)不帶動(dòng)畫   
          mRvRingView.showViewWithAnimation();//動(dòng)畫展示圓環(huán)虑灰,默認(rèn)2s    
      }
}

3.自定義view的源碼

public class RingView extends View {    
    private static final int CIRCLE_ANGLE = 360;//圓環(huán)的角度    
    private static final int RING_STROKE_WIDTH = 20;//默認(rèn)圓環(huán)的寬度為20dp    
    private Paint mNoAssetsPaint, mInnerCirclePaint;    
    private ArrayList<Paint> mPaints;    
    private int mRingStrokeWidth;//圓環(huán)的寬度    
    private int mCanvasWidth, mCanvasHeight;    
    private RectF mRingRect, mInnerRect;    
    private int mDensity;//手機(jī)屏幕密度    
    private int mNoDataPaintColor = Color.parseColor("#cccccc");//沒(méi)有數(shù)據(jù)的paint的顏色    
    private int mInnerCirclePaintColor = Color.parseColor("#ffffff");//內(nèi)圓的paint的顏色    
    private ArrayList<Integer> mAngles;//傳入的數(shù)據(jù)    
    private boolean mHasData = false;    
    private ArrayList<Integer> mLevelStartAngles;//每段圓弧的起始角度值    
    private int mMoveAngle;//圓弧移動(dòng)的角度    
    private int mRingStartAngle = -90;//圓環(huán)的起始角度    
    private RingAnimation mRingAnim;    

    public RingView(Context context) {
        super(context);
        init(context);
    }

    public RingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public RingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context ctx) {
        mDensity = (int) ctx.getResources().getDisplayMetrics().density;
        mRingStrokeWidth = RING_STROKE_WIDTH * mDensity;
        mPaints = new ArrayList<Paint>();
        mAngles = new ArrayList<Integer>();
        mLevelStartAngles = new ArrayList<Integer>();
        mNoAssetsPaint = new Paint();
        mNoAssetsPaint.setAntiAlias(true);
        mNoAssetsPaint.setStyle(Paint.Style.FILL);
        mNoAssetsPaint.setColor(mNoDataPaintColor);
        mInnerCirclePaint = new Paint();
        mInnerCirclePaint.setAntiAlias(true);
        mInnerCirclePaint.setStyle(Paint.Style.FILL);
        mInnerCirclePaint.setColor(mInnerCirclePaintColor);
        mRingAnim = new RingAnimation();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mCanvasWidth == 0) {
            initRect(); 
       } 
       if (!mHasData) {//沒(méi)有數(shù)據(jù)
            mMoveAngle = CIRCLE_ANGLE; 
           drawRingView(canvas, mRingStartAngle, mMoveAngle, mNoAssetsPaint); 
       } else {
            int _level = 0;//圓弧的段數(shù)
            for (int _i = 0; _i < mAngles.size(); _i++) {//計(jì)算需要畫幾段圓弧
                if (mMoveAngle < mLevelStartAngles.get(1)) { 
                   _level = 1; 
               } else if (mMoveAngle > mLevelStartAngles.get(_i) && mMoveAngle <= mLevelStartAngles.get(_i + 1)) {
                   _level = _i + 1;
               }
            }
            drawRing(_level, canvas); 
       }
        canvas.drawArc(mInnerRect, mRingStartAngle, CIRCLE_ANGLE, true, mInnerCirclePaint);//畫內(nèi)部的圓
    }

    /**
     *
     * @param level 圓環(huán)的段數(shù)
     * @param canvas
     */
    private void drawRing(int level, Canvas canvas) {
        if (level <= 0) {
            drawRingView(canvas, mRingStartAngle, CIRCLE_ANGLE, mNoAssetsPaint);
            return;
        }
        if (mAngles.size() > mPaints.size()) {
            int _temp = mAngles.size() - mPaints.size();
            for (int _i = 0; _i < _temp; _i++) {
                mPaints.add(mNoAssetsPaint);
            }
        }
        for (int _i = 0; _i < level; _i++) {
            if (_i == level - 1) {
                drawRingView(canvas, mRingStartAngle + mLevelStartAngles.get(_i), 
mMoveAngle - mLevelStartAngles.get(_i), mPaints.get(_i));
            } else {
                drawRingView(canvas, mRingStartAngle + mLevelStartAngles.get(_i), mAngles.get(_i), mPaints.get(_i));
            }
        }
    }

    /**
     *
     * @param canvas
     * @param startAngle 開始的角度
     * @param sweepAngle 旋轉(zhuǎn)的角度
     * @param paint 畫筆 
     */
    private void drawRingView(Canvas canvas, int startAngle, int sweepAngle, Paint paint) {
        if (sweepAngle != 0) {
            canvas.drawArc(mRingRect, startAngle, sweepAngle, true, paint);
        }
    }

    public void setNoDataPaintColor(int color) {
        mNoAssetsPaint.setColor(getResources().getColor(color));
    }

    public void setNoDataPaintColor(String color) {
        mNoAssetsPaint.setColor(Color.parseColor(color));
    }

    public void setInnerCirclePaintColor(int colorId) {
        mInnerCirclePaint.setColor(getResources().getColor(colorId));
    }

    public void setInnerCirclePaintColor(String color){
        mInnerCirclePaint.setColor(Color.parseColor(color));
    }

    public void initPaint(ArrayList<Integer> colors) {
        mPaints.clear();
        for (int _i = 0; _i < colors.size(); _i++) {
            Paint _paint = new Paint();
            _paint.setAntiAlias(true);
            _paint.setStyle(Paint.Style.FILL);
            _paint.setColor(colors.get(_i));
            mPaints.add(_paint);
        }
    }

    public void initPaint(String... colors) {
        ArrayList<Integer> _colors = new ArrayList<Integer>();
        for (int _i = 0; _i < colors.length; _i++) {
            _colors.add(Color.parseColor(colors[_i]));
        }
        initPaint(_colors);
    }

    public void initPaint(int... colorIds) {
        ArrayList<Integer> _colors = new ArrayList<Integer>();
        for (int _i = 0; _i < colorIds.length; _i++) {
            _colors.add(getResources().getColor(colorIds[_i]));
        }
        initPaint(_colors);
    }

    private void initRect() {
        mCanvasWidth = getWidth();
        mCanvasHeight = getHeight();
        mInnerRect = new RectF(mRingStrokeWidth, mRingStrokeWidth, mCanvasWidth - mRingStrokeWidth, mCanvasHeight - mRingStrokeWidth);
        mRingRect = new RectF(0, 0, mCanvasWidth, mCanvasHeight);
    }

    /**
     * 設(shè)置圓環(huán)起始的角度
     * @param angle
     */

    public void setRingStartAngle(int angle){
        mRingStartAngle = angle;
    }

    /**
     * 設(shè)置圓環(huán)的環(huán)寬
     *
     * @param width
     */
    public void setRingStrokeWidth(int width) {
        mRingStrokeWidth = width * mDensity;
        invalidate();
    }

    /**
     * 所需要顯示的數(shù)據(jù)的角度
     *
     * @param angles
     */
    public void setAngles(int... angles) {
        ArrayList<Integer> _angles = new ArrayList<Integer>();
        for (int _i = 0; _i < angles.length; _i++) {
            _angles.add(angles[_i]);
        }
        setAngles(_angles);
    }

    /**
     * 所需要顯示的數(shù)據(jù)的角度
     *
     * @param angles
     */

    public void setAngles(ArrayList<Integer> angles) {
        mAngles.clear();
        mAngles.addAll(angles);
        mLevelStartAngles.clear();
        mLevelStartAngles.add(0);
        int _angle = 0;
        for (int _i = 0; _i < mAngles.size(); _i++) {
            _angle += mAngles.get(_i);
            mLevelStartAngles.add(_angle);
            if (mAngles.get(_i) > 0) {
                mHasData = true;
            }
        }
    }

    /**
     * 設(shè)置數(shù)據(jù)來(lái)計(jì)算角度并繪制圓環(huán)
     *
     * @param data
     */
    public void setAnglesData(BigDecimal... data) {
        BigDecimal _total = new BigDecimal("0.00");
        for (int _i = 0; _i < data.length; _i++) {
            _total = _total.add(data[_i]);
        }

        if (_total.compareTo(BigDecimal.valueOf(0)) == 0) {
            mHasData = false;
            return;
        }

        BigDecimal[] _dbData = new BigDecimal[data.length];
        for (int _i = 0; _i < data.length; _i++) {
            _dbData[_i] = data[_i].divide(_total, 10, ROUND_HALF_UP).multiply(BigDecimal.valueOf(360));
        }

        int[] _intData = new int[data.length];
        for (int _i = 0; _i < data.length; _i++) {
            //數(shù)值小于1且大于0的吨瞎,就直接定1,否則轉(zhuǎn)int類型穆咐,確保小數(shù)據(jù)也能出現(xiàn)在圓環(huán)上
            _intData[_i] = _dbData[_i].compareTo(BigDecimal.valueOf(1.0)) < 0 && _dbData[_i].compareTo(BigDecimal.valueOf(0)) > 0 ?
                    1 : _dbData[_i].intValue();
        }

        //所有數(shù)據(jù)加起來(lái)可能會(huì)不滿360也可能會(huì)超出360颤诀,由于精度的問(wèn)題
        //處理方案是把缺少的度數(shù)(有正也有負(fù))加在最大的值上字旭,這樣圖形出現(xiàn)的誤差會(huì)不明顯
        int _remind = 360;//剩余的角度
        int _maxPosition = -1, _max = _intData[0];
        for (int _i = 0; _i < _intData.length; _i++) {
            _remind = _remind - _intData[_i];
            if (_max <= _intData[_i]) {
                _maxPosition = _i;
            }
        }
        _intData[_maxPosition] += _remind;//將缺少的度數(shù)加載最大值上

        //將最終的數(shù)據(jù)設(shè)置到圓環(huán)上
        setAngles(_intData);
    }

    public void setAnglesData(String... data) {
        BigDecimal[] _bdData = new BigDecimal[data.length];
        for (int _i = 0; _i < data.length; _i++) {
            _bdData[_i] = new BigDecimal(TextUtils.isEmpty(data[_i]) ? "0" : data[_i]);
        }
        setAnglesData(_bdData);
    }

    public void setAnglesData(double... data) {
        BigDecimal[] _bdData = new BigDecimal[data.length];
        for (int _i = 0; _i < data.length; _i++) {
            _bdData[_i] = BigDecimal.valueOf(data[_i]);
        }
        setAnglesData(_bdData);
    }

    /**
     * 自定義動(dòng)畫時(shí)間的圓環(huán)
     *
     * @param animTime
     */
    public void showViewWithAnimation(int animTime) {
        startAnimation(animTime);
    }

    /**
     * 默認(rèn)時(shí)間(2000)的圓環(huán)
     */
    public void showViewWithAnimation() {
        startAnimation(-1);
    }

    /**
     * 不帶動(dòng)畫的圓環(huán)
     */
    public void showViewWithoutAnimation() {
        mMoveAngle = CIRCLE_ANGLE;
        invalidate();
    }

    private void startAnimation(int animTime) {
        mRingAnim.setDuration(animTime <= 0 ? 2000 : animTime);
        startAnimation(mRingAnim);
    }

    private class RingAnimation extends Animation {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            mMoveAngle = (int) (interpolatedTime * CIRCLE_ANGLE);
            invalidate();
        }
    }
}

4.代碼實(shí)現(xiàn)的一些注意點(diǎn)

1)控件的寬和高必須是固定的,不然無(wú)法顯示崖叫。
2)畫筆顏色的數(shù)組長(zhǎng)度必須大于或等于數(shù)據(jù)數(shù)組的長(zhǎng)度遗淳,不然超出的數(shù)據(jù)將由默認(rèn)的沒(méi)有數(shù)據(jù)的顏色顯示。
3)在設(shè)置畫筆顏色時(shí)心傀,使用的字符串形式的顏色必須嚴(yán)格遵循顏色的書寫方式屈暗,不然會(huì)出現(xiàn)無(wú)法正確顯示view。例如:不支持“#fff”脂男,支持“#ffffff”养叛。

該控件中還存在很多需要優(yōu)化的地方和更多的功能支持,后期會(huì)補(bǔ)充疆液,希望各位大神可以給出指導(dǎo)性的意見(jiàn)和建議一铅,十分感謝??陕贮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末堕油,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子肮之,更是在濱河造成了極大的恐慌掉缺,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戈擒,死亡現(xiàn)場(chǎng)離奇詭異眶明,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)筐高,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門搜囱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人柑土,你說(shuō)我怎么就攤上這事蜀肘。” “怎么了稽屏?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵扮宠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我狐榔,道長(zhǎng)坛增,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任薄腻,我火速辦了婚禮收捣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘庵楷。我一直安慰自己罢艾,他們只是感情好萝玷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著昆婿,像睡著了一般球碉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仓蛆,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天睁冬,我揣著相機(jī)與錄音,去河邊找鬼看疙。 笑死豆拨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的能庆。 我是一名探鬼主播施禾,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼搁胆!你這毒婦竟也來(lái)了弥搞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤渠旁,失蹤者是張志新(化名)和其女友劉穎攀例,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顾腊,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡粤铭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杂靶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梆惯。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吗垮,靈堂內(nèi)的尸體忽然破棺而出垛吗,到底是詐尸還是另有隱情,我是刑警寧澤抱既,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布职烧,位于F島的核電站,受9級(jí)特大地震影響防泵,放射性物質(zhì)發(fā)生泄漏蚀之。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一捷泞、第九天 我趴在偏房一處隱蔽的房頂上張望足删。 院中可真熱鬧,春花似錦锁右、人聲如沸失受。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拂到。三九已至痪署,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兄旬,已是汗流浹背狼犯。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留领铐,地道東北人悯森。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像绪撵,于是被迫代替她去往敵國(guó)和親瓢姻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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