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

