Android 項目中 shape 標簽的整理和思考(2)

在之前的博客中,我們曾經(jīng)討論設計過一個通用組件:CommonShapeButton 。主要用來移除項目中大量的 shape 文件,提高我們項目的可維護性韧涨。有興趣的朋友可以點擊下方鏈接進行閱讀:
Android - Kotlin 是時候跟 shape 標簽說拜拜了
這篇博客發(fā)布以后,得到了大家的廣泛關注侮繁,可能大家也切身感受到了 CommonShapeButton 給我們帶來的便利虑粥。而今天在這里,筆者想要討論的是這個通用組件不能解決的應用場景宪哩,以及給出新的解決方案舀奶。

我們先來看看 CommonShapeButton 不能解決的應用場景是什么?這里我們需要回顧下這個通用組件斋射,它本身是用來解決 shape 文件泛濫的問題育勺,支持 shape 的各種特性,同時也支持文本樣式和按鈕樣式罗岖。但是歸根結底 CommonShapeButton 只是一個 View 涧至,它沒有辦法解決 ViewGroup 的應用場景。而在實際開發(fā)過程中桑包,在 ViewGroup 這一層去設置 shape 樣式的背景是一個常見的需求南蓬。分析到這里,我們得出結論哑了,我們還需要一個通用組件 CommonShapeViewGroup 來協(xié)助我們項目開發(fā)赘方。

正當筆者準備著手設計這個新的通用組件的時候,腦中突然閃過一個官方提供的組件 CardView 弱左,這個位于 support-v7 下面的谷歌親兒子窄陡,好像已經(jīng)解決了我們的問題?于是筆者又去啃了一下官方文檔拆火,對這個 CardView 做了一個全面的梳理跳夭,發(fā)現(xiàn)了它的局限性:

  • CardView 繼承自 FrameLayout 涂圆,而現(xiàn)在主流的 ViewGroup 應該是 ConstraintLayout 和 RelativeLayout。
  • CardView 支持設置背景顏色币叹,但是只能設置純色润歉,無法設置漸變顏色。
  • CardView 支持設置圓角大小颈抚,但是只能同時設置四個角的圓角大小踩衩,無法單一設置左側圓角或者右側圓角。
  • CardView 只支持矩形一種形狀贩汉。
  • CardView 不支持設置描邊顏色和描邊寬度驱富。

沒辦法,看來谷歌親兒子也不頂用雾鬼,還是自己擼吧萌朱。

Talk is cheap. Show me the code

第一步宴树,我們需要確定支持的 ViewGroup 有哪些策菜。還是那句話,現(xiàn)在主流的 ViewGroup 應該是 ConstraintLayout 和 RelativeLayout 酒贬,這里需要重點推一波 ConstraintLayout 又憨,自從用了它以后,腰也不酸了锭吨,腿也不疼了蠢莺,媽媽再也不用擔心我寫布局了。但是考慮到我們程序猿都是重感情的人零如,之前最愛的 RelativeLayout 也不能說有了新歡就不管了是吧躏将,好吧,把 RelativeLayout 加上考蕾,就支持這兩兄弟了祸憋。

第二步,繼續(xù)思考如何來設計這個通用組件肖卧,主要是從以下幾個方面進行了考慮:

  • ViewGroup 的設計要比 View 更簡單蚯窥,因為它是純展示的,沒有交互也不需要動效塞帐。
  • 直接繼承 ConstraintLayout 和 RelativeLayout 拦赠,進行背景的動態(tài)設置是最為簡單有效的方式。
  • 自定義屬性方面葵姥,完全可以參照 CommonShapeButton 荷鼠,去掉一些不需要的屬性即可。
  • 新增一個陰影屬性榔幸,提升一下逼格颊咬∥裆控件陰影這個問題,在 5.0 以上也就一行代碼的事喳篇。在 5.0 以下敞临,筆者花了不少時間,用各種方案做出來的效果都不盡人意麸澜。本著寧缺毋濫的原則挺尿,最終還是選擇了放棄。其實主要原因還是 5.0 以下的用戶確實越來越少炊邦,花費過多的精力去做一些收效甚微的工作也不符合軟件工程的思想编矾。當然這方面有興趣的朋友,可以在文章的后面拿到源碼以后進行自己的擴展和修改馁害。

第三步窄俏,思路已經(jīng)梳理清楚了,那就開擼吧碘菜。這里就以 ConstraintLayout 為例凹蜈,

class ShapeConstraintLayout @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

這里選擇了直接繼承 ConstraintLayout 進行擴展。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    // 初始化shape
    with(mGradientDrawable) {
        // 漸變色
        if (mStartColor != Color.parseColor("#FFFFFF") && mEndColor != Color.parseColor("#FFFFFF")) {
            colors = intArrayOf(mStartColor, mEndColor)
            when (mOrientation) {
                0 -> orientation = GradientDrawable.Orientation.TOP_BOTTOM
                1 -> orientation = GradientDrawable.Orientation.LEFT_RIGHT
            }
        }
        // 填充色
        else {
            setColor(mFillColor)
        }
        when (mShapeMode) {
            0 -> shape = GradientDrawable.RECTANGLE
            1 -> shape = GradientDrawable.OVAL
            2 -> shape = GradientDrawable.LINE
            3 -> shape = GradientDrawable.RING
        }
        // 統(tǒng)一設置圓角半徑
        if (mCornerPosition == -1) {
            cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mCornerRadius.toFloat(), resources.displayMetrics)
        }
        // 根據(jù)圓角位置設置圓角半徑
        else {
            cornerRadii = getCornerRadiusByPosition()
        }
        // 默認的透明邊框不繪制
        if (mStrokeColor != Color.parseColor("#00000000")) {
            setStroke(mStrokeWidth, mStrokeColor)
        }
    }

    // 設置背景
    background = mGradientDrawable

    // 5.0以上設置陰影
    if (mWithElevation && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        elevation = DEFAULT_ELEVATION
    }
}

核心代碼依然選擇在 onMeasure 方法中實現(xiàn)忍啸,我們做一個簡單的分析:

  • 首先對 mGradientDrawable 設置當前是漸變色渲染還是填充色渲染仰坦,漸變色渲染還需要單獨控制渲染的方向。
  • 然后對 mGradientDrawable 設置 shape 模式计雌、圓角以及描邊悄晃。這里的圓角設置區(qū)分了統(tǒng)一設置四個角還是根據(jù)圓角位置設置。
  • 然后設置 ViewGroup 的背景凿滤。
  • 最后在 5.0 以上設置控件陰影妈橄。

到這里,就完成了核心實現(xiàn)翁脆。下面我們看一下根據(jù)圓角位置設置圓角半徑的具體實現(xiàn):

/**
 * 根據(jù)圓角位置獲取圓角半徑
 */
private fun getCornerRadiusByPosition(): FloatArray {
    val result = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
    val cornerRadius = mCornerRadius.toFloat()
    if (containsFlag(mCornerPosition, TOP_LEFT)) {
        result[0] = cornerRadius
        result[1] = cornerRadius
    }
    if (containsFlag(mCornerPosition, TOP_RIGHT)) {
        result[2] = cornerRadius
        result[3] = cornerRadius
    }
    if (containsFlag(mCornerPosition, BOTTOM_RIGHT)) {
        result[4] = cornerRadius
        result[5] = cornerRadius
    }
    if (containsFlag(mCornerPosition, BOTTOM_LEFT)) {
        result[6] = cornerRadius
        result[7] = cornerRadius
    }
    return result
}

/**
 * 是否包含對應flag
 */
private fun containsFlag(flagSet: Int, flag: Int): Boolean {
    return flagSet or flag == flagSet
}

簡單分析一下:

  • 自定義圓角位置支持四個方位的:TOP_LEFT眷蚓、TOP_RIGHT、BOTTOM_RIGHT鹃祖、BOTTOM_LEFT溪椎。
  • 通過自定義屬性中的 flag 標簽設置了圓角方位支持按位或運算。
  • 生成四個角對應的8位數(shù)組恬口,解析 xml 屬性根據(jù)按位或運算設置對應方位的圓角半徑校读。

到這里,也就是 CommonShapeViewGroup 的全部實現(xiàn)了祖能。其實筆者寫到這里的時候歉秫,陷入了一個思考,我們到現(xiàn)在實現(xiàn)了 CommonShapeButton 和 CommonShapeViewGroup 养铸,其實這兩者的本質都是用代碼去實現(xiàn) shape 效果雁芙,也就是對 GradientDrawable 的二次封裝轧膘,那么我們是不是實現(xiàn)一個封裝以后的 CommonShapeDrawable 就可以解決所有問題呢?TextView 兔甘、Button 谎碍、ConstraintLayout 、RelativeLayout等等以及其他的應用場景都可以適配洞焙。筆者產(chǎn)生了這個想法以后蟆淀,就馬上去實現(xiàn)了一個。但是實際開發(fā)用起來以后澡匪,發(fā)現(xiàn)它并不像我們想象的那么方便熔任,需要創(chuàng)建一個 CommonShapeDrawable 對象,然后逐一調用對應的方法去設置 shape 效果唁情,最后還要在一個恰當?shù)臅r機設置成控件的背景疑苔。這跟我們通過 xml 自定義屬性就能實現(xiàn)效果來比,繁瑣了不少甸鸟,最終還是選擇了放棄惦费。有興趣的朋友也可以通過這兩篇博客的學習,自己去擼一個出來哀墓。

題外話說了這么多趁餐,這里還是回到 CommonShapeViewGroup 喷兼,照例貼上全部的自定義屬性:

<declare-styleable name="CommonShapeViewGroup">
    <attr name="csvg_shapeMode" format="enum">
        <enum name="rectangle" value="0" />
        <enum name="oval" value="1" />
        <enum name="line" value="2" />
        <enum name="ring" value="3" />
    </attr>
    <attr name="csvg_fillColor" format="color" />
    <attr name="csvg_strokeColor" format="color" />
    <attr name="csvg_strokeWidth" format="dimension" />
    <attr name="csvg_cornerRadius" format="dimension" />
    <attr name="csvg_cornerPosition">
        <flag name="topLeft" value="1" />
        <flag name="topRight" value="2" />
        <flag name="bottomRight" value="4" />
        <flag name="bottomLeft" value="8" />
    </attr>
    <attr name="csvg_startColor" format="color" />
    <attr name="csvg_endColor" format="color" />
    <attr name="csvg_orientation" format="enum">
        <enum name="TOP_BOTTOM" value="0" />
        <enum name="LEFT_RIGHT" value="1" />
    </attr>
    <attr name="csvg_withElevation" format="boolean" />
</declare-styleable>

以下是效果圖:

image

最后再附上:github地址傳送門 喜歡就 star 一下唄篮绰。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市季惯,隨后出現(xiàn)的幾起案子吠各,更是在濱河造成了極大的恐慌,老刑警劉巖勉抓,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贾漏,死亡現(xiàn)場離奇詭異,居然都是意外死亡藕筋,警方通過查閱死者的電腦和手機纵散,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來隐圾,“玉大人伍掀,你說我怎么就攤上這事∠静兀” “怎么了蜜笤?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盐碱。 經(jīng)常有香客問我把兔,道長沪伙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任县好,我火速辦了婚禮围橡,結果婚禮上,老公的妹妹穿的比我還像新娘缕贡。我一直安慰自己某饰,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布善绎。 她就那樣靜靜地躺著黔漂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪禀酱。 梳的紋絲不亂的頭發(fā)上炬守,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音剂跟,去河邊找鬼减途。 笑死,一個胖子當著我的面吹牛曹洽,可吹牛的內容都是我干的鳍置。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼送淆,長吁一口氣:“原來是場噩夢啊……” “哼税产!你這毒婦竟也來了?” 一聲冷哼從身側響起偷崩,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辟拷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后阐斜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衫冻,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年谒出,在試婚紗的時候發(fā)現(xiàn)自己被綠了隅俘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡笤喳,死狀恐怖为居,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情莉测,我是刑警寧澤颜骤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站捣卤,受9級特大地震影響忍抽,放射性物質發(fā)生泄漏八孝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一鸠项、第九天 我趴在偏房一處隱蔽的房頂上張望干跛。 院中可真熱鬧,春花似錦祟绊、人聲如沸楼入。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘉熊。三九已至,卻和暖如春扬舒,著一層夾襖步出監(jiān)牢的瞬間阐肤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工讲坎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留孕惜,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓晨炕,卻偏偏與公主長得像衫画,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瓮栗,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內容