Android 實(shí)現(xiàn)一個(gè)通用的圓角布局

image

前言

我們?cè)谄綍r(shí)的開發(fā)中,經(jīng)常會(huì)遇到圓角需求稠歉,比如下圖

image

一般的實(shí)現(xiàn)方法是上面的圖片左上和右上設(shè)置圓角,下面的文字部分左下和右下設(shè)置圓角,而 Glide 默認(rèn)是不支持指定位置設(shè)置圓角的钮追,需要通過自定義 Transformation 實(shí)現(xiàn),而 GIF 動(dòng)圖也是不支持圓角的阿迈。

有些同學(xué)說(shuō)了元媚,加個(gè)遮罩不就行了嗎?

先不說(shuō)會(huì)不會(huì)被視覺小姐姐噴:一個(gè)圓角都做不了苗沧,還要我給你做遮罩圖刊棕!

我自己本身也是無(wú)法接受這種實(shí)現(xiàn)方式的…

image

那么,實(shí)現(xiàn)一個(gè)通用的圓角布局待逞,不就可以以不變應(yīng)萬(wàn)變了嗎甥角?

正文

如何將 layout 剪裁為圓角?

我們知道 view 繪制時(shí)會(huì)調(diào)用 draw 方法识樱,draw 方法中有大量邏輯嗤无,直接復(fù)寫該方法是不現(xiàn)實(shí)的,看下 draw 方法中的一段注釋

Draw traversal performs several drawing steps which must be executed
in the appropriate order:

     1. Draw the background // drawBackground
     2. If necessary, save the canvas' layers to prepare for fading
     3. Draw view's content // onDraw
     4. Draw children // dispatchDraw
     5. If necessary, draw the fading edges and restore layers
     6. Draw decorations (scrollbars for instance) // onDrawForeground

完整的描述了繪制流程怜庸,后面的注釋是我補(bǔ)充的對(duì)應(yīng)的方法当犯,因此我們只需要從 onDraw 和 dispatchDraw 下手即可。

  • 如果需要剪裁背景割疾,那么需要復(fù)寫 onDraw 和 dispatchDraw
  • 如果不需要剪裁背景嚎卫,那么只需要復(fù)寫 dispatchDraw

剪裁圓角只需要利用畫布 save restore 機(jī)制即可

對(duì)自定義 view 比較熟悉的同學(xué)應(yīng)該知道,使用 canvas.clipPath(path) 會(huì)有鋸齒效果杈曲,為了實(shí)現(xiàn)抗鋸齒效果驰凛,我們使用 canvas.drawPath(path, paint),為 paint 添加抗鋸齒標(biāo)記担扑,并設(shè)置 XFermodes恰响。

有些同學(xué)可能會(huì)發(fā)現(xiàn),在 Android P 上無(wú)法使用 canvas.drawPath(path, paint) 剪裁布局涌献,原因是 Android P 上 XFermodes 行為變更導(dǎo)致的胚宦,詳細(xì)可參考:https://issuetracker.google.com/issues/111819103

為了兼容所有版本,我們只能暫且在 P 上使用 canvas.clipPath(path) 實(shí)現(xiàn)圓角燕垃,會(huì)有鋸齒效果枢劝。

看下實(shí)現(xiàn)效果

image

源碼

代碼不多,直接貼上源碼

public class RoundRelativeLayout extends RelativeLayout {
    private Path mPath;
    private Paint mPaint;
    private RectF mRectF;
    private float mRadius;
    private boolean isClipBackground;

    public RoundRelativeLayout(@NonNull Context context) {
        this(context, null);
    }
    public RoundRelativeLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public RoundRelativeLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundRelativeLayout);
        mRadius = ta.getDimension(R.styleable.RoundRelativeLayout_rlRadius, 0);
        isClipBackground = ta.getBoolean(R.styleable.RoundRelativeLayout_rlClipBackground, true);
        ta.recycle();

        mPath = new Path();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mRectF = new RectF();

        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    }
    public void setRadius(float radius) {
        mRadius = radius;
        postInvalidate();
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRectF.set(0, 0, w, h);
    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void draw(Canvas canvas) {
        if (Build.VERSION.SDK_INT >= 28) {
            draw28(canvas);
        } else {
            draw27(canvas);
        }
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (Build.VERSION.SDK_INT >= 28) {
            dispatchDraw28(canvas);
        } else {
            dispatchDraw27(canvas);
        }
    }
    private void draw27(Canvas canvas) {
        if (isClipBackground) {
            canvas.saveLayer(mRectF, null, Canvas.ALL_SAVE_FLAG);
            super.draw(canvas);
            canvas.drawPath(genPath(), mPaint);
            canvas.restore();
        } else {
            super.draw(canvas);
        }
    }
    private void draw28(Canvas canvas) {
        if (isClipBackground) {
            canvas.save();
            canvas.clipPath(genPath());
            super.draw(canvas);
            canvas.restore();
        } else {
            super.draw(canvas);
        }
    }
    private void dispatchDraw27(Canvas canvas) {
        canvas.saveLayer(mRectF, null, Canvas.ALL_SAVE_FLAG);
        super.dispatchDraw(canvas);
        canvas.drawPath(genPath(), mPaint);
        canvas.restore();
    }
    private void dispatchDraw28(Canvas canvas) {
        canvas.save();
        canvas.clipPath(genPath());
        super.dispatchDraw(canvas);
        canvas.restore();
    }
    private Path genPath() {
        mPath.reset();
        mPath.addRoundRect(mRectF, mRadius, mRadius, Path.Direction.CW);
        return mPath;
    }
}

attrs

<declare-styleable name="RoundRelativeLayout">
    <attr name="rlRadius" format="dimension" />
    <attr name="rlClipBackground" format="boolean" />
</declare-styleable>

后記

如果在 Android P 上你有更好的實(shí)現(xiàn)方法卜壕,還請(qǐng)告知您旁,感謝!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末轴捎,一起剝皮案震驚了整個(gè)濱河市鹤盒,隨后出現(xiàn)的幾起案子蚕脏,更是在濱河造成了極大的恐慌,老刑警劉巖侦锯,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驼鞭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡尺碰,警方通過查閱死者的電腦和手機(jī)挣棕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)亲桥,“玉大人洛心,你說(shuō)我怎么就攤上這事×铰” “怎么了皂甘?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)悼凑。 經(jīng)常有香客問我,道長(zhǎng)璧瞬,這世上最難降的妖魔是什么户辫? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮嗤锉,結(jié)果婚禮上渔欢,老公的妹妹穿的比我還像新娘。我一直安慰自己瘟忱,他們只是感情好奥额,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著访诱,像睡著了一般垫挨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上触菜,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天九榔,我揣著相機(jī)與錄音,去河邊找鬼涡相。 笑死哲泊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的催蝗。 我是一名探鬼主播切威,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼丙号!你這毒婦竟也來(lái)了先朦?” 一聲冷哼從身側(cè)響起缰冤,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烙无,沒想到半個(gè)月后锋谐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡截酷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年涮拗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迂苛。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡三热,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出三幻,到底是詐尸還是另有隱情就漾,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布念搬,位于F島的核電站抑堡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏朗徊。R本人自食惡果不足惜首妖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望爷恳。 院中可真熱鬧有缆,春花似錦、人聲如沸温亲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)栈虚。三九已至袖外,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間节芥,已是汗流浹背在刺。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留头镊,地道東北人蚣驼。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像相艇,于是被迫代替她去往敵國(guó)和親颖杏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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