麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁(yè) > 系統(tǒng) > Android > 正文

Android貝塞爾曲線實(shí)現(xiàn)消息拖拽消失

2019-10-21 21:26:31
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

寫在前頭

寫消息拖拽效果的文章不少,但是大部分都把自定義View寫死了,我們要實(shí)現(xiàn)的是傳入一個(gè)View,每個(gè)View都可以實(shí)現(xiàn)拖拽消失爆炸的效果,當(dāng)然我也是站在巨人的肩膀上來(lái)學(xué)習(xí)的。但個(gè)人覺(jué)得程序員本就應(yīng)該敢于學(xué)習(xí)和借鑒。

源碼地址:源碼Github地址

效果圖

Android,貝塞爾曲線,消息拖拽

 分析(用到的知識(shí)點(diǎn)): 

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

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

(3)貝塞爾一階曲線

(4)自定義View的基礎(chǔ)知識(shí)

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

具體實(shí)現(xiàn)方法

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

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

基礎(chǔ)效果圖

Android,貝塞爾曲線,消息拖拽

 分析:

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

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

貝塞爾曲線的畫法

Android,貝塞爾曲線,消息拖拽

首先我們需要求出角a的大小,根據(jù)角a來(lái)求到A,B,C,D的坐標(biāo)位子,然后求到控制點(diǎn)E點(diǎn)的坐標(biāo),通過(guò)Path.quadTo()方法來(lái)連接A,B和C,D兩條貝塞爾曲線。

各點(diǎn)坐標(biāo)

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) {   // 超過(guò)一定距離 貝塞爾和固定圓都不要畫了   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); // 移動(dòng)  // 兩個(gè)點(diǎn)  PointF controlPoint = getControlPoint();  // 畫了第一條 第一個(gè)點(diǎn)(控制點(diǎn),兩個(gè)圓心的中心點(diǎn)),終點(diǎn)  bezeierPath.quadTo(controlPoint.x,controlPoint.y,Bx,By);   // 畫第二條  bezeierPath.lineTo(Cx,Cy); // 鏈接到  bezeierPath.quadTo(controlPoint.x,controlPoint.y,Dx,Dy);  bezeierPath.close();   return bezeierPath; }

 二、完善代碼

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

需要完成的功能:

(1)將傳入的View畫出來(lái)

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

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

(4)回彈或者爆炸結(jié)束后調(diào)用回調(diào)通知?jiǎng)赢嫿Y(jié)束

(5)使用WindowManager把自定義拖拽View加進(jìn)去,隱藏原來(lái)得View實(shí)現(xiàn)View在任意地方拖動(dòng)

完整代碼部分

(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) {   // 小到一定層度就不見(jiàn)了(不畫了)   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) {   // 超過(guò)一定距離 貝塞爾和固定圓都不要畫了   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); // 移動(dòng)  // 兩個(gè)點(diǎn)  PointF controlPoint = getControlPoint();  // 畫了第一條 第一個(gè)點(diǎn)(控制點(diǎn),兩個(gè)圓心的中心點(diǎn)),終點(diǎn)  bezeierPath.quadTo(controlPoint.x,controlPoint.y,Bx,By);   // 畫第二條  bezeierPath.lineTo(Cx,Cy); // 鏈接到  bezeierPath.quadTo(controlPoint.x,controlPoint.y,Dx,Dy);  bezeierPath.close();   return bezeierPath; } /**  * 獲得控制點(diǎn)距離  */ public PointF getControlPoint() {  return new PointF((mLittleCirclePoint.x+mBigCirclePoint.x)/2,(mLittleCirclePoint.y+mBigCirclePoint.y)/2); }  /**  * 獲得兩點(diǎn)之間的距離  */ 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 值變化的動(dòng)畫 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);    }   });   // 設(shè)置一個(gè)差值器 在結(jié)束的時(shí)候回彈   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; // 爆炸動(dòng)畫 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));    // 給消息拖拽設(shè)置一個(gè)Bitmap    mMsgDrafitingView.setDragBitmap(bitmap);    //設(shè)置OnTouchUpListener    mMsgDrafitingView.setOnToucnUpListener(new MsgDrafitingView.OnToucnUpListener() {     @Override     public void restore() {      //還原位子      // 把消息的View移除      mWindowManager.removeView(mMsgDrafitingView);      // 把原來(lái)的View顯示      view.setVisibility(View.VISIBLE);     }      @Override     public void dismiss(PointF pointF) {      //爆炸效果      // 要去執(zhí)行爆炸動(dòng)畫 (幀動(dòng)畫)      //移除拖拽的view      mWindowManager.removeView(mMsgDrafitingView);      // 要在 mWindowManager 添加一個(gè)爆炸動(dòng)畫      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í)行完之后我要移除掉這個(gè) 爆炸動(dòng)畫也就是 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); }  /**  * 獲取爆炸動(dòng)畫畫的時(shí)間  * @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) {    }  }); }}

源碼地址:源碼Github地址

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持VEVB武林網(wǎng)。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到Android開(kāi)發(fā)頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 国产精品一区视频 | 毛片视频网站在线观看 | 久久影院一区二区三区 | 亚洲第一页在线观看 | 国产亚洲精品成人a | 欧美粗暴analvideos | 毛片在线视频免费观看 | 粉嫩粉嫩一区二区三区在线播放 | 激情网站视频 | 一本视频在线观看 | 亚洲国产精品高潮呻吟久久 | 国内精品久久久久久久久久 | 亚洲第一综合色 | 久久久久久久久久亚洲 | 欧美成人精品一区二区三区 | 久久国产精品久久精品国产演员表 | 欧美日韩亚州综合 | 色999久久久精品人人澡69 | 国产精品视频1区 | 国产一级毛片国产 | 香蕉视频网站在线观看 | av在线免费不卡 | 国产精品久久999 | 久久艹一区| 少妇一级淫片免费放4p | 免费放黄网站在线播放 | 午夜天堂在线视频 | 亚洲第一激情网 | 国产外围在线 | 性感美女一级毛片 | 黄色aaa视频 | 日本黄色免费观看视频 | 一区二区三区视频在线观看 | 免费1级做55爰片l在线观看 | 成年人在线视频观看 | 国产免费网站视频 | 丰满年轻岳中文字幕一区二区 | 日本在线不卡免费 | 最新在线中文字幕 | 毛片哪里看| 久久国产精品久久精品国产演员表 |