a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 系統 > Android > 正文

Android貝塞爾曲線實現消息拖拽消失

2019-10-21 21:26:31
字體:
來源:轉載
供稿:網友

寫在前頭

寫消息拖拽效果的文章不少,但是大部分都把自定義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在任意地方拖動

完整代碼部分

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 国产精品久久九九 | 精品亚洲一区二区 | 国产小视频在线免费观看 | 久久综合社区 | 日本一区二区三区四区 | 久久久久久久久蜜桃 | 在线日韩 | 欧美久草 | 精品久久影院 | 欧美在线观看视频 | 国产精品成av人在线视午夜片 | 伊人av超碰久久久麻豆 | 精品www | 欧美激情精品一区 | 天天天天天天天天干 | 欧美自拍视频 | 日本午夜影院 | 日韩影音 | 日韩精品一区二区三区中文在线 | 欧美理论片在线 | 国产精品久久久久久久久久久久久久久久 | 国产精品久久久久久久久久免费看 | 亚洲成人福利在线观看 | 在线免费观看激情视频 | 高清久久 | www久| 久久亚洲天堂 | 亚洲乱码国产乱码精品精 | 欧美成人精品一区二区男人小说 | 九九视频这里只有精品 | 日本不卡视频 | 亚洲欧美另类在线 | 国产成人精品一区二 | 91精品国产综合久久久久久漫画 | 伊人欧美在线 | 好大好爽快点深一点陶软 | 91一区二区在线 | 国产精品久久久久久久久岛 | 精品国产aⅴ一区二区 | 黄色毛片在线看 | 日本久久精品电影 |