热门标签 | HotTags
当前位置:  开发笔记 > 开发工具 > 正文

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

这篇文章主要为大家详细介绍了Android贝塞尔曲线实现消息拖拽消失,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

写在前头

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

源码地址:源码Github地址

效果图

 分析(用到的知识点): 

(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)

贝塞尔曲线的path代码

private Path getBezeierPath() {
  double distance = getDistance(mBigCirclePoint,mLittleCirclePoint);
 
  mLittleCircleRadius = (int) (mLittleCircleRadiusMax - distance / 10);
  if (mLittleCircleRadius 

 二、完善代码

 这部分我们需要完善所有代码,实现代码的分离,使得所用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) {
   // 回弹 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

 (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地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Python 异步编程:深入理解 asyncio 库(上)
    本文介绍了 Python 3.4 版本引入的标准库 asyncio,该库为异步 IO 提供了强大的支持。我们将探讨为什么需要 asyncio,以及它如何简化并发编程的复杂性,并详细介绍其核心概念和使用方法。 ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • 探讨一个显示数字的故障计算器,它支持两种操作:将当前数字乘以2或减去1。本文将详细介绍如何用最少的操作次数将初始值X转换为目标值Y。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 本文详细介绍了如何在 Spring Boot 应用中通过 @PropertySource 注解读取非默认配置文件,包括配置文件的创建、映射类的设计以及确保 Spring 容器能够正确加载这些配置的方法。 ... [详细]
  • This document outlines the recommended naming conventions for HTML attributes in Fast Components, focusing on readability and consistency with existing standards. ... [详细]
  • 本文详细介绍了Java中org.w3c.dom.Text类的splitText()方法,通过多个代码示例展示了其实际应用。该方法用于将文本节点在指定位置拆分为两个节点,并保持在文档树中。 ... [详细]
  • 本文详细介绍了 Apache Jena 库中的 Txn.executeWrite 方法,通过多个实际代码示例展示了其在不同场景下的应用,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 在现代网络环境中,两台计算机之间的文件传输需求日益增长。传统的FTP和SSH方式虽然有效,但其配置复杂、步骤繁琐,难以满足快速且安全的传输需求。本文将介绍一种基于Go语言开发的新一代文件传输工具——Croc,它不仅简化了操作流程,还提供了强大的加密和跨平台支持。 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 解决微信电脑版无法刷朋友圈问题:使用安卓远程投屏方案
    在工作期间想要浏览微信和朋友圈却不太方便?虽然微信电脑版目前不支持直接刷朋友圈,但通过远程投屏技术,可以轻松实现在电脑上操作安卓设备的功能。 ... [详细]
  • 网络运维工程师负责确保企业IT基础设施的稳定运行,保障业务连续性和数据安全。他们需要具备多种技能,包括搭建和维护网络环境、监控系统性能、处理突发事件等。本文将探讨网络运维工程师的职业前景及其平均薪酬水平。 ... [详细]
author-avatar
龙love猫
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有