先扯兩句
不知道多長(zhǎng)時(shí)間沒(méi)有登錄簡(jiǎn)書了闽巩,正好今天不太忙進(jìn)來(lái)看一眼,然后看了好多文章流椒,從我發(fā)的東西也能看出來(lái)敏簿,絕對(duì)不肯能是純技術(shù)的,那看的種類那叫一個(gè)多啊宣虾。發(fā)現(xiàn)首頁(yè)有幾篇申請(qǐng)簡(jiǎn)書創(chuàng)作者沒(méi)成功的帖子惯裕,再看看自己的簡(jiǎn)書創(chuàng)作者,不由老臉一紅绣硝,還是寫一篇對(duì)得起平臺(tái)對(duì)我的認(rèn)可蜻势。
正文
隨著UI越來(lái)越美觀,原本直來(lái)直去的布局樣式越來(lái)越不受喜歡鹉胖,圓角樣式握玛,這個(gè)最簡(jiǎn)單的優(yōu)化方案在越來(lái)越多的場(chǎng)合下被應(yīng)用到。而具體怎么樣才能展示出圓角的效果甫菠,相比大家都有N多中方案败许,我這里就列舉一些自己常用的方案吧。當(dāng)然淑蔚,還有一些第三方的框架支持,這里就不列舉了愕撰,主要是我個(gè)人感覺(jué)為了一個(gè)簡(jiǎn)單的效果刹衫,集成一個(gè)庫(kù)不太劃算醋寝,尤其是庫(kù)中很大一部分功能都用不到的時(shí)候。而且第三方的框架貢獻(xiàn)者實(shí)力不等带迟,有很多都要導(dǎo)入源碼進(jìn)一步優(yōu)化音羞,分析太麻煩了,一向懶漢自稱的我是不可能這么勤快去列舉的仓犬,大家有興趣可以自行研究嗅绰。
樣式參考就已自如房型列表的item為例吧:
方案0
首先說(shuō)明一下,什么叫做方案0搀继,不是因?yàn)槌绦騿T窘面,計(jì)數(shù)一定要從0開(kāi)始的潔癖,單純是因?yàn)檫@些方案算是比較常用的叽躯,大家遇到類似的需求很快就能想到的财边。這里只是簡(jiǎn)單介紹一下,給一些新入門的同學(xué)看看点骑,在遇到相應(yīng)的圓角需求的時(shí)候酣难,可以最方便的解決問(wèn)題。
當(dāng)然黑滴,還是同樣的憨募,如果一定要找一個(gè)第三方框架,一個(gè)參數(shù)控制去實(shí)現(xiàn)也不是不可以袁辈,性能各方面我這里也沒(méi)有去具體評(píng)估過(guò)菜谣,也沒(méi)辦法說(shuō)一定誰(shuí)好誰(shuí)壞。所以對(duì)于一切反對(duì)意見(jiàn)吵瞻,我這里的回應(yīng)都是:“我錯(cuò)了葛菇!你說(shuō)的對(duì)!”
1 圓角背景:xml
圓角的背景橡羞,可以實(shí)現(xiàn)的效果就是上面圖中的下半部分
首先我們需要分析這個(gè)部分有什么要求:
可以看到有兩個(gè)地方有要求眯停,分別是:1.白色背景;2.灰色邊框卿泽。所以對(duì)應(yīng)的xml只需要滿足這兩點(diǎn)就可以了莺债。
-
創(chuàng)建drawable的xml文件
創(chuàng)建目錄 -
命名,并將對(duì)應(yīng)的跟元素設(shè)置為shape(形狀)
image.png - 配置屬性
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!--中間的白色實(shí)心背景-->
<solid android:color="#FFFFFF" />
<!--灰色邊框签夭,色值和寬度-->
<stroke
android:width="0.5dp"
android:color="#999999" />
<!--圓角角度:左下和右下-->
<corners
android:bottomLeftRadius="5dp"
android:bottomRightRadius="5dp" />
</shape>
其中需要注意的是齐邦,圖中可以看到,需要有圓角的區(qū)域只在下半部分第租,所以這里配置的時(shí)候也要只配置下半部措拇,不然圖片與背景的交接處就會(huì)有相當(dāng)怪異。
這里可能有人會(huì)說(shuō)慎宾,直接繪制一個(gè)白色的全圓角背景放到下面丐吓,上面蓋上一個(gè)圓角的圖片不就好了嗎浅悉?你真是一個(gè)小機(jī)靈鬼,這個(gè)方案當(dāng)然可行券犁。但是性能稍微有點(diǎn)追求的建議了解一些過(guò)度繪制术健,雖然不是說(shuō)背景上面疊加一張圖片就會(huì)對(duì)性能造成斷崖式影響,但是雪崩之下粘衬,可沒(méi)有哪朵雪花是無(wú)辜的荞估。
當(dāng)然,這里為了看得清晰稚新,頁(yè)面的背景設(shè)置成了黑色勘伺,圓角也調(diào)整到了20dp,正常情況下也會(huì)出現(xiàn)問(wèn)題枷莉,但是不會(huì)這么明顯娇昙,能不能發(fā)現(xiàn)就看測(cè)試團(tuán)隊(duì)和UI團(tuán)隊(duì)的審查力度了。
2 純圖片Glide
現(xiàn)在用的比較多的笤妙,或者是我工作這么多年用的比較多的圖片框架就是glide冒掌,想當(dāng)初在剛開(kāi)始用glide的時(shí)候還不支持圓角,需要額外添加一堆配置蹲盘,還要導(dǎo)入第三方的庫(kù)股毫,現(xiàn)在好了,沒(méi)有幾行代碼召衔,基本屬于一鍵配置了铃诬。
Glide.with(this).load(R.drawable.bg_scene)
.apply(
RequestOptions
.bitmapTransform(RoundedCorners(dip2px(this, 20f)))
).into(findViewById(R.id.test_img))
其中apply中的內(nèi)容就是圓角的配置,當(dāng)然苍凛,猜也能猜出來(lái)趣席,這里只配置了一個(gè)角度,顯示的時(shí)候底部肯定有問(wèn)題醇蝴,應(yīng)該只配置頂部的兩個(gè)圓角才對(duì)宣肚,這里給大家找了個(gè)鏈接:Glide 加載部分圓角圖片,作為一個(gè)懶漢悠栓,沒(méi)有做測(cè)試霉涨,哪位如果正好趕上有這個(gè)需求可以測(cè)試一下,我們直接進(jìn)入下一個(gè)原生方案惭适。
3 CardView
UI樣式的大趨勢(shì)笙瑟,作為Android的幕后大boss google怎么可能不知道呢,所以官方控件里也有這么一個(gè)小朋友可以實(shí)現(xiàn)這個(gè)功能癞志,那就是CardView往枷,直接上代碼:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_tv"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:background="#fefefe"
android:padding="15dp"
tools:ignore="SpUsage"
tools:viewBindingIgnore="true">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/test_img"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="centerInside"
android:src="@drawable/bg_scene" />
<View
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="20dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
然后看效果:
可以看到,前面的兩個(gè)問(wèn)題,在這里都直接搞定了错洁,背景圖也不用設(shè)置圓角了茅信,glide也不用設(shè)置圓角了,在CardView中添加了app:cardCornerRadius="5dp"后墓臭,一切問(wèn)題都迎刃而解了,是不是特別爽妖谴,特別開(kāi)心窿锉!
缺點(diǎn)分析
上面雖然說(shuō)實(shí)現(xiàn)了功能,但是能放到方案0上膝舅,就代表還有其他在我個(gè)人的能力水平內(nèi)能看到的更好的方案(具體是不是還是要交給大神們?cè)u(píng)估)嗡载,那就先說(shuō)說(shuō)上面的這些我所看到的缺點(diǎn)吧。
- xml只能對(duì)簡(jiǎn)單的色塊等做配置仍稀,如果是圖片的雖然也可以通過(guò)layer-list來(lái)完成拼接繪制洼滚,但是繪制難度跟在layout的xml中的繪制難度就不是一個(gè)量級(jí)的(比如位置的相對(duì)關(guān)系)
- glide針對(duì)的主要是圖片的處理,另外還有一個(gè)問(wèn)題對(duì)于加載中的網(wǎng)絡(luò)圖片的支持可以技潘,但是圖片加載前的占位圖和圖片加載后的缺省圖支持的就不是很好遥巴,一旦圖片請(qǐng)求失敗,展示個(gè)沒(méi)有圓角的圖享幽,測(cè)試絕對(duì)會(huì)來(lái)找你的麻煩
-
CardView看起來(lái)可以完美的規(guī)避掉上面兩種方案的問(wèn)題铲掐,但是可以看到,我們?cè)诶L制頁(yè)面的時(shí)候值桩,CardView中需要添加一個(gè)子View去繪制其中各個(gè)View的相對(duì)關(guān)系摆霉,畢竟CardView的父控件是FrameLayout,又沒(méi)有重寫onLayout方法奔坟,所以子View的展示邏輯携栋,可以理解為就是FrameLayout。這樣為了實(shí)現(xiàn)一個(gè)效果就需要多一層View嵌套咳秉。且同樣婉支,CardView配置圓角的時(shí)候,也是四個(gè)角同步配置的滴某,如果需要部分配置的時(shí)候也是不支持的磅摹。
CardView
那么我們就進(jìn)入到為大家準(zhǔn)備的其他方案。
方案一:自定義View圓角
這個(gè)方案就是通過(guò)declare-styleable配置圓角角度霎奢,然后在執(zhí)行onDraw的時(shí)候户誓,通過(guò)裁剪Carvas實(shí)現(xiàn)圓角效果,代碼如下:
<declare-styleable name="XXXView">
<!-- 通用圓角配置 -->
<attr format="dimension" name="radius"/>
<!-- 右上圓角 -->
<attr format="dimension" name="rightTopRadius"/>
<!-- 左上圓角 -->
<attr format="dimension" name="leftTopRadius"/>
<!-- 右下圓角 -->
<attr format="dimension" name="rightBottomRadius"/>
<!-- 左下圓角 -->
<attr format="dimension" name="leftBottomRadius"/>
</declare-styleable>
自定義View代碼
class XXXView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
private val mPath = Path()
private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
/**
* 左上角 圓角大小
*/
private var leftTopRadius: Float = 0f
/**
* 右上角 圓角大小
*/
private var rightTopRadius: Float = 0f
/**
* 左下角 圓角大小
*/
private var leftBottomRadius: Float = 0f
/**
* 右下角 圓角大小
*/
private var rightBottomRadius: Float = 0f
/**
* 全部圓角
*/
private var radius: Float = 0f
private val clipRectF = RectF()
init {
val obtainStyledAttributes = getContext().obtainStyledAttributes(attrs, R.styleable.XXXView)
radius= obtainStyledAttributes.getDimension(R.styleable.XXXView_radius, radius)
if (radius != 0f) {
//不知道為啥 不能 leftTopRadius=rightTopRadius=rightBottomRadius=leftBottomRadius = radius
leftTopRadius = radius
rightTopRadius = radius
rightBottomRadius = radius
leftBottomRadius = radius
} else {
leftTopRadius = obtainStyledAttributes.getDimension(R.styleable.XXXView_leftTopRadius, leftTopRadius)
rightTopRadius = obtainStyledAttributes.getDimension(R.styleable.XXXView_rightTopRadius, rightTopRadius)
leftBottomRadius = obtainStyledAttributes.getDimension(R.styleable.XXXView_leftBottomRadius, leftBottomRadius)
rightBottomRadius = obtainStyledAttributes.getDimension(R.styleable.XXXView_rightBottomRadius, rightBottomRadius)
}
obtainStyledAttributes.recycle()
}
override fun onDraw(canvas: Canvas?) {
//1.
//dx,dy 成對(duì)出現(xiàn)幕侠,控制上右下左帝美,四個(gè)位置圓角
val array = floatArrayOf(leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius);
clipRectF.set(0f, 0f, width.toFloat(), height.toFloat())
mPath.addRoundRect(clipRectF, array, Path.Direction.CW)
//2.
//3.
canvas?.clipPath(mPath)
//4.
super.onDraw(canvas)
}
}
自定義ViewGroup圓角(onDraw替換為dispatchDraw)
override fun dispatchDraw(canvas: Canvas) {
//1.
//dx,dy 成對(duì)出現(xiàn),控制上右下左晤硕,四個(gè)位置圓角
val array = floatArrayOf(leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius);
mPath.addRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat()), array, Path.Direction.CW)
//2.
//3.
canvas.clipPath(mPath)
//4.
super.dispatchDraw(canvas)
}
注:
1. canvas裁剪需要放置在super之前
2. 頻繁刷新UI悼潭,會(huì)導(dǎo)致頻繁在onDraw執(zhí)行canvas裁剪庇忌,出現(xiàn)黑色閃屏
方案二:ViewOutlineProvider
其實(shí)我較多的時(shí)候使用的還是方案一,直到一次的需求需要頻繁刷新UI舰褪,才發(fā)現(xiàn)了黑色閃屏的問(wèn)題皆疹,查找其他方案的時(shí)候,才知道的ViewOutlineProvider占拍,參考鏈接:Material Design :ViewOutlineProvider
demo代碼如下:
<declare-styleable name="XXXView">
<!-- 圓角角度 -->
<attr format="dimension" name="radius"/>
</declare-styleable>
圓角View
class XXXView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
private var radius = 0f
init {
val obtainStyledAttributes =
getContext().obtainStyledAttributes(attrs, R.styleable.XXXView)
radius =
obtainStyledAttributes.getDimension(R.styleable.XXXView_radius, radius)
obtainStyledAttributes.recycle()
clipToOutline = true
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline?) {
view?.let {
outline?.setRoundRect(
0 + paddingLeft,
0 + paddingTop,
it.width - paddingRight,
it.height - paddingBottom,
radius
)
}
}
}
}
// 業(yè)務(wù)代碼邏輯
// ...
}
布局:
<com.example.demo.widget.radius.RadiusView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:padding="15dp"
app:radius="50dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!--圖片自己網(wǎng)上隨便找一張就可以-->
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/test_img"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="centerInside"
android:src="@drawable/bg_scene" />
<View
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@android:color/holo_purple" />
</LinearLayout>
</com.example.demo.widget.radius.RadiusView>
效果如圖:
注:
1. 代碼中圓角的位置是會(huì)收到padding影響的略就,如果不想收到影響,去掉setRoundRect中的四個(gè)padding參數(shù)即可晃酒,具體依據(jù)具體需求調(diào)整
2. 由于ViewOutlineProvider api的限制表牢,暫時(shí)能配置的都是四個(gè)角統(tǒng)一角度,如果想要分別配置贝次,該方案不適用
方案三 切圖覆蓋
可以看到崔兴,上面的所有方案在特定的情況下都會(huì)有所限制,所以在相應(yīng)的場(chǎng)景下蛔翅,產(chǎn)品經(jīng)歷還是要求我們實(shí)現(xiàn)對(duì)應(yīng)的圓角效果敲茄,我們又不能讓他閉嘴的時(shí)候,只能再想其他方案搁宾。
這里的建議就是切圖覆蓋折汞,也就是讓UI切一個(gè)顏色與背景色相同的圓角邊框阻桅,覆蓋到需要圓角展示的View上方著摔,假裝實(shí)現(xiàn)了一個(gè)圓角的效果。
圓角圖片:
注:為了讓圓角圖片在文章中顯示不站太大的控件遏考,所以尺寸降低到了200翩腐,如果需要大尺寸鸟款,可以在圖片鏈接中修改.
繪制布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_tv"
android:layout_width="200dp"
android:layout_height="200dp"
tools:viewBindingIgnore="true">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/test_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/bg_scene" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/mask" />
</FrameLayout>
圓角效果:
優(yōu)點(diǎn):基本可以兼容產(chǎn)品的所有圓角樣式需求
缺點(diǎn):需要多繪制一層View,不夠優(yōu)雅
總結(jié)
上面列舉的就是我現(xiàn)階段想到的所有圓角效果的實(shí)現(xiàn)方案茂卦,在工作場(chǎng)景中何什,其實(shí)大多數(shù)情況也都?jí)蛴昧耍?dāng)然等龙,大家如果有其他的方案处渣,也歡迎分享一下,大家共同進(jìn)步蛛砰。
這里也說(shuō)過(guò)了罐栈,是沒(méi)有列舉第三方的方案的,都是一些原生的實(shí)現(xiàn)方案泥畅,雖然說(shuō)用第三方的方案很爽荠诬,但是大多數(shù)情況下,還是建議自己學(xué)習(xí)一下具體的實(shí)現(xiàn),較少一些不必要的引用柑贞。
另外方椎,關(guān)于最后一條要說(shuō)幾句,其實(shí)這個(gè)方案是一次看網(wǎng)課有學(xué)生提問(wèn)钧嘶,老師提到的方案棠众,結(jié)果是學(xué)生感覺(jué)這種方案是在忽悠他,最終不歡而散有决。其實(shí)說(shuō)實(shí)話摄欲,這個(gè)方案,我個(gè)人也不是那么建議使用的疮薇,只是有很多情況我們并沒(méi)有辦法很快的實(shí)現(xiàn)產(chǎn)品或者UI想要的一些效果,不得不使用一些折中的方案我注,也算是工作中的一種妥協(xié)和無(wú)奈按咒。
這里不是建議大家一點(diǎn)不用,但是最好應(yīng)付過(guò)關(guān)后但骨,在空閑時(shí)間思考一下励七,還有沒(méi)有其他好的解決方案豐富自己。
好了奔缠,祝大家新年快樂(lè)掠抬,開(kāi)年第一天的摸魚(yú)……還有什么可以做的呢,容我想想校哎,哈哈哈两波!