最近想做类似QQ消息拖拽气泡的效果,在GitHub上找到一个实现的不错的DragPointView,看到源码感觉不错就来分析一下。
代码比较简单,主要有DragPointView,DragPointViewWindow,DragViewHelper三个比较重要的类。
- DragPointView 原始的气泡
- DragPointViewWindow 滑动时的气泡,显示时隐藏原始的气泡
- DragViewHelper 拦截事件,创建DragPointViewWindow
其中DragPointView,DragPointViewWindow继承了 AbsDragPointView(继承TextView),是原始气泡的View和拖拽气泡的View。DragPointView依赖一个DragViewHelper,DragViewHelper负责拦截DragPointView触摸事件,并且添加一个全屏window来展示拖拽的气泡DragPointViewWindow。
为什么不是直接滑动DragPointView而要做一个DragPointView的复制的一份DragPointViewWindow来滑动呢?因为原始的DragPointView只能在自己的父控件内滑动,而我们要实现的QQ消息拖拽气泡在滑动时是全屏滑动的,所以拖动气泡是隐藏原始气泡,滑动复制了他的bitmap的DragPointViewWindow。
DragPointView
@Override
public void startRemove() {
dragViewHelper.startRemove();
}
private void init() {
dragViewHelper = new DragViewHelper(getContext(),this);
}
代码很简单,有自定义view属性,创建了一个DragViewHelper。我们看看DragViewHelper
public DragViewHelper(Context context, final DragPointView originView) {
this.context = context;
this.originView = originView;
this.originView.setOnTouchListener(this);//拦截了DragPointView 的事件
animRunnable = new Runnable() {
@Override
public void run() {
windowView.startRemove();
}
};
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_DOWN) {
ViewParent parent = v.getParent();
if (parent == null) {
return false;
}
parent.requestDisallowInterceptTouchEvent(true);//屏蔽父元素onInterceptTouchEvent判断,说明以后的ACTION_MOVE肯定能传过来
addViewToWindow();//ACTION_DOWN就创建DragPointViewWindow
}
return windowView.onTouchEvent(event);//又把事件传递给了DragPointViewWindow
}
public void addViewToWindow() {
if (windowManager == null) {
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
if (windowView == null) {
createWindowView();//创建DragPointViewWindow
}
if (windowParams == null ||
layoutParams == null) {
initParams();// 初始化windowParams ,layoutParams
}
if (container == null) {
container = new FrameLayout(context);// 全屏的FrameLayout
container.setClipChildren(false);//false允许子控件超出父布局绘制,这个很有用
container.setClipToPadding(false);//false允许子控件在父布局Padding内绘制
windowView.setLayoutParams(layoutParams);
container.addView(windowView, layoutParams);//DragPointViewWindow加入到FrameLayout
}
int[] ps = new int[2];
originView.getLocationInWindow(ps);//控件在其父窗口中的坐标位置,getLocationOnScreen整个屏幕上
layoutParams.setMargins(ps[0], ps[1], 0, 0);
layoutParams.width = originView.getWidth();
layoutParams.height = originView.getHeight();
windowView.setOrigView(originView);
originView.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(originView.getDrawingCache());//原本气泡的Bitmap
originView.setDrawingCacheEnabled(false);
windowView.setOrigBitmap(bitmap);
onPointDragListener = originView.getOnPointDragListener();
windowView.setVisibility(View.VISIBLE);
if(container.getParent() != null)
windowManager.removeView(container);
windowManager.addView(container, windowParams);// 加入到PhoneWindow
originView.setVisibility(View.INVISIBLE);
}
private void createWindowView() {
windowView = new DragPointViewWindow(context);
windowView.setCanDrag(originView.isCanDrag());
windowView.setCenterMinRatio(originView.getCenterMinRatio());
windowView.setCenterRadius(originView.getCenterRadius());
windowView.setColorStretching(originView.getColorStretching());
windowView.setDragRadius(originView.getDragRadius());
windowView.setClearSign(originView.getClearSign());
windowView.setSign(originView.getSign());
windowView.setMaxDragLength(originView.getMaxDragLength());
windowView.setRecoveryAnimBounce(originView.getRecoveryAnimBounce());
windowView.setRecoveryAnimDuration(originView.getRecoveryAnimDuration());
windowView.setRecoveryAnimInterpolator(originView.getRecoveryAnimInterpolator());
if (originView.getRemoveAnim() != null)
windowView.setRemoveAnim(originView.getRemoveAnim().setView(windowView));
windowView.setOnPointDragListener(this);
}
private void initParams() {
windowParams = new WindowManager.LayoutParams();// 全屏的FrameLayout的布局属性
windowParams.gravity = Gravity.LEFT | Gravity.TOP;
windowParams.format = PixelFormat.TRANSLUCENT;
windowParams.type = WindowManager.LayoutParams.TYPE_TOAST;
windowParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
windowParams.width = WindowManager.LayoutParams.MATCH_PARENT;
windowParams.height = WindowManager.LayoutParams.MATCH_PARENT;
// DragPointViewWindow在FrameLayout的布局属性
layoutParams = new FrameLayout.LayoutParams(originView.getWidth(), originView.getHeight());
}
我们看到,当有MotionEvent.ACTION_DOWN事件时,创建了一个显示原始气泡的DragPointViewWindow来代替进行全屏的滑动。这个DragPointViewWindow被添加到一个全屏的FrameLayout里面。这个FrameLayout是通过WindowManager直接添加到activity的PhoneWindow里面的。
DragPointViewWindow
DragPointViewWindow比较重要,要拦截滑动事件,绘制利用贝塞尔曲线做出的拖拽效果。
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!canDrag || ClearViewHelper.getInstance().isClearSigning(sign)
|| (mRecoveryAnim != null && mRecoveryAnim.isRunning())
|| (mRemoveAnim != null && mRemoveAnim.isRunning())) {
return super.onTouchEvent(event);
}
if (mRecoveryAnim == null || !mRecoveryAnim.isRunning()) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if(getParent() != null)
getParent().requestDisallowInterceptTouchEvent(true);//屏蔽父元素onInterceptTouchEvent判断,说明以后的ACTION_MOVE肯定能传过来
downX = event.getRawX();//初始点击位置,用来计算拖动效果
downY = event.getRawY();
isInCircle = true;
postInvalidate();//此时原始气泡已经不可见,绘制自己
break;
case MotionEvent.ACTION_MOVE:
float dx = (int) event.getRawX() - downX;
float dy = (int) event.getRawY() - downY;
mCenterCircle.x = mWidthHalf - dx;
mCenterCircle.y = mHeightHalf - dy;
mDistanceCircles = MathUtils.getDistance(mCenterCircle, mDragCircle);
mIsDragOut = mIsDragOut ? mIsDragOut : mDistanceCircles > mMaxDragLength;
setX(origX + dx);
setY(origY + dy);
postInvalidate();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
getParent().requestDisallowInterceptTouchEvent(false);
upX = getX();
upY = getY();
upAndCancelEvent();
break;
}
}
return true;
}
网友评论