溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在Android中通過貝塞爾曲線實現(xiàn)消息拖拽消失

發(fā)布時間:2021-05-31 17:20:23 來源:億速云 閱讀:132 作者:Leah 欄目:移動開發(fā)

本篇文章為大家展示了怎么在Android中通過貝塞爾曲線實現(xiàn)消息拖拽消失,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

分析(用到的知識點): 

(1)ValueAnimator (數(shù)值生成器) 用于生成數(shù)值,可以設置差值器來改變數(shù)字的變化幅度。

(2)ObjectAnimator (動畫生成器) 用于生成各種屬性,布局動畫,同樣也可以設置差值器來改變效果。

(3)貝塞爾一階曲線

(4)自定義View的基礎知識

(5)WindowManager 使view拖拽能顯示在整個屏幕的任何地方,而不是局限于父布局內(nèi)

具體實現(xiàn)方法

一、首先我們要實現(xiàn)基礎效果

基礎效果是點擊屏幕任意一點能出現(xiàn)消息拖拽的效果,但是此時我們不用管我們拖動的View,只需要完成大致模型。該部分的難點在于貝塞爾一階曲線的怎么實現(xiàn)。

基礎效果圖

怎么在Android中通過貝塞爾曲線實現(xiàn)消息拖拽消失

 分析:

(1)點擊任意一點畫出兩個圓,和一個有貝塞爾曲線組成的path路徑

(2)隨著拖動距離的增加原點的圓半徑逐漸縮小,當距離達到一定大以后原點的圓和貝塞爾曲線組成的path不再顯示

貝塞爾曲線的畫法

怎么在Android中通過貝塞爾曲線實現(xiàn)消息拖拽消失

首先我們需要求出角a的大小,根據(jù)角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;
 }

 二、完善代碼

 這部分我們需要完善所有代碼,實現(xiàn)代碼的分離,使得所用View都能被拖動,且需要創(chuàng)建一個監(jiān)聽器來監(jiān)聽View是否拖動結束了,結束后調(diào)用回調(diào)方法以便需要做其他處理。

需要完成的功能:

(1)將傳入的View畫出來

(2)在手指抬起時判斷是爆炸還是回彈

(3)完成回彈和爆炸的代碼部分

(4)回彈或者爆炸結束后調(diào)用回調(diào)通知動畫結束

(5)使用WindowManager把自定義拖拽View加進去,隱藏原來得View實現(xiàn)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軸需要減去狀態(tài)欄的高度
    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) {
      //爆炸效果
      // 要去執(zhí)行爆炸動畫 (幀動畫)
      //移除拖拽的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();
      // 等它執(zhí)行完之后我要移除掉這個 爆炸動畫也就是 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的調(diào)用代碼

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) {
 
   }
  });
 }
}

上述內(nèi)容就是怎么在Android中通過貝塞爾曲線實現(xiàn)消息拖拽消失,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI