# 简介

Android之实现妙趣横生的粘连布局

# 原理介绍

public void quadTo (float x1, float y1, float x2, float y2)
Add a quadratic bezier from the last point, approaching control point (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for this contour, the first point is automatically set to (0,0).
Parameters
x1 The x-coordinate of the control point on a quadratic curve
y1 The y-coordinate of the control point on a quadratic curve
x2 The x-coordinate of the end point on a quadratic curve
y2 The y-coordinate of the end point on a quadratic curve

/**
* 画贝塞尔曲线
*
* @param canvas
*/
private void drawBezier(Canvas canvas) {

/* 求三角函数 */
float sin = (float) Math.sin(atan);
float cos = (float) Math.cos(atan);

/* 四个点 */

float footerX1 = mFooterCircle.curx - mFooterCircle.curRadius * sin;
float footerY1 = mFooterCircle.cury + mFooterCircle.curRadius * cos;

float footerX2 = mFooterCircle.curx + mFooterCircle.curRadius * sin;
float footerY2 = mFooterCircle.cury - mFooterCircle.curRadius * cos;

float anchorX = ( mHeaderCircle.curx + mFooterCircle.curx ) / 2;
float anchorY = ( mHeaderCircle.cury + mFooterCircle.cury ) / 2;

/* 画贝塞尔曲线 */
mPath.reset();
mPath.lineTo(footerX2, footerY2);
canvas.drawPath(mPath, mPaint);
}          

# 设计思路

## 1、属性列表

public class AdherentLayout extends RelativeLayout {
private Circle mHeaderCircle = new Circle();
private Circle mFooterCircle = new Circle();

//画笔
private Paint mPaint = new Paint();
//画贝塞尔曲线的Path对象
private Path mPath = new Path();
//粘连的颜色
private int mColor = Color.rgb(247,82,49);
//是否粘连着
//本View初始宽度、高度
private int mOriginalWidth;
private int mOriginalHeight;
//是否第一次onSizeChanged
private boolean isFirst = true;
//用户添加的视图（可以不添加）
private View mView;
//是否正在进行动画中
private boolean isAnim = false;
//记录按下的x、y
float mDownX;
float mDownY;
//本View的左上角x、y
private float mX;
private float mY;
//父控件左、上内边距
//默认粘连的最大长度
//头部圆缩小时不能小于这个最小半径
//是否允许可以扯断
private boolean isDismissed = true;
//是否按下
boolean isDown = false;
...
}

/**
* 圆点类
*/
private class Circle{
/**
* 初始坐标x,y
*/
float ox;
float oy;
/**
* 当前坐标x,y
*/
float curx;
float cury;
//初始半径
//当前半径
}

## 2、圆点绘制

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (isFirst && w > 0 && h > 0) {
mView = getChildAt(0);
//记录初始宽高，用于复原
mOriginalWidth = w;
mOriginalHeight = h;
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
mX = getX()-lp.leftMargin;//起始位置
mY = getY()-lp.topMargin;
ViewGroup mViewGroup = (ViewGroup) getParent();
if(mViewGroup!=null){
}
reset();
isFirst = false;
}
}

/**
* 重置所有参数
*/
public void reset() {
setWidthAndHeight(mOriginalWidth, mOriginalHeight);
if (mView != null) {
if(isFirst){
mView.setX(0);
mView.setY(0);
}else{
}
}
isAnim = false;
}

**
* 根据内边距返回圆的半径
* @return
*/
return
(float)(Math.min(
}

## 3、圆点脱离

move时计算移动距离，然后根据这个距离去修改mFooterCircle的位置即可

@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);

if (isAnim) return true;

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setWidthAndHeight(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
if (mView != null) {
}
mDownX = event.getRawX();
mDownY = event.getRawY();
//标记按下
isDown = true;
break;
case MotionEvent.ACTION_MOVE:
if(!isDown) break;
//偏移
float detalX = event.getRawX()-mDownX;
float detalY = event.getRawY()-mDownY;

mFooterCircle.curx = mFooterCircle.ox+detalX;
mFooterCircle.cury = mFooterCircle.oy+detalY;
if (mView != null) {
}
break;
case MotionEvent.ACTION_UP:
...
}

## 4、绘制贝塞尔曲线，起始圆缩放

**
* 处理粘连效果逻辑
*/
//两圆心的距离
float distance = (float) Math.sqrt(Math.pow(mFooterCircle.curx - mHeaderCircle.ox, 2) + Math.pow(mFooterCircle.cury - mHeaderCircle.oy, 2));
//缩放比例
float scale = 1 - distance / mMaxAdherentLength;
if (distance > mMaxAdherentLength && isDismissed) {
}
else
}

## 5、松开手指，回弹动画

/* x方向 */
ValueAnimator xValueAnimator = ValueAnimator.ofFloat(mFooterCircle.curx, mFooterCircle.ox);
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFooterCircle.curx = (float) (Float)animation.getAnimatedValue();
invalidate();
}
});

/**
* 开始粘连动画
*/
private void startAnim() {

/* x方向 */
ValueAnimator xValueAnimator = ValueAnimator.ofFloat(mFooterCircle.curx, mFooterCircle.ox);
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFooterCircle.curx = (float) (Float)animation.getAnimatedValue();
invalidate();
}
});

/* y方向 */
ValueAnimator yValueAnimator = ValueAnimator.ofFloat(mFooterCircle.cury, mFooterCircle.oy);
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFooterCircle.cury = (float) (Float)animation.getAnimatedValue();
invalidate();
}
});

/* 用户添加的视图x、y方向 */
ObjectAnimator objectAnimator = null;
if (mView != null) {
objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mView, pvhX, pvhY);
}

/* 动画集合 */
AnimatorSet animSet = new AnimatorSet();
if (mView != null)
animSet.playTogether(xValueAnimator,yValueAnimator,objectAnimator);
else
animSet.playTogether(xValueAnimator,yValueAnimator);
animSet.setInterpolator(new BounceInterpolator());
animSet.setDuration(400);
animSet.start();
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
reset();
}
});
}

# 写在最后

• 本文已收录于以下专栏：

## 仿QQ 拖动小红点原理及其实现

qq小红点可以拖动相信大家已经玩过了,用户体验也非常的好.今天就来搞个demo出来,相信看完这个博文大家一定能自己实现下. 效果很不错 效果图! 思路: 小红点是可以拖拽到原来的控件外面...
• u011748648
• 2015年08月31日 12:54
• 2192

## 【iOS效果集】实现QQ消除小红点（一键退朝）效果

QQ上黏黏的小红点很好玩有木有，于是自己也想实现一番，看到iOS实现的人比较少，Android的比较多，于是这个就用iOS来实现哈~ 效果图： 调试图： 其实从实现来讲，我是先实现第二...
• XieYupeng520
• 2016年01月28日 14:23
• 3582

## 高仿QQ消息，可以下拉刷新带小红点

• u011625768
• 2015年12月08日 11:06
• 1206

## 模仿手机QQ红点消除功能

• 2015年11月18日 16:13
• 8.94MB
• 下载

## 手机QQ一键消除红点功能创造灵感

• klxh2009
• 2015年11月20日 12:44
• 429

## 仿QQ拖动红点消除

• 2015年08月25日 11:50
• 710KB
• 下载

## QQ消除小红点（一键退朝）动画。

• wsh7365062
• 2016年09月05日 17:19
• 383

## 仿qq拖拽红点效果

• 2016年07月05日 18:46
• 22.02MB
• 下载

## 博客《自定义控件三部曲之绘图篇（十五）——QQ红点拖动删除效果实现（基本原理篇）》

• 2016年06月08日 20:21
• 1.75MB
• 下载

## 类似微信qq的fragment加上tab的集合，包含了小红点

• 2015年09月22日 14:23
• 2.63MB
• 下载

举报原因： 您举报文章：模仿手机QQ红点消除功能 色情 政治 抄袭 广告 招聘 骂人 其他 (最多只允许输入30个字)