Android自定義View實現微信拍一拍的動畫效果
微信的頭像拍一拍效果可以說是很吸引人了,就是下面這個gif圖,圖片展示和實際效果還是有差距的,實際體驗效果更佳!
那麼我們如何通過自定義View,來實現微信的這種效果呢?
首先我們得把圖片做的像一個用戶的頭像,這裏我就用熱巴的頭像吧!
1.自定義View實現圓角矩形的頭像
因爲這樣才更像微信的頭像啊,原生的ImageView是沒有圓角的,所以實現一個圓角的ImageView
class ShakeImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
//圓角矩形的Path
private val path = Path()
private val paint = Paint().apply {
//開啓抗鋸齒
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
//開啓離屏緩衝
val count = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), paint)
//path添加一個圓角矩形
path.addRoundRect(0f, 0f, width.toFloat(), height.toFloat(), imageCorner, imageCorner, Path.Direction.CW)
//Canvas裁切成一個圓角矩形
canvas.clipPath(path)
//調用AppCompatImageView的onDraw方法
super.onDraw(canvas)
//恢復離屏緩衝
canvas.restoreToCount(count)
}
}
- 繼續AppCompatImageView重寫onDraw方法
- 開啓離屏緩衝,來拿出一塊離屏緩衝來繪製
- 給Path添加一個View大小的圓角矩形
- Canvas裁切成一個圓角矩形
- 調用AppCompatImageView的onDraw方法,在裁剪後的Canvas上繪製圖片
- 恢復離屏緩衝
通過以上的幾個步驟就能實現一個圓角矩形的頭像,注意要開啓緩衝
2.讓ShakeImageView能響應雙擊事件
爲了實現雙擊拍一拍的動畫,首先得讓自定義的View響應雙擊事件
class ShakeImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
//初始化GestureDetector
private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
//必須返回true 不然不能接管事件
override fun onDown(e: MotionEvent?) = true
//當雙擊的時候回調
override fun onDoubleTap(e: MotionEvent?): Boolean {
//處理動畫
return true
}
})
override fun onTouchEvent(event: MotionEvent?): Boolean {
//用gestureDetector來接管觸摸事件
return gestureDetector.onTouchEvent(event)
}
}
- 生命一個gestureDetector的實例,並且在onDown方法中返回true,代表接管事件
- 在onTouchEvent,用gestureDetector來接管觸摸事件
- GestureDetector相當於一個檢測器,而各種事件的響應會回調到SimpleOnGestureListener方法中
- SimpleOnGestureListener是一個實現了 OnGestureListener和OnDoubleTapListener的接口,用它更便捷
當雙擊的時候回調已經處理好了,接下來就要設置動畫了。
3.給ShakeImageView設置動畫
class ShakeImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
//圖片動畫執行的偏移距離
private var offset = 3f
//動畫執行的時間
private var duration = 500
//初始化動畫
private val animator by lazy {
ObjectAnimator.ofFloat(this, "rotation", offset, 0f, -offset, 0f, offset, 0f, -offset, 0f, offset, 0f)
}.apply {
this.value.duration = duration.toLong()
}
//初始化GestureDetector
private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
//必須返回true 不然不能接管事件
override fun onDown(e: MotionEvent?) = true
//當雙擊的時候回調
override fun onDoubleTap(e: MotionEvent?): Boolean {
shake()
return true
}
})
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
//軸心爲寬的一半,高的底部
pivotX = width / 2f
pivotY = height.toFloat()
}
//執行拍一拍的動畫
fun shake() {
animator.start()
}
}
- 通過Kotlin by lazy 初始化 animator動畫對象,並設置旋轉的距離和時間,注意X軸Y軸都要旋轉
- onSizeChanged方法中設置旋轉的軸心(width/2,height)軸心爲寬的一半,高的底部
- onDoubleTap 雙擊回調中,調用shake()方法執行動畫
這個效果通過這三個步驟就做出來了,如果你想做的更通用一點可以通過給自定義View配置屬性的方式,來讓你的自定義View更靈活。
4.設置自定義View的自定義屬性
比如可以在如下幾個方面來設置
- 是否開啓拍一拍效果
- 圖片的圓角大小
- 圖片動畫執行的偏移距離
- 動畫執行的時間
4.1.在value文件夾下創建一個attr.xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ShakeImageView">
<attr name="openShake" format="boolean" />
<attr name="imageCorner" format="dimension" />
<attr name="offset" format="float" />
<attr name="animDuration" format="integer" />
</declare-styleable>
</resources>
4.2.在代碼中獲取在xml配置的屬性
class ShakeImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
//開啓拍一拍效果
private var openShake = true
//圖片的圓角大小
private var imageCorner = 10.dp
//圖片動畫執行的偏移距離
private var offset = 3f
//動畫執行的時間
private var duration = 500
init {
//獲取xml中定義的值
val ta = context.obtainStyledAttributes(attrs, R.styleable.ShakeImageView)
openShake = ta.getBoolean(R.styleable.ShakeImageView_openShake, true)
imageCorner = ta.getDimension(R.styleable.ShakeImageView_imageCorner, 10.dp)
offset = ta.getFloat(R.styleable.ShakeImageView_offset, 3.5f)
duration = ta.getInt(R.styleable.ShakeImageView_animDuration, 350)
ta.recycle()
}
}
- 通過TypeArray來獲取對應的屬性
- 回收TypeArray
通過在init代碼塊中獲取這些屬性,如果沒聲明則給一個默認值,賦值給成員變量,在接下來的時候使用
4.3.使用在xml中獲取的屬性值
class ShakeImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
//開啓拍一拍效果
private var openShake = true
//圖片的圓角大小
private var imageCorner = 10.dp
//圖片動畫執行的偏移距離
private var offset = 3f
//動畫執行的時間
private var duration = 500
//初始化動畫
private val animator by lazy {
ObjectAnimator.ofFloat(this, "rotation", offset, 0f, -offset, 0f, offset, 0f, -offset, 0f, offset, 0f)
}.apply {
this.value.duration = duration.toLong()
}
override fun onDraw(canvas: Canvas) {
//開啓離屏緩衝
val count = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), paint)
//path添加一個圓角矩形
path.addRoundRect(0f, 0f, width.toFloat(), height.toFloat(), imageCorner, imageCorner, Path.Direction.CW)
//Canvas裁切成一個圓角矩形
canvas.clipPath(path)
//調用AppCompatImageView的onDraw方法
super.onDraw(canvas)
//恢復離屏緩衝
canvas.restoreToCount(count)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
//開啓拍一拍,則用gestureDetector來接管觸摸事件 否則歐系統默認的觸摸流程
return if (openShake) gestureDetector.onTouchEvent(event) else super.onTouchEvent(event)
}
}
- openShake:開啓拍一拍,則用gestureDetector來接管觸摸事件 否則走系統默認的觸摸流程
- imageCorner:在path.addRoundRect()中進行配置
- offset和duration在動畫初始化中使用,注意animator的初始化一定要寫在init代碼塊下面,不然你取到的不是最新的值
4.4.在xml中配置屬性
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".customviewcollection.MainActivity">
<com.jhb.customviewcollection.shakeimageview.ShakeImageView
android:id="@+id/siv"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/dlrb"
android:scaleType="centerCrop"
app:imageCorner="10dp"
app:openShake="true"
app:animDuration="500"
app:offset="3.5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
好了,這就是自定義屬性的使用方式,很簡單吧!
5.總結
本文通過自定義View,先實現了,圓角矩形頭像,然後增加了雙擊的監聽,接着通過動畫,一步一步實現了微信拍一拍的效果,最後還實現了自定義配置屬性。
這是一個非常好的練手Demo了
6.源碼地址
7.原文地址
文末
喜歡的朋友點個關注吧分享Android乾貨,交流Android技術。一個喜歡自定義View和NDK,研究源碼的小喵喵
對文章有何見解,或者有何技術問題,都可以在評論區一起留言討論,都會看的哦。
也歡迎大家來我的B站找我玩,有各類Android架構師進階技術難點的視頻講解
B站直通車:https://space.bilibili.com/544650554