詳解Android如何實(shí)現(xiàn)不同大小的圓角
背景圓角
Shape
對(duì)于一般的背景,我們可以直接使用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ò)誤寫(xiě)法
有人發(fā)文說(shuō),可以通過(guò)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)過(guò)實(shí)測(cè),這樣寫(xiě)是不行的,準(zhǔn)確的來(lái)說(shuō),在大部分系統(tǒng)上是不行的(MIUI上可以,我不知道是該夸它兼容性太好了還是該吐槽它啥,我的測(cè)試機(jī)用的小米,這導(dǎo)致我在最后的測(cè)試階段才發(fā)現(xiàn)這個(gè)問(wèn)題)
指出錯(cuò)誤方法后,讓我們來(lái)看看正確解法有哪些
CardView
說(shuō)到切內(nèi)容圓角,我們自然而然會(huì)去想到CardView
,其實(shí)CardView
的圓角也是通過(guò)Outline
實(shí)現(xiàn)的
有人可能要問(wèn)了,CardView
不是只支持四角相同radius
嗎?別急,且看我靈機(jī)一動(dòng)想出來(lái)的神奇嵌套大法
神奇嵌套大法
既然一個(gè)CardView
只能設(shè)一個(gè)radius
,那我多用幾個(gè)CardView
嵌套是否能解決問(wèn)題呢?
舉個(gè)最簡(jiǎn)單的例子,比如說(shuō)設(shè)計(jì)想要上半部分為12dp
的圓角,下半部分沒(méi)有圓角,我們需要一個(gè)輔助View
,讓他的頂部和父布局的底部對(duì)齊,然后設(shè)置成圓角大小的高度或者margin
,接著使用CardView
,讓它的底部對(duì)齊這個(gè)輔助View
的底部,再設(shè)置一個(gè)圓角大小的padding
,這樣,由于CardView
超出了父布局的邊界,所以底部的圓角不會(huì)顯示出來(lái),再由于我們?cè)O(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>
上面的例子沒(méi)有嵌套,因?yàn)榱硪贿厸](méi)有圓角,那么如果我們需要上半部分為12dp
的圓角,下半部分為6dp
的圓角,我們可以這樣操作
手法和上面的例子一樣,不過(guò)我們?cè)谧钔鈱釉偾短滓粋€(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ì)覆蓋小圓角裁切的范圍,從視覺(jué)上看就實(shí)現(xiàn)了兩邊的不同圓角
那么如果我們想進(jìn)一步實(shí)現(xiàn)三邊不同圓角或者四邊不同圓角呢?原理和上面是一樣的,只不過(guò)嵌套和占位會(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)容都是圖片,所以我們也可以直接對(duì)圖片進(jìn)行裁切,此時(shí)我們就可以自定義ImageView
來(lái)將圖片裁剪出不同大小的圓角
clipPath
先說(shuō)這個(gè)方法的缺點(diǎn),那就是無(wú)法使用抗鋸齒,這一點(diǎn)缺陷注定了它無(wú)法被正式使用,但我們還是來(lái)看看他是如何實(shí)現(xiàn)的
首先,我們需要重寫(xiě)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) }
接著我們重寫(xiě)onDraw
方法
override fun onDraw(canvas: Canvas) { canvas.clipPath(path) super.onDraw(rawBitmapCanvas) }
網(wǎng)上有的教程說(shuō)要設(shè)置PaintFlagsDrawFilter
,但實(shí)際上就算為這個(gè)PaintFlagsDrawFilter
設(shè)置了Paint.ANTI_ALIAS_FLAG
抗鋸齒屬性也沒(méi)用,抗鋸齒只在使用了Paint
的情況下才可以生效
PorterDuff
既然clipPath
無(wú)法使用抗鋸齒,那我們可以換一條路線曲線救國(guó),那就是使用PorterDuff
當(dāng)然,這種方法也有它的缺點(diǎn),那就是不能使用硬件加速,但相比無(wú)法使用抗鋸齒而言,這點(diǎn)缺點(diǎn)也就不算什么了
首先,我們要在構(gòu)造方法中禁用硬件加速
init { setLayerType(LAYER_TYPE_SOFTWARE, null) }
然后重寫(xiě)onSizeChanged
方法,在這個(gè)方法中,我們需要確定Path
,構(gòu)造出相應(yīng)大小的Bitmap
和Canvas
,這倆是用來(lái)獲取原始無(wú)圓角的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!!) }
接著我們重寫(xiě)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
方法,獲取到原始無(wú)圓角的Bitmap
,然后繪制Path
,再通過(guò)PorterDuff
的疊加效果繪制我們剛剛得到的原始Bitmap
,由于PorterDuff.Mode.SRC_IN
的效果是取兩層繪制交集,顯示上層,所以我們最終便獲得了一個(gè)帶圓角的圖片
截圖問(wèn)題
如果想要將View
截圖成Bitmap
,在Android 8.0
及以上系統(tǒng)中我們可以使用PixelCopy
,此時(shí)使用CardView
或Outline
裁切的圓角不會(huì)有任何問(wèn)題,而在Android 8.0
以下的系統(tǒng)中,通常我們是構(gòu)建一個(gè)帶Bitmap
的Canvas
,然后對(duì)要截圖的View
調(diào)用draw
方法達(dá)成截圖效果,而在這種情況下,使用CardView
或Outline
裁切的圓角便會(huì)出現(xiàn)無(wú)效的情況(截圖出來(lái)的Bitmap
中,圓角沒(méi)了),這種情況的出現(xiàn)似乎也和硬件加速有關(guān),針對(duì)這種問(wèn)題,我個(gè)人的想法是準(zhǔn)備兩套布局,8.0
以上使用CardView
或Outline
,截圖使用PixelCopy
,8.0
以下使用PorterDuff
方案直接裁切圖片,最大程度避免性能損耗
總結(jié)
以上就是我本人目前對(duì)Android
實(shí)現(xiàn)不同大小的圓角的一些想法和遇到的問(wèn)題,至于CardView
嵌套會(huì)不會(huì)帶來(lái)什么性能問(wèn)題,我目前并沒(méi)有做驗(yàn)證,各位小伙伴有什么更好的解決方案,歡迎在評(píng)論區(qū)指出,大家一起集思廣益。
到此這篇關(guān)于詳解Android如何實(shí)現(xiàn)不同大小的圓角的文章就介紹到這了,更多相關(guān)Android實(shí)現(xiàn)不同大小的圓角內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android實(shí)現(xiàn)常駐通知欄遇到的問(wèn)題及解決辦法
這篇文章主要介紹了android實(shí)現(xiàn)常駐通知欄遇到的問(wèn)題及解決辦法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Android中實(shí)現(xiàn)密碼的隱藏和顯示的示例
本篇文章主要介紹了Android中實(shí)現(xiàn)密碼的隱藏和顯示的示例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-09-09Android仿簡(jiǎn)書(shū)動(dòng)態(tài)searchview搜索欄效果
這篇文章主要為大家詳細(xì)介紹了Android仿簡(jiǎn)書(shū)動(dòng)態(tài)searchview效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Android自定義帶動(dòng)畫(huà)效果的圓形ProgressBar
這篇文章主要為大家詳細(xì)介紹了Android自定義帶動(dòng)畫(huà)效果的圓形ProgressBar,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05Android 數(shù)據(jù)庫(kù)SQLite 寫(xiě)入SD卡的方法
如果手機(jī)沒(méi)有root,數(shù)據(jù)庫(kù)文件是無(wú)法查看到的,不方便調(diào)試。最好的辦法是把數(shù)據(jù)庫(kù)寫(xiě)進(jìn)SD卡。通過(guò)本文給大家介紹Android 數(shù)據(jù)庫(kù)SQLite 寫(xiě)入SD卡的方法,需要的朋友參考下吧2016-04-04Android自定義流式布局實(shí)現(xiàn)淘寶搜索記錄
這篇文章主要為大家詳細(xì)介紹了Android自定義流式布局實(shí)現(xiàn)淘寶搜索記錄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10