Android贝塞尔曲线实现消息拖拽消失

发布时间:2019-08-08 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Android贝塞尔曲线实现消息拖拽消失脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

写在前头

写消息拖拽效果的文章不少,但是大部分都把自定义View写死了,我们要实现的是传入一个View,每个View都可以实现拖拽消失爆炸的效果,当然我也是站在巨人的肩膀上来学习的。但个人觉得程序员本就应该敢于学习和借鉴。

码地址:源码Github地址

效果图

Android贝塞尔曲线实现消息拖拽消失

 分析(用到的知识点): 

(1)ValueAnimator (数值生成器) 用于生成数值,可以设置差值器来改变数字的变化幅度。

(2)ObjectAnimator (动画生成器) 用于生成各种属性,布局动画,同样也可以设置差值器来改变效果。

(3)贝塞尔一阶曲线

(4)自定义View的基础知识

(5)WindowManager 使view拖拽能显示在整个屏幕的任何地方,而不是局限于父布局内

具体实现方法

一、首先我们要实现基础效果

基础效果是点击屏幕任意一点能出现消息拖拽的效果,但是此时我们不用管我们拖动的View,只需要完成大致模型。该部分的难点在于贝塞尔一阶曲线的怎么实现。

基础效果图

Android贝塞尔曲线实现消息拖拽消失

 分析:

(1)点击任意一点画出两个,和一个有贝塞尔曲线组成的path路径

(2)随着拖动距离的增加原点的圆径逐渐缩小,当距离达到一定大以后原点的圆和贝塞尔曲线组成的path不再显示

贝塞尔曲线的画法

Android贝塞尔曲线实现消息拖拽消失

首先我们需要求出角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)

贝塞尔曲线的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在任意地方拖动

完整代码部分@H_360_97@

(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 MotionEvent.ACTION_UP:     mMsgDrafitingView.OnTouchUp();     break;   }   return true;  }    private Bitmap getBitmapByView(View view) {   view.buildDrawingCache();   Bitmap bitmap = view.getDrawingCache();   return bitmap;  }      public interface BubbleDisappearListener {   void dismiss(View view);  }    /**   * 获取爆炸动画画的时间   * @param drawable   * @return   */  private long getAnimationDrawableTime(AnimationDrawable drawable) {   int numberOfFrames = drawable.getNumberOfFrames();   long time = 0;   for (int i=0;i<numberOfFrames;i++){    time += drawable.getDuration(i);   }   return time;  } }

 (3)View的调用代码

 public class MsgDrafitingViewActivity extends AppCompatActivity{  private Button mButton;  private TextView mText;  @Override  protected void onCreate(@Nullable Bundle savedInstanceState) {   super.onCreate(savedInstanceState);   setContentView(R.layout.QQ_msg_drafitingview_activity);   mButton = findViewById(R.id.mBTn);   mText = findViewById(R.id.mText);   MsgDrafitingView.attach(mButton, new MsgDrafitingListener.BubbleDisappearListener() {    @Override    public void dismiss(View view) {      }   });   MsgDrafitingView.attach(mText, new MsgDrafitingListener.BubbleDisappearListener() {    @Override    public void dismiss(View view) {      }   });  } }

源码地址:源码Github地址

android教程
脚本网站
android studio

脚本宝典总结

以上是脚本宝典为你收集整理的Android贝塞尔曲线实现消息拖拽消失全部内容,希望文章能够帮你解决Android贝塞尔曲线实现消息拖拽消失所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。