您好,登錄后才能下訂單哦!
本篇文章為大家展示了怎么在Android中通過貝塞爾曲線實現消息拖拽消失,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
分析(用到的知識點):
(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 < 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) { } }); } }
上述內容就是怎么在Android中通過貝塞爾曲線實現消息拖拽消失,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。