效果图:
实现思路
如上图,可以把拖拽时的视图分成三个,圆a、圆b和点ABCD通过贝塞尔曲线围起来的区域,两圆心连线的中点E当作AD和BC的贝塞尔控制点
代码:
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import kotlin.math.absoluteValue
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
/**
* 仿qq拖拽效果
*/
class QQDragView(context: Context, attributeSet: AttributeSet?, defStyleAttr : Int) : View(context, attributeSet, defStyleAttr){
constructor(context: Context) : this(context,null,0)
constructor(context: Context, attributeSet: AttributeSet) : this(context,attributeSet,0)
var mainPaint = Paint()
var bacPaint = Paint()
var dragPaint = Paint()
var mainPath = Path()
var bacPath = Path()
var dragPath = Path()
var viewWidth = 0f
var viewHeight = 0f
var mainRadius = 0f
/**
* 最长距离
*/
var maxLength = 0f
private var mainRectF = RectF()
private var bacRectF = RectF()
private var pointX = 0f
private var pointY = 0f
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = min(measure(widthMeasureSpec,true),measure(heightMeasureSpec,false))
viewWidth = width.toFloat()
viewHeight = viewWidth
mainRadius = viewWidth / 30
pointX = viewWidth / 2
pointY = viewHeight / 2
maxLength = viewWidth * 2 / 5
setMeasuredDimension(width,width)
}
init {
mainPaint.color = Color.BLACK
mainPaint.strokeWidth = 1f
mainPaint.style = Paint.Style.FILL
mainPaint.isAntiAlias = true
bacPaint.color = Color.BLACK
bacPaint.strokeWidth = 1f
bacPaint.style = Paint.Style.FILL
bacPaint.isAntiAlias = true
dragPaint.color = Color.BLACK
dragPaint.strokeWidth = 1f
dragPaint.style = Paint.Style.FILL
dragPaint.isAntiAlias = true
}
/**
* 是否拽出
*/
var isOutDrag = false
/**
* 已经清除
*/
var isClear = false
override fun onDraw(canvas: Canvas?) {
canvas?.let {
if(isClear){
return
}
//圆心距离
val distance = getDistance(viewWidth / 2,viewHeight / 2,pointX,pointY)
if(!isOutDrag && !isOutDistance(distance)){//没有超过拖拽距离
val r = mainRadius - mainRadius * distance / maxLength
bacPath.reset()
bacRectF.left = viewWidth / 2 - r
bacRectF.right = viewWidth / 2 + r
bacRectF.top = viewHeight / 2 - r
bacRectF.bottom = viewHeight / 2 + r
bacPath.addOval(bacRectF,Path.Direction.CW)
drawDragPart(canvas,distance,r)
it.drawPath(bacPath, bacPaint)
}else{
isOutDrag = true
}
mainPath.reset()
mainRectF.left = pointX - mainRadius
mainRectF.right = pointX + mainRadius
mainRectF.top = pointY - mainRadius
mainRectF.bottom = pointY + mainRadius
mainPath.addOval(mainRectF,Path.Direction.CW)
it.drawPath(mainPath, mainPaint)
}
}
/**
* 绘制拖拽部分
*/
private fun drawDragPart(canvas: Canvas,distance: Float,radius:Float) {
if(distance > 0){
dragPath.reset()
//计算两个圆四个点的坐标
var bac_x1 = if(viewWidth / 2 > pointX) viewWidth / 2 - radius * (viewWidth / 2 - pointY).absoluteValue / distance
else viewWidth / 2 + radius * (viewWidth / 2 - pointY).absoluteValue / distance
var bac_y1 = if(viewWidth / 2 > pointY) viewHeight / 2 + radius * (viewHeight / 2 - pointX).absoluteValue / distance
else viewHeight / 2 - radius * (viewHeight / 2 - pointX).absoluteValue / distance
var bac_x2 = if(viewWidth / 2 > pointX) viewWidth / 2 + radius * (viewWidth / 2 - pointY).absoluteValue / distance
else viewWidth / 2 - radius * (viewWidth / 2 - pointY).absoluteValue / distance
var bac_y2 = if(viewWidth / 2 > pointY) viewHeight / 2 - radius * (viewHeight / 2 - pointX).absoluteValue / distance
else viewHeight / 2 + radius * (viewHeight / 2 - pointX).absoluteValue / distance
var main_x1 = if(viewWidth / 2 > pointX) pointX - mainRadius * (viewWidth / 2 - pointY).absoluteValue / distance
else pointX + mainRadius * (viewWidth / 2 - pointY).absoluteValue / distance
var main_y1 = if(viewWidth / 2 > pointY) pointY + mainRadius * (viewHeight / 2 - pointX).absoluteValue / distance
else pointY - mainRadius * (viewHeight / 2 - pointX).absoluteValue / distance
var main_x2 = if(viewWidth / 2 > pointX) pointX + mainRadius * (viewWidth / 2 - pointY).absoluteValue / distance
else pointX - mainRadius * (viewWidth / 2 - pointY).absoluteValue / distance
var main_y2 = if(viewWidth / 2 > pointY) pointY - mainRadius * (viewHeight / 2 - pointX).absoluteValue / distance
else pointY + mainRadius * (viewHeight / 2 - pointX).absoluteValue / distance
//以两个圆心连线的中点当作贝塞尔曲线的控制点
var centerX = (viewWidth / 2 + pointX) / 2
var centerY = (viewHeight / 2 + pointY) / 2
//连接各点形成闭环
dragPath.moveTo(bac_x1,bac_y1)
dragPath.lineTo(bac_x2,bac_y2)
dragPath.quadTo(centerX,centerY,main_x2,main_y2)
dragPath.lineTo(main_x1,main_y1)
dragPath.quadTo(centerX,centerY,bac_x1,bac_y1)
dragPath.close()
canvas.drawPath(dragPath,dragPaint)
}
}
/**
* 测距
*/
private fun getDistance(x1 : Float,y1 : Float,x2 : Float,y2 : Float) : Float{
return sqrt(((x1 - x2).pow(2) + (y1 - y2).pow(2)))
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
if(it.action == MotionEvent.ACTION_MOVE){
pointX = event.x
pointY = event.y
invalidate()
}else if(it.action == MotionEvent.ACTION_UP || it.action == MotionEvent.ACTION_CANCEL){
if(!isOutDrag){//没有拽出来
/*pointX = viewWidth / 2
pointY = viewHeight / 2
invalidate()*/
}else{//拽出来了
//圆心距离
val distance = getDistance(viewWidth / 2,viewHeight / 2,pointX,pointY)
if(isOutDistance(distance)){//超过了拖拽距离
isClear = true
clearListener?.onClear()
}else{
isOutDrag = false
}
}
pointX = viewWidth / 2
pointY = viewHeight / 2
invalidate()
}
}
return true
}
interface ClearListener{
fun onClear()
}
private var clearListener : ClearListener? = null
fun setOnClearListener(clearListener : ClearListener){
this.clearListener = clearListener
}
/**
* 是否超过距离
*/
private fun isOutDistance(distance: Float) : Boolean{
return distance > maxLength / 2
}
private fun measure( measureSpec :Int, isWidth : Boolean) : Int{
var result = 0
val mode = MeasureSpec.getMode(measureSpec);
val size = MeasureSpec.getSize(measureSpec);
val padding = if(isWidth) getPaddingLeft() + getPaddingRight()
else getPaddingTop() + getPaddingBottom()
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = if(isWidth) getSuggestedMinimumWidth()
else getSuggestedMinimumHeight();
result += padding;
if (mode == MeasureSpec.AT_MOST) {
if (isWidth) {
result = Math.max(result, size);
} else {
result = Math.min(result, size);
}
}
}
return result
}
}