本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布[2022-8-26]
本系列自定义View全部选用kt
体系:mac
android studio: 4.1.3
kotlin version:1.5.0
gradle: gradle-6.5-bin.zip
废话不多说,先来看今天要完结的作用:
作用一 | 作用二 |
---|---|
作用二是在作用一的根底上改的,能够经过一行代码,让所有控件都能实现拖拽作用!
所以先来编写作用一的代码~
根底制作
首先编写一下根底代码:
class TempView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {
companion object {
// 大圆半径
private val BIG_RADIUS = 50.dp
// 小圆半径
private val SMALL_RADIUS = BIG_RADIUS * 0.618f
// 最大规模(半径),超出这个规模大圆不显现
private val MAX_RADIUS = 150.dp
}
private val paint = Paint().apply {
color = Color.RED
}
// 大圆初始方位
private val bigPointF by lazy { PointF(width / 2f + 300, height / 2f) }
// 小圆初始方位
private val smallPointF by lazy { PointF(width / 2f, height / 2f) }
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = Color.RED
// 制作大圆
canvas.drawCircle(bigPointF.x, bigPointF.y, BIG_RADIUS, paint)
// 制作小圆
canvas.drawCircle(smallPointF.x, smallPointF.y, SMALL_RADIUS, paint)
// 制作辅佐圆
paint.color = Color.argb(20, 255, 0, 0)
canvas.drawCircle(smallPointF.x, smallPointF.y, MAX_RADIUS, paint)
}
}
现在作用:
这段代码很简单,都是一些根底api的调用,
辅佐圆的作用:
-
当大圆超出辅佐圆规模的时分,大圆得“爆破”,
-
假设大圆未超出辅佐圆内的话,大圆得回弹回去~
首要便是起到这样的作用.
大圆动起来
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
}
MotionEvent.ACTION_MOVE -> {
bigPointF.x = event.x
bigPointF.y = event.y
}
MotionEvent.ACTION_UP -> {
}
}
invalidate()
return true // 消费事情
}
大圆动起来很简单,只需求在ACTION_MOVE中一向刷新移动方位即可
辅佐图1.1
:
咱们想要的作用是手指按下之后,大圆跟着移动,
在辅佐图1.1
后半段能够看出这儿有一个小问题, 手指按什么方位小球就移动到什么方位,不是咱们想要的作用
那么咱们知道所有的事情都是在DOWN平分发出来的,
所以只需求在DOWN事情中判别当时是否点击到大圆即可,
// 标记是否选中了大圆
var isMove = false
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 判别当时点击区域是否在大圆规模内
isMove = bigPointF.contains(PointF(event.x, event.y), BIG_RADIUS)
}
MotionEvent.ACTION_MOVE -> {
if (isMove) {
bigPointF.x = event.x
bigPointF.y = event.y
}
}
}
invalidate()
return true // 消费事情
}
contains是自己写的一个扩展函数:
// 判别一个点是否在另一个点内
fun PointF.contains(b: PointF, bPadding: Float = 0f): Boolean {
val isX = this.x <= b.x + bPadding && this.x >= b.x - bPadding
val isY = this.y <= b.y + bPadding && this.y >= b.y - bPadding
return isX && isY
}
辅佐图1.2
:
大圆超出辅佐圆规模就消失
有了PointF.contains() 这个扩展,任务就变得轻松起来了
只需求在制作的时分判别一下当时方位即可
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 大圆方位是否在辅佐圆内
if(bigPointF.contains(smallPointF, MAX_RADIUS)){
// 制作大圆
canvas.drawCircle(bigPointF.x, bigPointF.y, BIG_RADIUS, paint)
}
// 制作小圆
...
// 制作辅佐圆
...
}
辅佐图1.3
:
大圆越往外,小球越小
要想求出大圆是否越往外,那么就得先核算出当时大圆与小圆的间隔
辅佐图1.4
:
-
dx = 大圆.x – 小圆.x
-
dy = 大圆.y – 小圆.y
经过勾股定理就能够核算出他们之间的间隔
// 小圆与大圆之间的间隔
private fun distance(): Float {
val current = bigPointF - smallPointF
return sqrt(current.x.toDouble().pow(2.0) + (current.y.toDouble().pow(2.0))).toFloat()
}
bigPointF – smallPointF 选用的是ktx中自带的运算符重载函数
知道大圆和小圆的间隔之后,就能够核算出份额
份额 = 间隔 / 总长度
// 大圆与小圆之间的间隔
val d = distance()
// 总长度
var ratio = d / MAX_RADIUS
// 假设当时份额 > 0.618 那么就让=0.618
if (ratio > 0.618) {
ratio = 0.618f
}
为什么要选0.618,
0.618是黄金份额切割点,传闻选了0.618制作出来的东西会很和谐?
我一个糙人也看不出来美不美, 可能是看着更专业一点吧.
完好制作小圆代码:
//小圆半径
private val SMALL_RADIUS = BIG_RADIUS
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 制作大圆
...
// 两圆之间的间隔
val d = distance()
var ratio = d / MAX_RADIUS
if (ratio > 0.618) {
ratio = 0.618f
}
// 小圆半径
val smallRadius = SMALL_RADIUS - SMALL_RADIUS * ratio
// 制作小圆
canvas.drawCircle(smallPointF.x, smallPointF.y, smallRadius, paint)
// 制作辅佐圆
...
}
辅佐图1.5
:
制作贝塞尔曲线
接下来只需求求出这4个点连接起来 , 看起来就像是把他们连接起来了
然后在找到一个控制点, 经过贝塞尔曲线让他略微曲折即可
辅佐图1.6
:
P1
辅佐图1.7
:
终究便是算出角A的坐标即可
现在已知
- 角A.x = 小圆.x + BC;
- 角A.y = 小圆.y – AC ;
Tips: 由于角A的坐标在小圆中心点上面, 在android坐标系中 角A.y = 小圆.y – AC ;
- 角C = 90度;
- 角ABD = 90度
角ABC + 角BAC = 90度; 角ABC +角CBD = 90度;
所以角BAC = 角CBD
BC 平行于 FD,那么角BDF = 角CBD = 角A
终究只需求出角BDF就算出了角A
假定现在知道角A, AB的长度 = 小圆的半径
就能够算出:
- BC = AB * sin(角A)
- AC = AB * cos(角A)
现在已知BF 和 FD的间隔
角BDF = arctan(BF / FD)
那么现在就核算出了角A的视点
-
p1X = 小圆.x + 小圆半径 * sin(角A)
-
p1Y = 小圆.y – 小圆半径 * cos(角A)
P2
辅佐图1.8
:
现在要求出P2的方位,也便是角E的方位
- 角E.x = 大圆.x + DG
- 角E.y = 大圆.y + EG
角BDE = 90度;
角BDF + 角EDG = 90度
那么角E = 角BDF
P1刚刚核算了角BDF,仍是热的.
- P2.x =大圆.x + DE * sin(角E)
- P2.y = 大圆.y – DE * cos(角E)
P3
辅佐图1.9
:
P3便是角K的方位
- 角K.x = 小圆.x – KH
- 角K.y = 小圆.y – BH
角KBH + 角HBD = 90度
角BDF + 角HBD = 90度
所以角KBH + 角BDF
KH = BK * sin(角KBH)
BK = BK * cos(角KBH)
-
P3.x = 小圆.x – KH
-
P3.y = 小圆.y – BH
P4
辅佐图1.10
:
-
角A.x = 大圆.x – CD
-
角A.y = 大圆.y + AC
角A + 角ADC = 90度
角BDF + 角ADC = 90度
所以角A = 角BDF
CD = AD * sin(角A)
AC = AD * cos(角A)
- P4.x = 大圆.x – CD
- p4.y = 大圆.y – AC
控制点
控制点就选大圆与小圆的中点即可
控制点.x = (大圆.x – 小圆.x) / 2 + 小圆.x
控制点.y = (大圆.y – 小圆.y) / 2 + 小圆.y
来看看完好代码:
/*
* 作者:史大拿
* @param smallRadius: 小圆半径
* @param bigRadius: 大圆半径
*/
private fun drawBezier(canvas: Canvas, smallRadius: Float, bigRadius: Float) {
val current = bigPointF - smallPointF
val BF = current.y.toDouble()
val FD = current.x.toDouble()
//
val BDF = atan(BF / FD)
val p1X = smallPointF.x + smallRadius * sin(BDF)
val p1Y = smallPointF.y - smallRadius * cos(BDF)
val p2X = bigPointF.x + bigRadius * sin(BDF)
val p2Y = bigPointF.y - bigRadius * cos(BDF)
val p3X = smallPointF.x - smallRadius * sin(BDF)
val p3Y = smallPointF.y + smallRadius * cos(BDF)
val p4X = bigPointF.x - bigRadius * sin(BDF)
val p4Y = bigPointF.y + bigRadius * cos(BDF)
// 控制点
val controlPointX = current.x / 2 + smallPointF.x
val controlPointY = current.y / 2 + smallPointF.y
val path = Path()
path.moveTo(p1X.toFloat(), p1Y.toFloat()) // 移动到p1方位
path.quadTo(controlPointX, controlPointY, p2X.toFloat(), p2Y.toFloat()) // 制作贝塞尔
path.lineTo(p4X.toFloat(), p4Y.toFloat()) // 连接到p4
path.quadTo(controlPointX, controlPointY, p3X.toFloat(), p3Y.toFloat()) // 制作贝塞尔
path.close() // 连接到p1
canvas.drawPath(path, paint)
}
调用:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = Color.RED
// 两圆之间的间隔
val d = distance()
var ratio = d / MAX_RADIUS
if (ratio > 0.618) {
ratio = 0.618f
}
// 小圆半径
val smallRadius = SMALL_RADIUS - SMALL_RADIUS * ratio
// 制作小圆
canvas.drawCircle(smallPointF.x, smallPointF.y, smallRadius, paint)
// 大圆方位是否在辅佐圆内
if (bigPointF.contains(smallPointF, MAX_RADIUS)) {
// 制作大圆
canvas.drawCircle(bigPointF.x, bigPointF.y, BIG_RADIUS, paint)
// 制作贝塞尔
drawBezier(canvas,smallRadius, BIG_RADIUS)
}
// 制作辅佐圆
...
}
辅佐图1.11
:
能够看出,根本作用现已到达了,可是这个大圆看着很大,总感觉有地方不和谐
那是由于这些参数都是我自己随意写的,到时分这些参数UI都会给你,必定没有这么随意..
能够先吧大圆半径缩小一点再看看作用怎么
辅佐图1.12
:
看着作用其实还能够.
拖动回弹
拖动回弹是指当拖动大圆时分,没有超出辅佐圆的规模, 此刻大圆还在辅佐圆规模内,
那么就需求将大圆回弹到小圆方位上.
那么必定是松手(ACTION_UP)事情的时分来处理:
private fun bigAnimator(): ValueAnimator {
return ObjectAnimator.ofObject(this, "bigPointF", PointFEvaluator(),
PointF(width / 2f, height / 2f)).apply {
duration = 400
interpolator = OvershootInterpolator(3f) // 设置回弹迭代器
}
}
常见插值器:
- AccelerateDecelerateInterpolator 动画从开端到完毕,改动率是先加速后减速的过程。
- AccelerateInterpolator 动画从开端到完毕,改动率是一个加速的过程。
- AnticipateInterpolator 开端的时分向后,然后向前甩
- AnticipateOvershootInterpolator 开端的时分向后,然后向前甩一定值后返回最后的值
- BounceInterpolator 动画完毕的时分弹起
- CycleInterpolator 动画从开端到完毕,改动率是循环给定次数的正弦曲线。
- DecelerateInterpolator 动画从开端到完毕,改动率是一个减速的过程。
- LinearInterpolator 以常量速率改动
- OvershootInterpolator 完毕时分向反方向甩某段间隔
插值器参考链接
小插曲:
最开端初始化大圆方位为:
private val bigPointF by lazy { PointF(width / 2f + 300, height / 2f) }
此刻经过动画来改动bigPointF必定是不可取的,由于他是懒加载
所以要修正初始化代码为:
var bigPointF = PointF(0f, 0f)
set(value) {
field = value
invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh
bigPointF.x = width / 2f
bigPointF.y = height / 2f
}
假设对为什么要在onSizeChanged中调用不明白的主张看一下View生命周期
调用:
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
....
MotionEvent.ACTION_UP -> {
// 大圆是否在辅佐圆规模内
if (bigPointF.contains(smallPointF, MAX_RADIUS)) {
// 回弹
bigAnimator().start()
} else {
// 爆破
}
}
}
invalidate()
return true // 消费事情
}
辅佐图1.13
:
最后当大圆拖动到辅佐圆外的时分,在UP方位制作爆破作用,
而且当爆破作用完毕时分,吧大圆x,y坐标回到小圆坐标即可!
制作爆破作用
爆破作用其实便是20张图片一向在切换,到达一帧一帧的作用即可
private val explodeImages by lazy {
val list = arrayListOf<Bitmap>()
// BIG_RADIUS = 大圆半径
val width = BIG_RADIUS * 2 * 2
list.add(getBitMap(R.mipmap.explode_0, width.toInt()))
list.add(getBitMap(R.mipmap.explode_1, width.toInt()))
list.add(getBitMap(R.mipmap.explode_2, width.toInt()))
list.add(getBitMap(R.mipmap.explode_3, width.toInt()))
list.add(getBitMap(R.mipmap.explode_4, width.toInt()))
list.add(getBitMap(R.mipmap.explode_5, width.toInt()))
list.add(getBitMap(R.mipmap.explode_5, width.toInt()))
list.add(getBitMap(R.mipmap.explode_6, width.toInt()))
list.add(getBitMap(R.mipmap.explode_7, width.toInt()))
list.add(getBitMap(R.mipmap.explode_8, width.toInt()))
list.add(getBitMap(R.mipmap.explode_9, width.toInt()))
list.add(getBitMap(R.mipmap.explode_10, width.toInt()))
list.add(getBitMap(R.mipmap.explode_11, width.toInt()))
list.add(getBitMap(R.mipmap.explode_12, width.toInt()))
list.add(getBitMap(R.mipmap.explode_13, width.toInt()))
list.add(getBitMap(R.mipmap.explode_14, width.toInt()))
list.add(getBitMap(R.mipmap.explode_15, width.toInt()))
list.add(getBitMap(R.mipmap.explode_16, width.toInt()))
list.add(getBitMap(R.mipmap.explode_17, width.toInt()))
list.add(getBitMap(R.mipmap.explode_18, width.toInt()))
list.add(getBitMap(R.mipmap.explode_19, width.toInt()))
list
}
// 爆破下标
var explodeIndex = -1
set(value) {
field = value
invalidate()
}
// 属性动画修正爆破下标,最后一帧的时分回到 -1
private val explodeAnimator by lazy {
ObjectAnimator.ofInt(this, "explodeIndex", 19, -1).apply {
duration = 1000
}
}
调用:
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
...
MotionEvent.ACTION_UP -> {
// 大圆是否在辅佐圆规模内
if (bigPointF.contains(smallPointF, MAX_RADIUS)) {
// 回弹
....
} else {
// 制作爆破作用
explodeAnimator.start()
// 爆破作用完毕后,将图片移动到原始方位
explodeAnimator.doOnEnd {
bigPointF.x = width / 2f
bigPointF.y = height / 2f
}
}
}
}
invalidate()
return true // 消费事情
}
制作BitMap:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 制作小圆
...
// 大圆方位是否在辅佐圆内
if (bigPointF.contains(smallPointF, MAX_RADIUS)) {
// 制作大圆
....
// 制作贝塞尔
...
}
// 制作爆破作用
if (explodeIndex != -1) {
// 圆和bitmap坐标系不同
// 圆的坐标系是中心点
// bitmap的坐标系是左上角
canvas.drawBitmap(explodeImages[explodeIndex],
bigPointF.x - BIG_RADIUS * 2,
bigPointF.y - BIG_RADIUS * 2,
paint)
}
// 制作辅佐圆
....
}
辅佐图1.14
:
虽然爆破作用制作出来了,可是看着爆破时分略微略微有一点颤动,
这是由于爆破作用的这20张图片是我在网上找的,一张一张切出来的.. 手艺欠好,可能有一点歪歪扭扭,可是不打紧,到时分UI都会给你的@.@
到此刻,作用一就完结了..
作用二
那么接下来继续完结作用二吧~:
回顾作用二
辅佐图2.1
:
赛前预备:
要想拖动View,那就必须有一个View,那么就以这个Button来演示吧~
辅佐图2.2
:
思路剖析:
- 经过setOnTouchListener{} 能够实现对View的触摸事情监听
- 在ACTION_DOWN事情时分,将当时View躲藏,经过WindowManager增加一个拖拽的气泡View((便是上面写好的), 而且给气泡View初始化好方位
- 在ACTION_MOVE 事情中不断的更新大圆的方位
- 在ACTION_UP事情的时分,判别是否在辅佐圆内,然后进行回弹或许爆破. 而且将拖拽气泡从WindowManager总删去掉
需求注意的是,既然经过setOnTouchListener{} 监听了移动方位,那么在拖拽View中 onTouchEvent()中的代码全都要删掉
这仅仅总体思路,还有许多细节,那就慢慢剖析吧~
将dragView增加WindowManager上
辅佐图2.3
:
辅佐图2.4
:
能够看出,现在有2个问题
- 初始化方位不对
- 当拖动的时分,状态栏变成了黑色
初始化方位不对
初始化方位不对,需求有2个初始点
-
小圆初始点: 小圆初始点既是当时view的中心点
-
大圆初始点: 大圆初始点便是当时按下的方位
当时View的中心点需求获取当时window的肯定坐标方位:
// location[0] = x;
// location[1] = y;
val location = IntArray(2)
view.getLocationInWindow(location) // 获取当时窗口的肯定坐标
大圆方位为当时点击屏幕的肯定方位:
即为 event.rawX; even.rawY
调用:
#BlogDragBubbleUtil.kt
when (event.action) {
MotionEvent.ACTION_DOWN -> {
val location = IntArray(2)
view.getLocationInWindow(location) // 获取当时窗口的肯定坐标
dragView.initPointF(
location[0].toFloat() + view.width / 2,
location[1].toFloat()+ view.height / 2 ,
event.rawX,
event.rawY
)
}
....
}
辅佐图2.5
:
能够看出,根本现已没问题了,可是点击的,显着有一点偏下,这是由于肯定方位不包括状体栏的高度
所以需求在减去状态栏的高度即可
// 获取状态栏高度
fun Context.statusBarHeight() = let {
var height = 0
val resourceId: Int = resources
.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
height = resources.getDimensionPixelSize(resourceId)
}
height
}
终究代码为:
// 屏幕状态栏高度
private val statusBarHeight by lazy {
context.statusBarHeight()
}
MotionEvent.ACTION_DOWN -> {
dragView.initPointF(
location[0].toFloat() + view.width / 2,
location[1].toFloat() + view.height / 2 - statusBarHeight,
event.rawX,
event.rawY - statusBarHeight
)
}
MotionEvent.ACTION_MOVE -> {
dragView.upDataPointF(event.rawX, event.rawY - statusBarHeight)
}
当拖动的时分,状态栏变成了黑色
状态栏变成黑色,阐明是LayoutParams 彻底占满了整个屏幕
那么只需求手动给他不包括状态栏的高度即可
private val layoutParams by lazy {
// WindowManager.LayoutParams().apply {
// format = PixelFormat.TRANSLUCENT // 设置windowManager为透明
// }
WindowManager.LayoutParams(screenWidth,
screenHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
PixelFormat.TRANSPARENT // 设置透明度
)
}
来看看WindowManager.LayoutParams的源码
辅佐图2.6
:
这儿我调用的是5个参数的,我也没研讨过这5个参数是干嘛用的
我只认识三个
- 宽
- 高
- 透明度
type 和 flags我都是设置的默认值.
无论怎么这么写是可行的,当时作用
辅佐图2.7
:
现在流程现已走了50%
复制drawView中BitMap,而且制作
这个标题我有必要解释一下,每一个控件内其实都是bitmap制作的
我想要的作用是当拖动的时分,拖动的是控件,而不是变成一个红色的小球
所以在拖动过程中,咱们要复制出drawView中bitmap图片,然后在从头制作一下
肉眼看就现已到达了作用,可是关于代码来说,本体现已躲藏了,仅仅留下了一个复制品来展现罢了
// 从View中获取bitMap
fun View.getBackgroundBitMap(): Bitmap = let {
this.buildDrawingCache()
this.drawingCache
}
在DOWN中设置bitMap图片
在UP的时分清空图片
#BlogDragBubbleUtil.kt
// view的图片
private val bitMap by lazy { view.getBackgroundBitMap() }
fun bind() {
view.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 初始化方位
dragView.initPointF(..)
// 设置BitMap图片
dragView.upDataBitMap(bitMap, bitMap.width.toFloat())
}
MotionEvent.ACTION_MOVE -> {
// 从头制作大圆方位
...
}
MotionEvent.ACTION_UP -> {
// 清空bitMap图片
dragView.upDataBitMap(null, bitMap.width.toFloat())
}
}
true
}
}
制作:
#DragView.kt
fun void onDraw(canvas: Canvas){
// 制作小圆
// 制作大圆
// 制作view中的bitMap
bitMap?.let {
canvas.drawBitmap(it,
bigPointF.x - it.width / 2f,
bigPointF.y - it.height / 2f, paint)
}
// 制作辅佐圆
}
var bitMap: Bitmap? = null
var bitMapWidth = 0f
fun upDataBitMap(bitMap: Bitmap?, bitMapWidth: Float) {
this.bitMap = bitMap
this.bitMapWidth = bitMapWidth
invalidate()
}
这儿的bitMapWidth现在还不必,后边会用到.
辅佐图2.8
:
回弹作用
回弹代码现已写好了,只需求调用一下即可
# BlogDragBubbleUtil.kt
MotionEvent.ACTION_UP -> {
/// 判别大圆是否在辅佐圆内
if (dragView.isContains()) {
// 回弹作用
dragView.bigAnimator().run {
start()
doOnEnd { // 完毕回调
// 显现View
view.visibility = View.VISIBLE
// 删去
windowManager.removeView(dragView)
dragView.upDataBitMap(null, bitMap.width.toFloat())
}
}
} else {
// 爆破作用
}
}
辅佐图2.9
:
爆破作用
MotionEvent.ACTION_UP -> {
/// 判别大圆是否在辅佐圆内
if (dragView.isContains()) {
// 回弹作用
dragView.bigAnimator().run {
start()
doOnEnd { // 完毕回调
// 显现View
view.visibility = View.VISIBLE
// 删去
windowManager.removeView(dragView)
dragView.upDataBitMap(null, bitMap.width.toFloat())
}
}
} else {
// 爆破作用
// 爆破之前先清空ViewBitMap
dragView.upDataBitMap(null, bitMap.width.toFloat())
dragView.explodeAnimator.run {
start() // 敞开动画
doOnEnd { // 完毕动画回调
windowManager.removeView(dragView)
view.visibility = View.VISIBLE
}
}
}
}
能够看出,根本作用现已完结了,可是还有一点,假设仔细看,
偶然状况下在大圆回到校园方位的时分,会闪烁一下
解决闪烁问题也很简单,只需求让他鄙人一帧的时分在进行躲藏或许显现操作即可
那么只需求调用View#postOnAnimation{}方法即可,吧辅佐圆去掉,看看现在的作用
此刻的作用现已接近完美了~
假设现在换个大一点的控件来看看作用:
能够看出,作用是没问题,可是爆破规模有一点小,我想控件有多宽,爆破规模就有多大
在上面更新View中BitMap图片的时分会传递BitMap的宽度,所以直接设置一下即可
private val explodeImages by lazy {
val list = arrayListOf<Bitmap>()
val width = bitMapWidth // 设置bitmap 宽度
list.add(getBitMap(R.mipmap.explode_0, width.toInt()))
... 加载20张图片
list.add(getBitMap(R.mipmap.explode_19, width.toInt()))
list
}
getBitMap是一个加载BitMap的扩展方法
fun View.getBitMap(@DrawableRes bitmap: Int = R.mipmap.user, width: Int = 640): Bitmap = let {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(resources, bitmap)
options.inJustDecodeBounds = false
options.inDensity = options.outWidth
options.inTargetDensity = width
BitmapFactory.decodeResource(resources, bitmap, options)
}
再来看看作用:
能够看出,爆照作用会跟随着图片的宽度来改动
可是爆破的时分会有白底,这彻底是由于我不会用ps,真的不会切图.. 就这么将就的看吧…
最后在RecyclerView中实战一下!
rv的代码就不看了,太简单了
能够看出,作用仍是不对,犯错原因子view抢事情没抢过recyclerView,那么只需求在ACTION_DOWN中抢一下事情即可
view.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 和父容器抢焦点
view.parent.requestDisallowInterceptTouchEvent(true)
}
...
}
来看终究作用:
完好代码
原创不易,您的点赞便是对我最大的支撑!
抢手文章:
- View生命周期
- android CoordinatorLayout 从源码到实战..
- android NestedScrollView 从源码到实战..
- android 图解 PhotoView,从‘百草园’到‘三味书屋’!
- android 浅析RecyclerView收回复用机制及实战(仿探探作用)