詳解Android如何實(shí)現(xiàn)不同大小的圓角
背景圓角
Shape
對于一般的背景,我們可以直接使用shape,這種方法天生支持設(shè)置四角不同的radius,比如:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#8358FF" />
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="20dp"
android:topLeftRadius="30dp"
android:topRightRadius="40dp" />
</shape>小貼士:shape在代碼層的實(shí)現(xiàn)為GradientDrawable,可以直接在代碼層構(gòu)建圓角背景
內(nèi)容圓角
很多情況下,設(shè)置背景的四邊不同圓角并不能滿足我們,大多數(shù)情況下,我們需要連著里面的內(nèi)容一起切圓角,這里我們需要先指正一下網(wǎng)上的一個(gè)錯(cuò)誤寫法
有人發(fā)文說,可以通過outline.setConvexPath方法,實(shí)現(xiàn)四角不同radius,如下:
outline?.setConvexPath(
Path().apply {
addRoundRect(
0f, 0f, width.toFloat(), height.toFloat(),
floatArrayOf(
topLeftRadius,
topLeftRadius,
topRightRadius,
topRightRadius,
bottomRightRadius,
bottomRightRadius,
bottomLeftRadius,
bottomLeftRadius
),
Path.Direction.CCW
)
}
)經(jīng)過實(shí)測,這樣寫是不行的,準(zhǔn)確的來說,在大部分系統(tǒng)上是不行的(MIUI上可以,我不知道是該夸它兼容性太好了還是該吐槽它啥,我的測試機(jī)用的小米,這導(dǎo)致我在最后的測試階段才發(fā)現(xiàn)這個(gè)問題)
指出錯(cuò)誤方法后,讓我們來看看正確解法有哪些
CardView
說到切內(nèi)容圓角,我們自然而然會(huì)去想到CardView,其實(shí)CardView的圓角也是通過Outline實(shí)現(xiàn)的
有人可能要問了,CardView不是只支持四角相同radius嗎?別急,且看我靈機(jī)一動(dòng)想出來的神奇嵌套大法
神奇嵌套大法
既然一個(gè)CardView只能設(shè)一個(gè)radius,那我多用幾個(gè)CardView嵌套是否能解決問題呢?
舉個(gè)最簡單的例子,比如說設(shè)計(jì)想要上半部分為12dp的圓角,下半部分沒有圓角,我們需要一個(gè)輔助View,讓他的頂部和父布局的底部對齊,然后設(shè)置成圓角大小的高度或者margin,接著使用CardView,讓它的底部對齊這個(gè)輔助View的底部,再設(shè)置一個(gè)圓角大小的padding,這樣,由于CardView超出了父布局的邊界,所以底部的圓角不會(huì)顯示出來,再由于我們設(shè)置了恰好的padding,所以CardView里面的內(nèi)容也能完整展示,可謂完美,實(shí)例如下:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Space
android:id="@+id/guideline"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="parent" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:contentPaddingBottom="12dp"
app:layout_constraintBottom_toBottomOf="@+id/guideline">
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
android:adjustViewBounds="true"
android:background="#8358FF" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>上面的例子沒有嵌套,因?yàn)榱硪贿厸]有圓角,那么如果我們需要上半部分為12dp的圓角,下半部分為6dp的圓角,我們可以這樣操作
手法和上面的例子一樣,不過我們在最外層再嵌套一個(gè)CardView,并且將其圓角設(shè)為較小的那個(gè)圓角大小6dp,將里面的CardView的圓角設(shè)置成較大的那個(gè)圓角大小12dp,具體實(shí)現(xiàn)如下:
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="6dp"
app:cardElevation="0dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Space
android:id="@+id/guideline"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="parent" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:contentPaddingBottom="12dp"
app:layout_constraintBottom_toTopOf="@+id/guideline">
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
android:adjustViewBounds="true"
android:background="#8358FF" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>本質(zhì)上就是大圓角套小圓角,大圓角的裁切范圍更大,會(huì)覆蓋小圓角裁切的范圍,從視覺上看就實(shí)現(xiàn)了兩邊的不同圓角
那么如果我們想進(jìn)一步實(shí)現(xiàn)三邊不同圓角或者四邊不同圓角呢?原理和上面是一樣的,只不過嵌套和占位會(huì)變得更加復(fù)雜,記住一個(gè)原則,小圓角在外,大圓角在內(nèi)即可,我直接把具體實(shí)現(xiàn)貼在下面,各位自取即可:
- 三邊不同圓角(左下6dp,左上12dp,右上24dp)
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Space
android:id="@+id/guideline"
android:layout_width="6dp"
android:layout_height="match_parent"
app:layout_constraintStart_toEndOf="parent" />
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="6dp"
app:cardElevation="0dp"
app:contentPaddingRight="6dp"
app:layout_constraintEnd_toEndOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Space
android:id="@+id/guideline2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="parent" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:contentPaddingBottom="12dp"
app:layout_constraintBottom_toTopOf="@+id/guideline2">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Space
android:id="@+id/guideline3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toStartOf="parent" />
<Space
android:id="@+id/guideline4"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="parent" />
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="24dp"
app:cardElevation="0dp"
app:contentPaddingBottom="24dp"
app:contentPaddingLeft="24dp"
app:layout_constraintBottom_toBottomOf="@+id/guideline4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline3">
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
android:adjustViewBounds="true"
android:background="#8358FF" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>- 四邊不同圓角(左下6dp,左上12dp,右上24dp,右下48dp)
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Space
android:id="@+id/guideline"
android:layout_width="6dp"
android:layout_height="match_parent"
app:layout_constraintStart_toEndOf="parent" />
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="6dp"
app:cardElevation="0dp"
app:contentPaddingRight="6dp"
app:layout_constraintEnd_toEndOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Space
android:id="@+id/guideline2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="parent" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:contentPaddingBottom="12dp"
app:layout_constraintBottom_toTopOf="@+id/guideline2">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Space
android:id="@+id/guideline3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toStartOf="parent" />
<Space
android:id="@+id/guideline4"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="parent" />
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="24dp"
app:cardElevation="0dp"
app:contentPaddingBottom="24dp"
app:contentPaddingLeft="24dp"
app:layout_constraintBottom_toBottomOf="@+id/guideline4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline3">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Space
android:id="@+id/guideline5"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="48dp"
app:layout_constraintEnd_toStartOf="parent" />
<Space
android:id="@+id/guideline6"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="48dp"
app:layout_constraintBottom_toTopOf="parent" />
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="48dp"
app:cardElevation="0dp"
app:contentPaddingLeft="48dp"
app:contentPaddingTop="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline5"
app:layout_constraintTop_toTopOf="@+id/guideline6">
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
android:adjustViewBounds="true"
android:background="#8358FF" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>自定義ImageView
由于大部分裁切內(nèi)容的需求,其中的內(nèi)容都是圖片,所以我們也可以直接對圖片進(jìn)行裁切,此時(shí)我們就可以自定義ImageView來將圖片裁剪出不同大小的圓角
clipPath
先說這個(gè)方法的缺點(diǎn),那就是無法使用抗鋸齒,這一點(diǎn)缺陷注定了它無法被正式使用,但我們還是來看看他是如何實(shí)現(xiàn)的
首先,我們需要重寫ImageView的onSizeChanged方法,為我們的Path確定路線
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
path.reset()
//這里的radii便是我們自定義的四邊圓角大小的數(shù)組(size為8,從左上順時(shí)針到左下)
path.addRoundRect(0f, 0f, w.toFloat(), h.toFloat(), radii, Path.Direction.CW)
}接著我們重寫onDraw方法
override fun onDraw(canvas: Canvas) {
canvas.clipPath(path)
super.onDraw(rawBitmapCanvas)
}網(wǎng)上有的教程說要設(shè)置PaintFlagsDrawFilter,但實(shí)際上就算為這個(gè)PaintFlagsDrawFilter設(shè)置了Paint.ANTI_ALIAS_FLAG抗鋸齒屬性也沒用,抗鋸齒只在使用了Paint的情況下才可以生效
PorterDuff
既然clipPath無法使用抗鋸齒,那我們可以換一條路線曲線救國,那就是使用PorterDuff
當(dāng)然,這種方法也有它的缺點(diǎn),那就是不能使用硬件加速,但相比無法使用抗鋸齒而言,這點(diǎn)缺點(diǎn)也就不算什么了
首先,我們要在構(gòu)造方法中禁用硬件加速
init {
setLayerType(LAYER_TYPE_SOFTWARE, null)
}然后重寫onSizeChanged方法,在這個(gè)方法中,我們需要確定Path,構(gòu)造出相應(yīng)大小的Bitmap和Canvas,這倆是用來獲取原始無圓角的Bitmap的
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
path.reset()
path.addRoundRect(0f, 0f, w.toFloat(), h.toFloat(), radii, Path.Direction.CW)
rawBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
rawBitmapCanvas = Canvas(rawBitmap!!)
}接著我們重寫onDraw方法
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
override fun onDraw(canvas: Canvas) {
val rawBitmap = rawBitmap ?: return
val rawBitmapCanvas = rawBitmapCanvas ?: return
super.onDraw(rawBitmapCanvas)
canvas.drawPath(path, paint)
paint.xfermode = xfermode
canvas.drawBitmap(rawBitmap, 0f, 0f, paint)
paint.xfermode = null
}這里,我們調(diào)用父類的onDraw方法,獲取到原始無圓角的Bitmap,然后繪制Path,再通過PorterDuff的疊加效果繪制我們剛剛得到的原始Bitmap,由于PorterDuff.Mode.SRC_IN的效果是取兩層繪制交集,顯示上層,所以我們最終便獲得了一個(gè)帶圓角的圖片
截圖問題
如果想要將View截圖成Bitmap,在Android 8.0及以上系統(tǒng)中我們可以使用PixelCopy,此時(shí)使用CardView或Outline裁切的圓角不會(huì)有任何問題,而在Android 8.0以下的系統(tǒng)中,通常我們是構(gòu)建一個(gè)帶Bitmap的Canvas,然后對要截圖的View調(diào)用draw方法達(dá)成截圖效果,而在這種情況下,使用CardView或Outline裁切的圓角便會(huì)出現(xiàn)無效的情況(截圖出來的Bitmap中,圓角沒了),這種情況的出現(xiàn)似乎也和硬件加速有關(guān),針對這種問題,我個(gè)人的想法是準(zhǔn)備兩套布局,8.0以上使用CardView或Outline,截圖使用PixelCopy,8.0以下使用PorterDuff方案直接裁切圖片,最大程度避免性能損耗
總結(jié)
以上就是我本人目前對Android實(shí)現(xiàn)不同大小的圓角的一些想法和遇到的問題,至于CardView嵌套會(huì)不會(huì)帶來什么性能問題,我目前并沒有做驗(yàn)證,各位小伙伴有什么更好的解決方案,歡迎在評(píng)論區(qū)指出,大家一起集思廣益。
到此這篇關(guān)于詳解Android如何實(shí)現(xiàn)不同大小的圓角的文章就介紹到這了,更多相關(guān)Android實(shí)現(xiàn)不同大小的圓角內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android實(shí)現(xiàn)常駐通知欄遇到的問題及解決辦法
這篇文章主要介紹了android實(shí)現(xiàn)常駐通知欄遇到的問題及解決辦法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
Android中實(shí)現(xiàn)密碼的隱藏和顯示的示例
本篇文章主要介紹了Android中實(shí)現(xiàn)密碼的隱藏和顯示的示例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-09-09
Android仿簡書動(dòng)態(tài)searchview搜索欄效果
這篇文章主要為大家詳細(xì)介紹了Android仿簡書動(dòng)態(tài)searchview效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Android自定義帶動(dòng)畫效果的圓形ProgressBar
這篇文章主要為大家詳細(xì)介紹了Android自定義帶動(dòng)畫效果的圓形ProgressBar,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05
Android 數(shù)據(jù)庫SQLite 寫入SD卡的方法
如果手機(jī)沒有root,數(shù)據(jù)庫文件是無法查看到的,不方便調(diào)試。最好的辦法是把數(shù)據(jù)庫寫進(jìn)SD卡。通過本文給大家介紹Android 數(shù)據(jù)庫SQLite 寫入SD卡的方法,需要的朋友參考下吧2016-04-04
Android自定義流式布局實(shí)現(xiàn)淘寶搜索記錄
這篇文章主要為大家詳細(xì)介紹了Android自定義流式布局實(shí)現(xiàn)淘寶搜索記錄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10

