




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】怎么在Android中通过贝塞尔曲线实现消息拖拽消失
本篇文章为大家展示了怎么在Android中通过贝塞尔曲线实现消息拖拽消失,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。分析(用到的知识点):
(1)ValueAnimator(数值生成器)用于生成数值,可以设置差值器来改变数字的变化幅度。(2)ObjectAnimator(动画生成器)用于生成各种属性,布局动画,同样也可以设置差值器来改变效果。(3)贝塞尔一阶曲线(4)自定义View的基础知识(5)WindowManager使view拖拽能显示在整个屏幕的任何地方,而不是局限于父布局内具体实现方法一、首先我们要实现基础效果基础效果是点击屏幕任意一点能出现消息拖拽的效果,但是此时我们不用管我们拖动的View,只需要完成大致模型。该部分的难点在于贝塞尔一阶曲线的怎么实现。基础效果图
分析:(1)点击任意一点画出两个圆,和一个有贝塞尔曲线组成的path路径(2)随着拖动距离的增加原点的圆半径逐渐缩小,当距离达到一定大以后原点的圆和贝塞尔曲线组成的path不再显示贝塞尔曲线的画法首先我们需要求出角a的大小,根据角a来求到A,B,C,D的坐标位子,然后求到控制点E点的坐标,通过Path.quadTo()方法来连接A,B和C,D两条贝塞尔曲线。各点坐标A(c1.x+sina*c1半径,c1.y-cina*c1半径)B(c2.x+sina*c2半径,c2.y-cina*c2半径)C(c2.x-sina*c1半径,c2.y+cina*c1半径)D(c1.x-sina*c2半径,c1.y+cina*c2半径)E((c1.x+c2.x)/2,(c1.y+c2.y)/2)A(c1.x+sina*c1半径,c1.y-cina*c1半径)B(c2.x+sina*c2半径,c2.y-cina*c2半径)C(c2.x-sina*c1半径,c2.y+cina*c1半径)D(c1.x-sina*c2半径,c1.y+cina*c2半径)E((c1.x+c2.x)/2,(c1.y+c2.y)/2)贝塞尔曲线的path代码private
Path
getBezeierPath()
{
double
distance
=
getDistance(mBigCirclePoint,mLittleCirclePoint);
mLittleCircleRadius
=
(int)
(mLittleCircleRadiusMax
-
distance
/
10);
if
(mLittleCircleRadius
<
mLittleCircleRadiusMin)
{
//
超过一定距离
贝塞尔和固定圆都不要画了
return
null;
}
Path
bezeierPath
=
new
Path();
//
求角
a
//
求斜率
float
dy
=
(mBigCirclePoint.y-mLittleCirclePoint.y);
float
dx
=
(mBigCirclePoint.x-mLittleCirclePoint.x);
float
tanA
=
dy/dx;
//
求角a
double
arcTanA
=
Math.atan(tanA);
//
A
float
Ax
=
(float)
(mLittleCirclePoint.x
+
mLittleCircleRadius*Math.sin(arcTanA));
float
Ay
=
(float)
(mLittleCirclePoint.y
-
mLittleCircleRadius*Math.cos(arcTanA));
//
B
float
Bx
=
(float)
(mBigCirclePoint.x
+
mBigCircleRadius*Math.sin(arcTanA));
float
By
=
(float)
(mBigCirclePoint.y
-
mBigCircleRadius*Math.cos(arcTanA));
//
C
float
Cx
=
(float)
(mBigCirclePoint.x
-
mBigCircleRadius*Math.sin(arcTanA));
float
Cy
=
(float)
(mBigCirclePoint.y
+
mBigCircleRadius*Math.cos(arcTanA));
//
D
float
Dx
=
(float)
(mLittleCirclePoint.x
-
mLittleCircleRadius*Math.sin(arcTanA));
float
Dy
=
(float)
(mLittleCirclePoint.y
+
mLittleCircleRadius*Math.cos(arcTanA));
//
拼装
贝塞尔的曲线路径
bezeierPath.moveTo(Ax,Ay);
//
移动
//
两个点
PointF
controlPoint
=
getControlPoint();
//
画了第一条
第一个点(控制点,两个圆心的中心点),终点
bezeierPath.quadTo(controlPoint.x,controlPoint.y,Bx,By);
//
画第二条
bezeierPath.lineTo(Cx,Cy);
//
链接到
bezeierPath.quadTo(controlPoint.x,controlPoint.y,Dx,Dy);
bezeierPath.close();
return
bezeierPath;
}
二、完善代码
这部分我们需要完善所有代码,实现代码的分离,使得所用View都能被拖动,且需要创建一个监听器来监听View是否拖动结束了,结束后调用回调方法以便需要做其他处理。需要完成的功能:(1)将传入的View画出来(2)在手指抬起时判断是爆炸还是回弹(3)完成回弹和爆炸的代码部分(4)回弹或者爆炸结束后调用回调通知动画结束(5)使用WindowManager把自定义拖拽View加进去,隐藏原来得View实现View在任意地方拖动完整代码部分(1)自定义View的代码public
class
MsgDrafitingView
extends
View{
private
PointF
mLittleCirclePoint;
private
PointF
mBigCirclePoint;
private
Paint
mPaint;
//大圆半径
private
int
mBigCircleRadius
=
10;
//小圆半径
private
int
mLittleCircleRadiusMax
=
10;
private
int
mLittleCircleRadiusMin
=
2;
private
int
mLittleCircleRadius;
private
Bitmap
dragBitmap;
private
OnToucnUpListener
mOnToucnUpListener;
public
MsgDrafitingView(Context
context)
{
this(context,null);
}
public
MsgDrafitingView(Context
context,
@Nullable
AttributeSet
attrs)
{
this(context,
attrs,0);
}
public
MsgDrafitingView(Context
context,
@Nullable
AttributeSet
attrs,
int
defStyleAttr)
{
super(context,
attrs,
defStyleAttr);
mBigCircleRadius
=
dip2px(mBigCircleRadius);
mLittleCircleRadiusMax
=
dip2px(mLittleCircleRadiusMax);
mLittleCircleRadiusMin
=
dip2px(mLittleCircleRadiusMin);
mPaint
=
new
Paint();
mPaint.setColor(Color.RED);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
}
private
int
dip2px(int
dip)
{
return
(int)
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics());
}
@Override
protected
void
onDraw(Canvas
canvas)
{
if
(mBigCirclePoint
==
null
||
mLittleCirclePoint
==
null)
{
return;
}
//画大圆
canvas.drawCircle(mBigCirclePoint.x,
mBigCirclePoint.y,
mBigCircleRadius,
mPaint);
//获得贝塞尔路径
Path
bezeierPath
=
getBezeierPath();
if
(bezeierPath!=null)
{
//
小到一定层度就不见了(不画了)
canvas.drawCircle(mLittleCirclePoint.x,
mLittleCirclePoint.y,
mLittleCircleRadius,
mPaint);
//
画贝塞尔曲线
canvas.drawPath(bezeierPath,
mPaint);
}
//
画图片
if
(dragBitmap
!=
null)
{
canvas.drawBitmap(dragBitmap,
mBigCirclePoint.x
-
dragBitmap.getWidth()
/
2,
mBigCirclePoint.y
-
dragBitmap.getHeight()
/
2,
null);
}
}
private
Path
getBezeierPath()
{
double
distance
=
getDistance(mBigCirclePoint,mLittleCirclePoint);
mLittleCircleRadius
=
(int)
(mLittleCircleRadiusMax
-
distance
/
10);
if
(mLittleCircleRadius
<
mLittleCircleRadiusMin)
{
//
超过一定距离
贝塞尔和固定圆都不要画了
return
null;
}
Path
bezeierPath
=
new
Path();
//
求角
a
//
求斜率
float
dy
=
(mBigCirclePoint.y-mLittleCirclePoint.y);
float
dx
=
(mBigCirclePoint.x-mLittleCirclePoint.x);
float
tanA
=
dy/dx;
//
求角a
double
arcTanA
=
Math.atan(tanA);
//
A
float
Ax
=
(float)
(mLittleCirclePoint.x
+
mLittleCircleRadius*Math.sin(arcTanA));
float
Ay
=
(float)
(mLittleCirclePoint.y
-
mLittleCircleRadius*Math.cos(arcTanA));
//
B
float
Bx
=
(float)
(mBigCirclePoint.x
+
mBigCircleRadius*Math.sin(arcTanA));
float
By
=
(float)
(mBigCirclePoint.y
-
mBigCircleRadius*Math.cos(arcTanA));
//
C
float
Cx
=
(float)
(mBigCirclePoint.x
-
mBigCircleRadius*Math.sin(arcTanA));
float
Cy
=
(float)
(mBigCirclePoint.y
+
mBigCircleRadius*Math.cos(arcTanA));
//
D
float
Dx
=
(float)
(mLittleCirclePoint.x
-
mLittleCircleRadius*Math.sin(arcTanA));
float
Dy
=
(float)
(mLittleCirclePoint.y
+
mLittleCircleRadius*Math.cos(arcTanA));
//
拼装
贝塞尔的曲线路径
bezeierPath.moveTo(Ax,Ay);
//
移动
//
两个点
PointF
controlPoint
=
getControlPoint();
//
画了第一条
第一个点(控制点,两个圆心的中心点),终点
bezeierPath.quadTo(controlPoint.x,controlPoint.y,Bx,By);
//
画第二条
bezeierPath.lineTo(Cx,Cy);
//
链接到
bezeierPath.quadTo(controlPoint.x,controlPoint.y,Dx,Dy);
bezeierPath.close();
return
bezeierPath;
}
/**
*
获得控制点距离
*/
public
PointF
getControlPoint()
{
return
new
PointF((mLittleCirclePoint.x+mBigCirclePoint.x)/2,(mLittleCirclePoint.y+mBigCirclePoint.y)/2);
}
/**
*
获得两点之间的距离
*/
private
double
getDistance(PointF
point1,
PointF
point2)
{
return
Math.sqrt((point1.x
-
point2.x)
*
(point1.x
-
point2.x)
+
(point1.y
-
point2.y)
*
(point1.y
-
point2.y));
}
/**
*
绑定View
*/
public
static
void
attach(View
view,
MsgDrafitingListener.BubbleDisappearListener
disappearListener)
{
view.setOnTouchListener(new
MsgDrafitingListener(view.getContext(),disappearListener));
}
public
void
initPoint(float
x,
float
y)
{
mBigCirclePoint
=
new
PointF(x,y);
mLittleCirclePoint
=
new
PointF(x,y);
}
public
void
updatePoint(float
x,float
y)
{
mBigCirclePoint.x
=
x;
mBigCirclePoint.y
=
y;
invalidate();
}
public
void
setDragBitmap(Bitmap
dragBitmap)
{
this.dragBitmap
=
dragBitmap;
}
public
void
setOnToucnUpListener(OnToucnUpListener
listener)
{
mOnToucnUpListener
=
listener;
}
public
interface
OnToucnUpListener
{
//
还原
void
restore();
//
消失爆炸
void
dismiss(PointF
pointF);
}
/**
*
处理手指抬起后的操作
*/
public
void
OnTouchUp()
{
if
(mLittleCircleRadius
>
mLittleCircleRadiusMin)
{
//
回弹
ValueAnimator
值变化的动画
0
变化到
1
ValueAnimator
animator
=
ObjectAnimator.ofFloat(1);
animator.setDuration(250);
final
PointF
start
=
new
PointF(mBigCirclePoint.x,
mBigCirclePoint.y);
final
PointF
end
=
new
PointF(mLittleCirclePoint.x,
mLittleCirclePoint.y);
animator.addUpdateListener(new
ValueAnimator.AnimatorUpdateListener()
{
@Override
public
void
onAnimationUpdate(ValueAnimator
animation)
{
float
percent
=
(float)
animation.getAnimatedValue();//
0
-
1
PointF
pointF
=
Utils.getPointByPercent(start,
end,
percent);
//更新位子
updatePoint(pointF.x,
pointF.y);
}
});
//
设置一个差值器
在结束的时候回弹
animator.setInterpolator(new
OvershootInterpolator(3f));
animator.start();
//
还要通知
TouchListener
animator.addListener(new
AnimatorListenerAdapter()
{
@Override
public
void
onAnimationEnd(Animator
animation)
{
if(mOnToucnUpListener
!=
null){
mOnToucnUpListener.restore();
}
}
});
}
else
{
//
爆炸
if(mOnToucnUpListener
!=
null){
mOnToucnUpListener.dismiss(mBigCirclePoint);
}
}
}
}
(2)自定义OnTouchListenner的代码public
class
MsgDrafitingListener
implements
View.OnTouchListener
{
private
WindowManager
mWindowManager;
private
WindowManager.LayoutParams
params;
private
MsgDrafitingView
mMsgDrafitingView;
private
Context
context;
//
爆炸动画
private
FrameLayout
mBombFrame;
private
ImageView
mBombImage;
private
BubbleDisappearListener
mDisappearListener;
public
MsgDrafitingListener(Context
context,BubbleDisappearListener
disappearListener)
{
mWindowManager
=
(WindowManager)
context.getSystemService(Context.WINDOW_SERVICE);
params
=
new
WindowManager.LayoutParams();
mMsgDrafitingView
=
new
MsgDrafitingView(context);
//背景透明
params.format
=
PixelFormat.TRANSPARENT;
this.context
=
context;
mBombFrame
=
new
FrameLayout(context);
mBombImage
=
new
ImageView(context);
mBombImage.setLayoutParams(new
FrameLayout.LayoutParams(Utils.dip2px(30,context),
Utils.dip2px(30,context)));
mBombFrame.addView(mBombImage);
this.mDisappearListener
=
disappearListener;
}
@Override
public
boolean
onTouch(final
View
view,
MotionEvent
motionEvent)
{
switch
(motionEvent.getAction())
{
case
MotionEvent.ACTION_DOWN:
//隐藏自己
view.setVisibility(View.INVISIBLE);
mWindowManager.addView(mMsgDrafitingView,params);
int[]
location
=
new
int[2];
view.getLocationOnScreen(location);
Bitmap
bitmap
=
getBitmapByView(view);
//y轴需要减去状态栏的高度
mMsgDrafitingView.initPoint(location[0]
+
view.getWidth()
/
2,
location[1]+view.getHeight()/2
-Utils.getStatusBarHeight(context));
//
给消息拖拽设置一个Bitmap
mMsgDrafitingView.setDragBitmap(bitmap);
//设置OnTouchUpListener
mMsgDrafitingView.setOnToucnUpListener(new
MsgDrafitingView.OnToucnUpListener()
{
@Override
public
void
restore()
{
//还原位子
//
把消息的View移除
mWindowManager.removeView(mMsgDrafitingView);
//
把原来的View显示
view.setVisibility(View.VISIBLE);
}
@Override
public
void
dismiss(PointF
pointF)
{
//爆炸效果
//
要去执行爆炸动画
(帧动画)
//移除拖拽的view
mWindowManager.removeView(mMsgDrafitingView);
//
要在
mWindowManager
添加一个爆炸动画
mWindowManager.addView(mBombFrame,params);
mBombImage.setBackgroundResource(R.drawable.anim_bubble_pop);
AnimationDrawable
drawable
=
(AnimationDrawable)
mBombImage.getBackground();
mBombImage.setX(pointF.x-drawable.getIntrinsicWidth()/2);
mBombImage.setY(pointF.y-drawable.getIntrinsicHeight()/2);
drawable.start();
//
等它执行完之后我要移除掉这个
爆炸动画也就是
mBombFrame
mBombImage.postDelayed(new
Runnable()
{
@Override
public
void
run()
{
mWindowManager.removeView(mBombFrame);
//
通知一下外面该消失
if(mDisappearListener
!=
null){
mDisappearListener.dismiss(view);
}
}
},getAnimationDrawableTime(drawable));
}
});
break;
case
MotionEvent.ACTION_MOVE:
mMsgDrafitingView.updatePoint(motionEvent.getRawX(),
motionEvent.getRawY()
-
Utils.getStatusBarHeight(context));
break;
case
MotionE
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025标准设备租赁合同格式合同范本
- 2025年公办中小学物理教师招聘考试命题规律与应对策略探讨
- 江宁消音片施工方案招标
- 门诊大楼预算方案范本
- 2025年健身教练中级专业知识点解析与模拟题
- 2025企业施工现场作业人员安全生产合同
- 茂名洁净车间施工方案
- 水库围栏工程施工方案
- 山东省青岛市2025年-2026年小学六年级数学综合练习(上,下学期)试卷及答案
- 盆景的欣赏 教案-2023-2024学年高一上学期劳动技术
- JG/T 11-2009钢网架焊接空心球节点
- 林地征占用协议书
- 合规审计笔试题及答案
- 中国工程总承包行业市场深度调研及发展趋势与投资前景研究报告2025-2028版
- 老年髋部骨折围术期护理临床实践专家共识2024版解读
- 中国胎教行业市场调研分析及投资前景预测报告
- 储能电站施工方案新建项目
- 《GNSS测量技术与应用》 课件 4.10GNSS控制测量技术总结
- DB32-T 4987-2024 桥梁轻量化监测系统建设规范
- 电梯自动化与智能化技术的前沿探索
- 2025年万达商业地产租赁合同标准版
评论
0/150
提交评论