自定義控件——弄個(gè)甜甜圈吧(2): 搭建

【注:】本文首發(fā)于簡(jiǎn)書,掘金會(huì)同步發(fā)送揩魂,其余網(wǎng)站皆無(wú)授權(quán)幽邓。

歡迎瀏覽掘金主頁(yè)和簡(jiǎn)書主頁(yè),我只是一枚普通的工程師-V-

喜歡自定義控件火脉,也喜歡分享我的思路牵舵,希望能得到你的批評(píng)和建議,也希望能幫到你

github:https://github.com/razerdp/AnimatedPieView

上一篇:《自定義控件——弄個(gè)甜甜圈吧(1): 起源》


從哪開(kāi)始倦挂?

上一篇畸颅,我們初步選定了方案,從這一篇文章開(kāi)始方援,我們將會(huì)從0開(kāi)始寫我們的控件

在上篇中我提到了我們會(huì)經(jīng)歷一個(gè)迷茫没炒,原因就是方向太多,但我們終歸是走過(guò)了那個(gè)迷茫犯戏,只是在大的方向上我們確定了送火,但是在實(shí)施的開(kāi)始,小方向上仍然好多選擇先匪,比如我是先寫View呢還是先寫接口种吸,還是先寫B(tài)ean,還是先寫什么呀非。坚俗。。

所以岸裙,從哪開(kāi)始就是一個(gè)問(wèn)題

如果看過(guò)我的朋友圈文集猖败,看過(guò)我分享我寫控件的思路,應(yīng)該會(huì)看得出降允,我一般先去寫attrs.xml辙浑,也就是先寫屬性,再慢慢的去確定其他的東西拟糕。

但是在甜甜圈工程判呕,我并沒(méi)有打算寫attrs,所以我會(huì)直接從View開(kāi)始


準(zhǔn)備階段

自定義控件說(shuō)白了其實(shí)就是讓我們?cè)谙到y(tǒng)給出的畫布里(View.onDraw()是空實(shí)現(xiàn))畫出我們所希望的東西送滞,所以如果說(shuō)自定義控件侠草,總是不會(huì)忘掉onDraw()這個(gè)方法的

在正式畫出來(lái)之前,我們需要去考慮我們的畫布尺寸犁嗅,看看需不需要我們?nèi)プ鰷y(cè)量

在本工程里边涕,我并不打算去要求大小,因?yàn)槲抑粫?huì)根據(jù)畫布的大小來(lái)決定我繪制的半徑,所以onMeasure()/onLayout()這兩個(gè)我們直接忽略功蜓,不再考慮

因此园爷,我們可以看看我們需要什么工具(參數(shù)):

  1. 畫筆
  2. 數(shù)據(jù)
  3. 沒(méi)了。式撼。童社。。哈哈

所以著隆,在一開(kāi)始的階段扰楼,我們不妨直搗黃龍,先把甜甜圈畫出來(lái)再說(shuō)美浦。

初次嘗試

畫一個(gè)甜甜圈非常簡(jiǎn)單弦赖,確定好角度,和多個(gè)Paint浦辨,通過(guò)canvas.drawArc()就可以完成:

public class AnimatedPieView extends View {
    protected final String TAG = this.getClass().getSimpleName();

    Paint paint1;
    Paint paint2;
    Paint paint3;

    RectF mDrawRectf=new RectF();

    ...構(gòu)造器(略)

    public AnimatedPieView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs);
    }


    private void initView(Context context, AttributeSet attrs) {
        if (paint1 == null) paint1 = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        paint1.setStyle(Paint.Style.STROKE);
        paint1.setStrokeWidth(80);
        paint1.setColor(Color.RED);

        if (paint2 == null) paint2 = new Paint(paint1);
        paint2.setColor(Color.GREEN);

        if (paint3 == null) paint3 = new Paint(paint1);
        paint3.setColor(Color.BLUE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        final float width = getWidth() - getPaddingLeft() - getPaddingRight();
        final float height = getHeight() - getPaddingTop() - getPaddingBottom();

        canvas.translate(width / 2, height / 2);
        //半徑
        final float radius = (float) (Math.min(width, height) / 2 * 0.85);
        mDrawRectf.set(-radius, -radius, radius, radius);

        canvas.drawArc(mDrawRectf,0,120,false,paint1);
        canvas.drawArc(mDrawRectf,120,120,false,paint2);
        canvas.drawArc(mDrawRectf,240,120,false,paint3);

    }
}
效果圖

非常簡(jiǎn)單蹬竖,對(duì)吧,三支筆流酬,三個(gè)角度币厕,完事~

這時(shí)候我們就可以叼著煙,架著二郎腿康吵,打個(gè)王者劈榨,漠視產(chǎn)品:“哥搞定了”

產(chǎn)品:搞定個(gè)屁7玫荨;耷丁!拷姿!

再次嘗試

被產(chǎn)品暴打一頓之后惭载,就開(kāi)始學(xué)乖了,同時(shí)心里那股追求完美的那把火也熊熊燃燒

丫的响巢,既然這個(gè)不能讓你閉嘴描滔,就寫出一個(gè)牛逼點(diǎn)的,干脆開(kāi)源

于是踪古,接下來(lái)我們陷入了深深的思考中

從上面簡(jiǎn)單的幾十行代碼中含长,我們不難看出,整個(gè)View的核心其實(shí)就在于幾個(gè)點(diǎn):

  • 畫筆
  • 角度
  • 半徑

其他的我們也許可以替換伏穆,但這三個(gè)點(diǎn)是無(wú)論如何都無(wú)法動(dòng)搖其三個(gè)大哥的根基的

所以考慮到我們要做一個(gè)庫(kù)而不是去完成什么簡(jiǎn)單的需求拘泞,因此就需要考慮擴(kuò)展性的問(wèn)題了,下面根據(jù)這三個(gè)核心點(diǎn)去思考

1.1 畫筆

對(duì)于一個(gè)庫(kù)的使用者來(lái)說(shuō)枕扫,我最希望的是允許我盡可能多的配置參數(shù)陪腌,但我又很不喜歡一個(gè)View包含著一大堆的getter/setter,因?yàn)樘嗟膅et/set帶來(lái)的只會(huì)是→選擇困難癥,同時(shí)诗鸭,我們使用這個(gè)庫(kù)也希望局限性不大染簇,給我們一個(gè)比較好的擴(kuò)展性和自由發(fā)揮空間。

但是對(duì)于庫(kù)的創(chuàng)造者來(lái)說(shuō)强岸,我們很明確的知道我們要實(shí)現(xiàn)一個(gè)效果锻弓,需要的什么參數(shù),但我們又不能去限定開(kāi)發(fā)者們请唱,必須使用我這樣的實(shí)體弥咪,否則那樣局限性也太大了。

綜上所述十绑,其實(shí)我們?cè)O(shè)計(jì)的時(shí)候就需要考慮兩點(diǎn):

  • 避免太多getter/setter集中在一個(gè)View中聚至,如果可以,盡量剝離本橙,這樣View的代碼不會(huì)很多參數(shù),其次也給需要看源碼的人一個(gè)方便甚亭,更多的是贷币。。役纹。。為了簡(jiǎn)潔清晰

  • 我們無(wú)法知道用戶的類里面的具體參數(shù)暇唾,但我們知道我們需要什么參數(shù),所以采取接口約束的形式策州,是一個(gè)很不錯(cuò)的方法

對(duì)于我們的這個(gè)甜甜圈工程瘸味,我們需要的畫筆,其實(shí)從開(kāi)發(fā)者那里獲取的也就是兩個(gè)參數(shù):

  • 顏色
  • 大信苑隆(線寬)

所以,我們不妨定義一個(gè)接口孽糖,接口里面包含著獲取顏色的方法枯冈,其他的我們就不管了(線寬等參數(shù)不必在這里限定办悟,因?yàn)槲覀冞€有config配置類)

public interface IPieInfo {

    int getColor();

}

至于開(kāi)發(fā)者怎么使用他們的類,我們不管誉尖,我們只需要保證他們的類有我們需要的顏色參數(shù)就好罪既。

其二,針對(duì)避免過(guò)多的getter/setter琢感,我們其實(shí)可以結(jié)合builder模式來(lái)寫出我們的option(本工程里稱為config)統(tǒng)一管理

在這里引用我在github上README寫的使用方法:

AnimatedPieView mAnimatedPieView = findViewById(R.id.animatedPieView);
        AnimatedPieViewConfig config = new AnimatedPieViewConfig();
        config.setStartAngle(-90)//起始角度偏移
                .addData(new SimplePieInfo(30, getColor("FFC5FF8C"), "這是第一段"))//數(shù)據(jù)(實(shí)現(xiàn)IPieInfo接口的bean)
                .addData(new SimplePieInfo(18.0f, getColor("FFFFD28C"), "這是第二段"))
                ...(盡管addData吧)
                .setDuration(2000)//持續(xù)時(shí)間
                .setInterpolator(new DecelerateInterpolator(2.5f));//插值器
        mAnimatedPieView.applyConfig(config);
        mAnimatedPieView.start();

總的來(lái)說(shuō)丢间,我們的庫(kù)具體分為兩個(gè)部分:

  • 渲染的主體(View)
  • 渲染的參數(shù)配置(config)

1.2 角度

對(duì)于一個(gè)餅圖,我們當(dāng)然不會(huì)希望我們寫出來(lái)的庫(kù)像上面例子那樣都限定死每塊120度驹针,否則都不用跳樓gg了烘挫,口水都能淹沒(méi)你。柬甥。饮六。

同時(shí)我們也不關(guān)心用戶數(shù)據(jù)結(jié)構(gòu),所以在1.1的基礎(chǔ)上苛蒲,我們?cè)诮涌诶镌偌s束一條:想哥渲染的漂亮不卤橄?想就給我一個(gè)值~

因此,現(xiàn)在我們的接口變成了這樣:

public interface IPieInfo {

    float getValue();

    int getColor();
}

有了值臂外,我們就可以計(jì)算出這個(gè)數(shù)據(jù)所占的比例窟扑,那么也就相當(dāng)于知道了這個(gè)數(shù)據(jù)在甜甜圈中掃描的角度了

在config中,我們用一個(gè)list來(lái)保存開(kāi)發(fā)者傳入的數(shù)據(jù)漏健,并修飾

因此我們的config就可以這樣子寫了:

public class AnimatedPieViewConfig implements Serializable {

    private List<IPieInfo> mIPieInfos;

    public AnimatedPieViewConfig() {
        mIPieInfos=new ArrayList<>();
    }
    
    public AnimatedPieViewConfig addData(IPieInfo info){
        if (mIPieInfos==null)mIPieInfos=new ArrayList<>();
        mIPieInfos.add(info);
        //計(jì)算角度
        return this;
    }
    
}

然而這里有個(gè)問(wèn)題嚎货,還記得我們傳入的是啥嗎,是一個(gè)接口蔫浆,這個(gè)接口我們只管取值

當(dāng)然殖属,我們可以約束開(kāi)發(fā)者一個(gè)setAngle,只不過(guò)這個(gè)setAngle只提供給我們用來(lái)把計(jì)算的值傳入而已瓦盛。

如果這樣做洗显。。谭溉。你看看開(kāi)發(fā)者會(huì)不會(huì)給你寄刀片←_←墙懂?

所以橡卤,我們當(dāng)然不可以這么蛋疼啦扮念,但我們又希望有個(gè)地方保存我們計(jì)算出來(lái)的數(shù)據(jù),那該咋辦碧库?

神說(shuō):要有光柜与,從此世界有了光
程序員說(shuō):要有對(duì)象,從此嵌灰,我們習(xí)慣了new(kotlin等語(yǔ)言除外哈)

既然我們需要一個(gè)地方保存弄匕,那我們就弄個(gè)類保存起來(lái)就好啦~

而且這個(gè)類只能我們知道,對(duì)于外部是不知道的-V-(權(quán)限修飾)

因此沽瞭,我們?cè)俣x一個(gè)類:PieInfoImpl迁匠,這個(gè)類不可繼承且對(duì)外隱藏,這個(gè)類對(duì)于我們來(lái)說(shuō)相當(dāng)于包裝,用戶數(shù)據(jù)被包在里面城丧,同時(shí)添加上我們需要的各種方法延曙,既能保證開(kāi)發(fā)者拿到自己的數(shù)據(jù)也能保證我們可以懟入我們的數(shù)據(jù)

因此,我們的類長(zhǎng)這樣:

final class PieInfoImpl {

    private final String id;
    private final IPieInfo mPieInfo;
    private float startAngle;
    private float endAngle;

    public static PieInfoImpl create(IPieInfo info) {
        return new PieInfoImpl(info);
    }
    //getter/setter和其他構(gòu)造器暫時(shí)忽略亡哄,以后的文章會(huì)描述
}

所以枝缔,對(duì)開(kāi)發(fā)者可見(jiàn)的config我們就可以修改了:

public class AnimatedPieViewConfig implements Serializable {

    private List<PieInfoImpl> mIPieInfos;
    private AnimatedPieViewHelper mPieViewHelper;

    public AnimatedPieViewConfig() {
        mIPieInfos=new ArrayList<>();
        mPieViewHelper=new AnimatedPieViewHelper();
    }

    public AnimatedPieViewConfig addData(IPieInfo info){
        if (mIPieInfos==null)mIPieInfos=new ArrayList<>();
        mIPieInfos.add(PieInfoImpl.create(info));
        mPieViewHelper.prepare();
        return this;
    }

    /**
     * 為了區(qū)分參數(shù)配置和參數(shù)計(jì)算,這里用一個(gè)內(nèi)部類來(lái)管理
     */
    protected final class AnimatedPieViewHelper {
        private double sumValue;

        private void prepare() {
            //計(jì)算角度
            if (ToolUtil.isListEmpty(mIPieInfos)) return;
            sumValue = 0;
            //算總和
            for (PieInfoImpl dataImpl : mIPieInfos) {
                IPieInfo info = dataImpl.getPieInfo();
                sumValue += info.getValue();
            }
            //算每部分的角度
            float start = 0;
            for (PieInfoImpl data : mIPieInfos) {
                data.setStartAngle(start);
                float angle = (float) (360.0 * (data.getPieInfo().getValue() / sumValue));
                angle = Math.max(1.0f, angle);
                float endAngle = start + angle;
                data.setEndAngle(endAngle);
                start = endAngle;
            }
        }

        public double getSumValue() {
            return sumValue;
        }
    }

}

1.3 半徑

請(qǐng)讓我喝口水蚊惯。愿卸。。截型。

然后

輕輕告訴你

往config塞一個(gè)半徑吧-V- hhhh

下一節(jié)趴荸,我們將會(huì)開(kāi)始我們的第一個(gè)難點(diǎn):

甜甜圈動(dòng)畫

下一篇:自定義控件——弄個(gè)甜甜圈吧(3): 動(dòng)畫篇【生長(zhǎng)動(dòng)畫】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宦焦,隨后出現(xiàn)的幾起案子赊舶,更是在濱河造成了極大的恐慌,老刑警劉巖赶诊,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笼平,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡舔痪,警方通過(guò)查閱死者的電腦和手機(jī)寓调,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)锄码,“玉大人夺英,你說(shuō)我怎么就攤上這事∽檀罚” “怎么了痛悯?”我有些...
    開(kāi)封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)载萌。 經(jīng)常有香客問(wèn)我,道長(zhǎng)扭仁,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任乖坠,我火速辦了婚禮,結(jié)果婚禮上熊泵,老公的妹妹穿的比我還像新娘仰迁。我一直安慰自己轩勘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布绊寻。 她就那樣靜靜地躺著,像睡著了一般澄步。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上村缸,一...
    開(kāi)封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天武氓,我揣著相機(jī)與錄音,去河邊找鬼县恕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛忠烛,可吹牛的內(nèi)容都是我干的属提。 我是一名探鬼主播冤议,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼师坎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了胯陋?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤惶岭,失蹤者是張志新(化名)和其女友劉穎犯眠,沒(méi)想到半個(gè)月后按灶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鸯旁,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡噪矛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年艇挨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缩滨。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泉瞻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出袖牙,到底是詐尸還是另有隱情,我是刑警寧澤鞭达,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站畴蹭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏叨襟。R本人自食惡果不足惜桨踪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一锻离、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧汽纠,春花似錦、人聲如沸虱朵。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至梆暮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啦粹,已是汗流浹背窘游。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忍饰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓艾蓝,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親饶深。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,139評(píng)論 25 707
  • 【注:】本文首發(fā)于簡(jiǎn)書敌厘,掘金會(huì)同步發(fā)送,其余網(wǎng)站皆無(wú)授權(quán)俱两。 歡迎瀏覽掘金主頁(yè)和簡(jiǎn)書主頁(yè),我只是一枚普通的工程師-V...
    Razerdp閱讀 1,371評(píng)論 0 2
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理宪彩,服務(wù)發(fā)現(xiàn),斷路器讲婚,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程,因...
    小菜c閱讀 6,419評(píng)論 0 17
  • 恒山派見(jiàn)岳不群推三阻四筹麸,不顧義氣,都心頭有氣物赶。儀琳道:“令狐師兄,你且在福州養(yǎng)傷酵紫,我們?nèi)ゾ攘藥煾浮煵貋?lái)奖地,再來(lái)探...
    littlestupid閱讀 211評(píng)論 0 1