自定義View,從畫(huà)個(gè)圓寫(xiě)個(gè)字開(kāi)始

前言

老早以前就開(kāi)始看關(guān)于自定義View的文章了畔柔,這兩天實(shí)踐了一下以前看的有關(guān)自定義View的知識(shí)氯夷,畫(huà)了個(gè)控件。這個(gè)控件雖然看起來(lái)簡(jiǎn)單靶擦,但自定義View的基本用法都用到了腮考。麻雀雖小,五臟俱全玄捕,所以還是記錄一下過(guò)程踩蔚。

關(guān)于自定義View的文章,我在這墻裂推薦一下GcsSloop寫(xiě)的安卓自定義View教程枚粘。這是目前我認(rèn)為寫(xiě)得比較好的關(guān)于自定義View的文章馅闽,而且很完整也很詳細(xì),通俗易懂馍迄。

那么福也,回歸主題,就從畫(huà)個(gè)不簡(jiǎn)單的圓開(kāi)始吧攀圈。先看效果圖暴凑,我知道沒(méi)有圖你們是不會(huì)往下看的。

gif

in_rv

gif圖的效果有點(diǎn)卡頓赘来,真機(jī)上是很流暢的现喳。

實(shí)現(xiàn)過(guò)程

拿到一個(gè)效果圖,先看看要怎么畫(huà)犬辰,思考清楚再去干活有時(shí)候會(huì)事半功倍(其實(shí)剛開(kāi)始我只是打算畫(huà)個(gè)圓嗦篱,畫(huà)著畫(huà)著就越來(lái)越多了...)。我們這效果很簡(jiǎn)單忧风,我們現(xiàn)在的需求只是要畫(huà)個(gè)圓默色,然后再在上面寫(xiě)個(gè)字。

畫(huà)個(gè)圓

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

        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();

        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBottom;

        float centerX = paddingLeft + width / 2;
        float centerY = paddingTop + height / 2;

        float radius = Math.min(width, height) / 2;
        canvas.drawCircle(centerX, centerY, radius, mCirclePaint);

    }

畫(huà)圓的代碼很簡(jiǎn)單狮腿,因?yàn)槭抢^承自View腿宰,所以需要先在onDrow方法中先進(jìn)行處理padding,不然padding會(huì)失效缘厢。拿到我們的View的實(shí)際的寬高后吃度,再去獲取View的中心點(diǎn),用來(lái)當(dāng)作圓心位置贴硫。因?yàn)槲疫@里的畫(huà)布的坐標(biāo)原點(diǎn)在屏幕左上角椿每,所以獲取中心點(diǎn)的時(shí)候要加上paddingLeft和paddingTop的距離伊者。centerX和centerY組成的點(diǎn)是我們布局的中心點(diǎn)。拿到中心點(diǎn)后间护,再根據(jù)長(zhǎng)寬中的最小值的一半當(dāng)作半徑亦渗,然后就可以進(jìn)行繪制圓了。So easy!

寫(xiě)個(gè)字

drawText我們用到的是四個(gè)參數(shù)的方法汁尺,這里有個(gè)要注意的地方就是第二個(gè)參數(shù)和第三個(gè)參數(shù)并不是文字的中心位置法精,分別是文字左邊的X值和文字底部的Y值〕胀唬看下源碼就知道了:

    /**
     * Draw the text, with origin at (x,y), using the specified paint. The
     * origin is interpreted based on the Align setting in the paint.
     *
     * @param text  The text to be drawn
     * @param x     The x-coordinate of the origin of the text being drawn
     * @param y     The y-coordinate of the baseline of the text being drawn
     * @param paint The paint used for the text (e.g. color, size, style)
     */
    public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
        native_drawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
                paint.getNativeInstance(), paint.mNativeTypeface);
    }

那么這個(gè)baseline又是什么搂蜓?baseline就是文字底部的Y值×勺埃看看下面這張圖你就明白了:

text

想要把文字放在圓的中心位置帮碰,先要找到baseline和文字的左邊X值。

    private void drawVerticalText(Canvas canvas, float centerX, float centerY, String text) {
        Paint.FontMetrics fontMetrics = mVerticalTextPaint.getFontMetrics();
        float baseLine = -(fontMetrics.ascent + fontMetrics.descent) / 2;
        float textWidth = mVerticalTextPaint.measureText(text);
        float startX = centerX - textWidth / 2;
        float endY = centerY + baseLine;
        canvas.drawText(text, startX, endY, mVerticalTextPaint);
    }

網(wǎng)上有很多關(guān)于drawText的文章拾积,不過(guò)多數(shù)是用一個(gè)Rect包裹著再畫(huà)殉挽,我這里是直接畫(huà)。畫(huà)字在這里我封裝成了一個(gè)方法拓巧,從畫(huà)筆中拿到FontMetrics對(duì)象此再,用來(lái)獲取文字的屬性,包括Ascent值和Descent值等玲销。然后計(jì)算baseline,用Paint的measureText方法獲取文字的長(zhǎng)度摘符。最后根據(jù)中心點(diǎn)偏移一下把文字放到中間贤斜,ok」淇悖看一下效果:

one

文字豎排

這時(shí)瘩绒,又有個(gè)需求,我想要文字豎排顯示怎么弄带族?第一反應(yīng)想到的是drawText是不是應(yīng)該有個(gè)可以控制方向的方法或參數(shù)锁荔?然而,并沒(méi)有這么容易蝙砌。drawText并不能豎排畫(huà)文字阳堕,那么drawTextOnPath方法是不是可以呢?在圓的豎直方向建一條路徑择克,但是結(jié)果并不是很理想恬总,圖我就不放了。最后想到的是用StaticLayout這個(gè)類(lèi)肚邢,通過(guò)換行來(lái)實(shí)現(xiàn)豎排壹堰,其實(shí)TextView源碼中也用到了這個(gè)來(lái)?yè)Q行拭卿。來(lái)看主要代碼:

    ...
    mHorizontalTextPaint = new TextPaint(mVerticalTextPaint);
    mStaticLayout = new StaticLayout(mText, mHorizontalTextPaint, 1, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
    ...
    private void drawHorizontalText(Canvas canvas, float centerX, float centerY) {
        canvas.translate(centerX, centerY - mStaticLayout.getHeight() / 2);
        mStaticLayout.draw(canvas);
    }

StaticLayout的構(gòu)造方法第二個(gè)參數(shù)需要傳個(gè)TextPaint進(jìn)去,TextPaint是繼承自Paint贱纠,所以可以直接傳個(gè)我們上面畫(huà)水平方向文字的時(shí)候的Paint對(duì)象進(jìn)去就行了峻厚,就不用重新設(shè)置顏色字體大小等屬性。關(guān)于StaticLayout的構(gòu)造方法的參數(shù)是什么作用我這就不詳細(xì)說(shuō)了谆焊,我們主要看第三個(gè)參數(shù)惠桃。這個(gè)參數(shù)代表超過(guò)多少個(gè)字符就換行,這里我們傳個(gè)1代表一個(gè)就換行懊渡。然后把畫(huà)布X軸移動(dòng)到圓心坐標(biāo)刽射,文字的高度的一半為圓心Y軸的值。這樣剃执,我們就實(shí)現(xiàn)了文字豎排:

two

文字任意角度

繼續(xù)加需求誓禁,要求文字45°斜著顯示該怎么弄?這個(gè)簡(jiǎn)單肾档,把畫(huà)布坐標(biāo)旋轉(zhuǎn)不就可以了嗎~

   canvas.rotate(mAngle, centerX, centerY);
   drawText(canvas, centerX, centerY);

調(diào)用畫(huà)布的rotate方法就可以了摹恰,就一行代碼。但是這里要注意的是在drawText前面旋轉(zhuǎn)畫(huà)布后再drawText怒见,一定要在drawText前面旋轉(zhuǎn)畫(huà)布俗慈!不然是沒(méi)效果的持寄,我在這就跳進(jìn)坑里了T.T 在drawText后才旋轉(zhuǎn)畫(huà)布腋颠,一直沒(méi)反應(yīng)。還以為是哪里出錯(cuò)了州胳。

three

旋轉(zhuǎn)

靜態(tài)的文字頭像有點(diǎn)單調(diào)了舵变,那就加個(gè)動(dòng)畫(huà)吧酣溃。好,說(shuō)加就加纪隙,加個(gè)旋轉(zhuǎn)的吧赊豌。

    public void startRotateAnimation() {
        if (mAnimator == null) {
            initRotateAnimation();
        }
        if (!mAnimator.isRunning()) {
            mAnimator.start();
        }
    }

    private void initRotateAnimation() {
        if (mRotateOrientation == ROTATE_CLOCKWISE) {
            mRotateValues = 360f;
        } else if (mRotateOrientation == ROTATE_ANTICLOCKWISE) {
            mRotateValues = -360f;
        }
        mAnimator = ValueAnimator.ofFloat(0f, mRotateValues);
        mAnimator.setInterpolator(new LinearInterpolator());
        mAnimator.setDuration(mSpeed);
        mAnimator.addUpdateListener(this);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.setRepeatMode(ValueAnimator.RESTART);
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mAngle = (float) animation.getAnimatedValue();
        invalidate();
    }

    public void stopRotateAnimation() {
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.cancel();
        }
    }

關(guān)于旋轉(zhuǎn)動(dòng)畫(huà)的主要就這四個(gè)方法。原理是用屬性動(dòng)畫(huà)ValueAnimator動(dòng)態(tài)線性均勻改變一個(gè)0到360浮點(diǎn)值绵咱,在AnimatorUpdateListener的onAnimationUpdate方法獲取范圍值碘饼,mAngle為上面任意角度文字的值,動(dòng)態(tài)改變這個(gè)值悲伶,不斷重繪界面艾恼,就可以實(shí)現(xiàn)文字的旋轉(zhuǎn)動(dòng)畫(huà)了。至于上面的360f和-360f是用來(lái)控制方向的拢切,順時(shí)針或者逆時(shí)針旋轉(zhuǎn)蒂萎。通過(guò)持續(xù)時(shí)間setDuration來(lái)控制速度。轉(zhuǎn)起來(lái):

four

縮放

看到FloatingActionButton自帶的show和hide的動(dòng)畫(huà)淮椰,這里我也仿照著弄了個(gè)五慈。

    public void show() {
        if (!isShow) {
            scaleAnimation(0.0f, 1.0f, 300);
            isShow = true;
        }
    }

    public void hide() {
        if (isShow) {
            scaleAnimation(1.0f, 0.0f, 300);
            isShow = false;
        }
    }

    private void scaleAnimation(float start, float end, long duration) {
        ObjectAnimator animatorX = ObjectAnimator.ofFloat(this, "scaleX", start, end);
        ObjectAnimator animatorY = ObjectAnimator.ofFloat(this, "scaleY", start, end);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(duration);
        animatorSet.playTogether(animatorX, animatorY);
        animatorSet.start();
    }

同樣是通過(guò)屬性動(dòng)畫(huà)ObjectAnimator纳寂,去縮放X軸方向“scaleX”,和Y軸方向“scaleY”泻拦。把兩個(gè)動(dòng)畫(huà)放到一個(gè)動(dòng)畫(huà)集AnimatorSet里面同時(shí)執(zhí)行毙芜,從0f到1f是顯示,1f到0f是隱藏争拐。

five

結(jié)語(yǔ)

至此腋粥,這個(gè)文字頭像控就控件就寫(xiě)完了。其實(shí)沒(méi)什么很難的地方架曹,只是把自定義View的基本流程寫(xiě)了一遍隘冲。對(duì)于學(xué)習(xí)自定義View,光看是不行的绑雄,一定要實(shí)踐展辞,動(dòng)手后發(fā)現(xiàn)也沒(méi)想象中的那么難。

同時(shí)万牺,我將這個(gè)CircleView封裝了一下發(fā)布到了JCenter罗珍,免得重復(fù)造輪子。

項(xiàng)目地址:https://github.com/finalrose7/CircleView

喜歡就點(diǎn)個(gè)Star吧脚粟!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末覆旱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子核无,更是在濱河造成了極大的恐慌扣唱,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件团南,死亡現(xiàn)場(chǎng)離奇詭異画舌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)已慢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)霹购,“玉大人佑惠,你說(shuō)我怎么就攤上這事∑敫恚” “怎么了膜楷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)贞奋。 經(jīng)常有香客問(wèn)我赌厅,道長(zhǎng),這世上最難降的妖魔是什么轿塔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任特愿,我火速辦了婚禮仲墨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘揍障。我一直安慰自己目养,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布毒嫡。 她就那樣靜靜地躺著癌蚁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兜畸。 梳的紋絲不亂的頭發(fā)上努释,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音咬摇,去河邊找鬼伐蒂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛菲嘴,可吹牛的內(nèi)容都是我干的饿自。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼龄坪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼昭雌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起健田,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤烛卧,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后妓局,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體总放,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年好爬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了局雄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡存炮,死狀恐怖炬搭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情穆桂,我是刑警寧澤宫盔,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站享完,受9級(jí)特大地震影響灼芭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜般又,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一彼绷、第九天 我趴在偏房一處隱蔽的房頂上張望巍佑。 院中可真熱鬧,春花似錦苛预、人聲如沸句狼。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腻菇。三九已至,卻和暖如春昔馋,著一層夾襖步出監(jiān)牢的瞬間筹吐,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工秘遏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丘薛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓邦危,卻偏偏與公主長(zhǎng)得像洋侨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子倦蚪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

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

  • 一希坚、概述 1. 四線格與基線 小時(shí)候,我們?cè)趧傞_(kāi)始學(xué)習(xí)寫(xiě)字母時(shí)陵且,用的本子是四線格的裁僧,我們必須把字母按照規(guī)則寫(xiě)在四線...
    addapp閱讀 7,675評(píng)論 2 17
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,332評(píng)論 25 707
  • 每次聽(tīng)到某大牛談?wù)撟远xView,頓時(shí)敬佩之心慕购,如滔滔江水連綿不絕聊疲,心想我什么時(shí)候能有如此境界,好了沪悲,心動(dòng)不如行動(dòng)...
    Code4Android閱讀 2,169評(píng)論 6 25
  • #連續(xù)留言39天# 【引子】“現(xiàn)在看來(lái)获洲,你在判斷趨勢(shì)上真的很厲害呢” 自己的經(jīng)歷: 判斷未來(lái)不容易 【分析】 1....
    毛豆爸的破冰之旅閱讀 128評(píng)論 0 0
  • #基本情況# 姓名:王名馳 年齡:2歲10個(gè)月 小組:第7組 #30天目標(biāo)及完成情況# 1、家長(zhǎng)目標(biāo):早起5之前睡...
    加油多麗閱讀 149評(píng)論 0 0