1.要模仿的效果
2 实现思路
2.1 怎么才能够把一个View拖动到状态栏上面
我们要把这个View放在 WindowManager 上面拖动,原来的View还是在原来位置只是隐藏,
拖动的时候其实是新建了一个View,复制一张图片在WindowManager上面拖动
2.2 回弹就是不断改变拖拽圆的位置
2.3 爆炸效果是一个帧动画
3 代码实现
public class BubbleMessageTouchListener implements View.OnTouchListener, MessageBubbleView.MessageBubbleListener {
private final Context mContext;
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mParams;
private View mView;
private final MessageBubbleView mMessageBubbleView;
private BubbleDisappearListener mBubbleTouchListener;
private FrameLayout mFrameLayout;
private ImageView mBombImage;
BubbleMessageTouchListener(View view, Context context, BubbleDisappearListener bubbleTouchListener) {
mView = view;
this.mContext = context;
mMessageBubbleView = new MessageBubbleView(view.getContext());
mBubbleTouchListener = bubbleTouchListener;
mMessageBubbleView.setMessageBubbleListener(this);
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mParams = new WindowManager.LayoutParams();
// 背景要透明
mParams.format = PixelFormat.TRANSPARENT;
mFrameLayout = new FrameLayout(context);
mBombImage = new ImageView(context);
mFrameLayout.addView(mBombImage);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mBombImage.getLayoutParams();
layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;
layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT;
mBombImage.setLayoutParams(layoutParams);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 要在WindowManager上面搞一个View ,上一节写好的贝塞尔的View
mWindowManager.addView(mMessageBubbleView, mParams);
//获取当前View的bitmap
int[] location = new int[2];
mView.getLocationOnScreen(location);
Bitmap bitmap = getBitmapByView(mView);
//初始化固定圆
mMessageBubbleView.setDragBitmap(bitmap);
mMessageBubbleView.initFixedPoint(location[0] + mView.getWidth() / 2,
location[1] + mView.getWidth() / 2 - BubbleUtils.getStatusBarHeight(mContext));
//按下,隐藏当前的view.
mView.setVisibility(View.INVISIBLE);
break;
case MotionEvent.ACTION_MOVE:
mMessageBubbleView.updateDragPoint(event.getRawX(), event.getRawY() - BubbleUtils.getStatusBarHeight(mContext));
break;
case MotionEvent.ACTION_UP:
mMessageBubbleView.handleActionUp();
break;
default:
break;
}
return true;
}
/**
* 从一个View中获取Bitmap
*
* @param view
* @return
*/
private Bitmap getBitmapByView(View view) {
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
return bitmap;
}
@Override
public void restore() {
mWindowManager.removeView(mMessageBubbleView);
mView.setVisibility(View.VISIBLE);
}
@Override
public void dismiss(PointF pointF) {
mWindowManager.removeView(mMessageBubbleView);
// 要在 mWindowManager 添加一个爆炸动画
mWindowManager.addView(mFrameLayout,mParams);
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(mFrameLayout);
// 通知一下外面该消失
if(mBubbleTouchListener != null){
mBubbleTouchListener.dismiss();
}
}
},getAnimationDrawableTime(drawable));
}
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;
}
public interface BubbleDisappearListener {
/**
* 消失
*/
void dismiss();
}
}
public interface BubbleDisappearListener {
/**
* 消失
*/
void dismiss();
}
}
public class BubbleUtils {
/**
* dip 转换成 px
*
* @param dip
* @param context
* @return
*/
public static int dip2px( float dip, Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics);
}
/**
* 获取状态栏高度
*
* @return
*/
public static int getStatusBarHeight(Context context) {
//获取status_bar_height资源的ID
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
//根据资源ID获取响应的尺寸值
return context.getResources().getDimensionPixelSize(resourceId);
}
return dip2px(25, context);
}
/**
* As meaning of method name. 获得两点之间的距离 (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2) 开平方
* Math.sqrt:开平方 Math.pow(p0.y - p1.y, 2):求一个数的平方
*
* @param p0
* @param p1
* @return
*/
public static float getDistanceBetween2Points(PointF p0, PointF p1) {
float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2)
+ Math.pow(p0.x - p1.x, 2));
return distance;
}
/**
* Get point between p1 and p2 by percent. 根据百分比获取两点之间的某个点坐标
*
* @param p1
* @param p2
* @param percent
* @return
*/
public static PointF getPointByPercent(PointF p1, PointF p2, float percent) {
return new PointF(evaluateValue(percent, p1.x, p2.x), evaluateValue(
percent, p1.y, p2.y));
}
/**
* 根据分度值,计算从start到end中,fraction位置的值。fraction范围为0 -> 1
*
* @param fraction
* = 1
* @param start
* = 10
* @param end
* = 3
* @return
*/
public static float evaluateValue(float fraction, Number start, Number end) {
// start = 10 end = 2
//fraction = 0.5
// result = 10 + (-8) * fraction = 6
return start.floatValue() + (end.floatValue() - start.floatValue())
* fraction;
}
/**
* Get the point of intersection between circle and line.
* 获取通过指定圆心,斜率为lineK的直线与圆的交点。
*
* @param pMiddle
* The circle center point.
* @param radius
* The circle radius.
* @param lineK
* The slope of line which cross the pMiddle.
* @return
*/
public static PointF[] getIntersectionPoints(PointF pMiddle, float radius,
Double lineK) {
PointF[] points = new PointF[2];
//高中数学:几何
float arctan, xOffset = 0, yOffset = 0;
if (lineK != null) {
// 计算直角三角形边长
// 余切函数(弧度)
arctan = (float) Math.atan(lineK);
// 正弦函数
xOffset = (float) (Math.sin(arctan) * radius);
// 余弦函数
yOffset = (float) (Math.cos(arctan) * radius);
} else {
xOffset = radius;
yOffset = 0;
}
points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y - yOffset);
points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y + yOffset);
return points;
}
}
public class MessageBubbleView extends View {
private static final String TAG = "zsjTAG";
private PointF mFixedPoint;
private PointF mDragPoint;
private int mFixedMaxRadius = 10;
private int mFixedMinRadius = 5;
private int mDragRadius = 12;
private Paint mPaint;
private PointF mP0;
private PointF mP1;
private PointF mP2;
private PointF mP3;
private PointF mControlPoint;
private Bitmap mDragBitmap;
public MessageBubbleView(Context context) {
this(context, null);
}
public MessageBubbleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MessageBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mFixedMaxRadius = dip2px(mFixedMaxRadius);
mFixedMinRadius = dip2px(mFixedMinRadius);
mDragRadius = dip2px(mDragRadius);
initPaint();
}
private void initPaint() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setDither(true);
mPaint.setColor(Color.RED);
}
private int dip2px(int dip) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
}
/* @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float fixedX = event.getX();
float fixedY = event.getY();
initFixedPoint(fixedX, fixedY);
break;
case MotionEvent.ACTION_MOVE:
float dragX = event.getX();
float dragY = event.getY();
updateDragPoint(dragX, dragY);
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
invalidate();
return true;
}*/
public void updateDragPoint(float dragX, float dragY) {
if (mDragPoint == null) {
mDragPoint = new PointF( dragX, dragY);
}
mDragPoint.x = dragX;
mDragPoint.y = dragY;
invalidate();
}
public void initFixedPoint(float fixedX, float fixedY) {
if (mFixedPoint == null) {
mFixedPoint = new PointF();
}
mFixedPoint.x = fixedX;
mFixedPoint.y = fixedY;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mFixedPoint == null || mDragPoint == null) {
return;
}
//绘制拖拽圆
canvas.drawCircle(mDragPoint.x, mDragPoint.y, mDragRadius, mPaint);
//移动的时候,固定圆缩小.根据拖拽圆和固定圆的距离缩小
//计算拖拽圆和固定圆的距离
double dragFixedDistance = getDragFixedDistance(mFixedPoint, mDragPoint);
float fixedRadius = (float) (mFixedMaxRadius - dragFixedDistance / 14);
if (fixedRadius > mFixedMinRadius) {
//绘制固定圆
canvas.drawCircle(mFixedPoint.x, mFixedPoint.y, fixedRadius, mPaint);
Path bezierPath = getBezierPath(mFixedPoint, mDragPoint);
canvas.drawPath(bezierPath, mPaint);
}
canvas.drawBitmap(mDragBitmap, mDragPoint.x - mDragBitmap.getWidth() / 2
, mDragPoint.y - mDragBitmap.getHeight() / 2, null);
}
private Path getBezierPath(PointF fixedPoint, PointF dagPoint) {
double dragFixedDistance = getDragFixedDistance(mFixedPoint, mDragPoint);
float fixedRadius = (float) (mFixedMaxRadius - dragFixedDistance / 14);
float dx = Math.abs(fixedPoint.x - dagPoint.x);
float dy = Math.abs(fixedPoint.y - dagPoint.y);
float tanA = dy / dx;
float a = (float) Math.atan(tanA);
//P0 点
if (mP0 == null) {
mP0 = new PointF();
}
mP0.x = mDragPoint.x + (int) (mDragRadius * Math.sin(a));
mP0.y = mDragPoint.y - (int) (mDragRadius * Math.cos(a));
//P1 点
if (mP1 == null) {
mP1 = new PointF();
}
mP1.x = mFixedPoint.x + (int) (fixedRadius * Math.sin(a));
mP1.y = mFixedPoint.y - (int) (fixedRadius * Math.cos(a));
//P2 点
if (mP2 == null) {
mP2 = new PointF();
}
mP2.x = mFixedPoint.x - (int) (fixedRadius * Math.sin(a));
mP2.y = mFixedPoint.y + (int) (fixedRadius * Math.cos(a));
//P0 点
if (mP3 == null) {
mP3 = new PointF();
}
mP3.x = mDragPoint.x - (int) (mDragRadius * Math.sin(a));
mP3.y = mDragPoint.y + (int) (mDragRadius * Math.cos(a));
//绘制路径
Path path = new Path();
path.moveTo(mP0.x, mP0.y);
//控制点选择固定圆和拖拽圆的中心点
PointF controlPoint = getControlPoint();
path.quadTo(controlPoint.x, controlPoint.y, mP1.x, mP1.y);
path.lineTo(mP2.x, mP2.y);
path.quadTo(controlPoint.x, controlPoint.y, mP3.x, mP3.y);
path.close();
return path;
}
private PointF getControlPoint() {
if (mControlPoint == null) {
mControlPoint = new PointF();
}
mControlPoint.x = (mDragPoint.x + mFixedPoint.x) / 2;
mControlPoint.y = (mDragPoint.y + mFixedPoint.y) / 2;
return mControlPoint;
}
private double getDragFixedDistance(PointF fixedPoint, PointF dagPoint) {
return Math.sqrt((dagPoint.x - fixedPoint.x) * (dagPoint.x - fixedPoint.x) + (dagPoint.y - fixedPoint.y) * (dagPoint.y - fixedPoint.y));
}
public static void attach(View view, BubbleMessageTouchListener.BubbleDisappearListener bubbleTouchListener) {
view.setOnTouchListener(new BubbleMessageTouchListener(view, view.getContext(),bubbleTouchListener));
}
public void setDragBitmap(Bitmap dragBitmap) {
mDragBitmap = dragBitmap;
invalidate();
}
public void handleActionUp() {
double dragFixedDistance = getDragFixedDistance(mFixedPoint, mDragPoint);
float fixedRadius = (float) (mFixedMaxRadius - dragFixedDistance / 14);
if (fixedRadius > mFixedMinRadius) {
//回弹
// 0 - 1
ValueAnimator valueAnimator = ValueAnimator.ofFloat(1);
valueAnimator.setDuration(250);
final PointF start = new PointF(mFixedPoint.x, mFixedPoint.y);
final PointF end = new PointF(mDragPoint.x, mDragPoint.y);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float percent = (float) animation.getAnimatedValue();
PointF pointF = BubbleUtils.getPointByPercent(end, start, percent);
updateDragPoint(pointF.x, pointF.y);
}
});
valueAnimator.setInterpolator(new OvershootInterpolator(3f));
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (mListener != null) {
mListener.restore();
}
}
});
valueAnimator.start();
} else {
//爆炸
if (mListener != null){
mListener.dismiss(mDragPoint);
}
}
}
private MessageBubbleListener mListener;
public void setMessageBubbleListener(MessageBubbleListener listener) {
this.mListener = listener;
}
public interface MessageBubbleListener {
// 还原
public void restore();
// 消失爆炸
public void dismiss(PointF pointF);
}
}