QQ聊天列表粘性控件

QQ聊天列表粘性控件

应用场景:未读数据的清除等
示例图

示例图

实现步骤:

1. 画静态图

  先画个两个静态的圆圈,一个大的,一个小的 ,要画的这个图的坐标如下图,通过Path类将上图中的路径坐标一一填充进方法中即可画出下图形状,然后算出两个圆对应下图的坐标替换进去即可。

这里写图片描述

protected void onDraw(Canvas canvas) {
    // 1. 画固定圆
    canvas.drawCircle(200f, 200f, 10f, mPaint);

    // 2. 画拖动圆
    canvas.drawCircle(100f, 100f, 15f, mPaint);

    // 3. 画中间连接线
    Path path = new Path();
    path.moveTo(300f, 300f);            // 跳转到A点, 
    path.quadTo(200f, 350f, 100f, 300f);// A到B 曲线,二次方贝塞尔曲线
    path.lineTo(100f, 400f);            // B到C 直线
    path.quadTo(200f, 350f, 300,400);   // C到D 曲线
    path.close();                       // D到A 直线
    canvas.drawPath(path, mPaint);
}

2. 把静止的值替换成变量

/** 固定圆的圆心 */
private PointF mStickCenter = new PointF(200f, 200f);
/** 固定圆的半径 */
private float mStickRadius = 10f;
/** 拖动圆的圆心 */
private PointF mDragCenter = new PointF(100f, 100f);
/** 拖动圆的半径 */
private float mDragRadius = 15f;

private PointF[] mStickPoints = new PointF[] { 
        new PointF(300, 300),  // A点
        new PointF(300, 400)    // D点
};
private PointF[] mDragPoints = new PointF[] { 
        new PointF(100f, 300f), // B点
        new PointF(100f, 400f)  // C点
};

/** 控制点坐标 */
private PointF mCtrlPoint = new PointF(200f, 350f);

protected void onDraw(Canvas canvas) {
    // 1. 画固定圆
    canvas.drawCircle(mStickCenter.x, mStickCenter.y, mStickRadius, mPaint);

    // 2. 画拖动圆
    canvas.drawCircle(mDragCenter.x, mDragCenter.y, mDragRadius, mPaint);

    // 3. 画中间连接线
    Path path = new Path();
    // 移动到A点
    path.moveTo(mStickPoints[0].x, mStickPoints[0].y);
    // 曲线:A-->B
    path.quadTo(mCtrlPoint.x, mCtrlPoint.y, mDragPoints[0].x, mDragPoints[0].y);
    // 直线: B-->C 
    path.lineTo(mDragPoints[1].x, mDragPoints[1].y);
    // 曲线:C-->d
    path.quadTo(mCtrlPoint.x, mCtrlPoint.y, mStickPoints[1].x, mStickPoints[1].y);
    // 直线:D-->A 
    path.close();
    canvas.drawPath(path, mPaint);
}

3. 计算ABCD交叉点和控件点

  • 通过圆心和半径算出四个附着点和控制点
protected void onDraw(Canvas canvas) {
    // 计算直线的斜率
    double dx = (mStickCenter.x - mDragCenter.x);
    double dy = (mStickCenter.y - mDragCenter.y);
    double lineK = 0; 
    if (dx != 0) { // 除数不能为0
        lineK = dy / dx;
    }
    // 计算ABCD四个交叉点
    mDragPoints = GeometryUtil.getIntersectionPoints(mDragCenter, mDragRadius, lineK);
    mStickPoints = GeometryUtil.getIntersectionPoints(mStickCenter, mStickRadius, lineK);
    // 计算控件点
    mCtrlPoint = GeometryUtil.getMiddlePoint(mDragCenter, mStickCenter);

    ...
}

4. 响应按下和拖动事件

  • 在onTouchEvent里面判断手指按下和抬起时将手指的坐标设置成拖拽圆的圆心并重绘界面。
public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            mDragCenter.set(event.getX(), event.getY());
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            break;
        }
        return true;
    }

5. 绘制参考圆

protected void onDraw(Canvas canvas) {
...
// 绘制参考圆:断开的最大范围
    mPaint.setStyle(Style.STROKE);
    canvas.drawCircle(mStickCenter.x, mStickCenter.y, mMaxRange, mPaint);
    mPaint.setStyle(Style.FILL);
}

6. 拖拽处理

  • 思路分析:
    a. 拖动超出最大范围,则断开; (Move事件)
    b. 超出了最大范围松开手, 则消失; (Up事件)
    c. 没超出了最大范围松开手: 断开后,又放回最大范围内,则恢复显示; (Up事件)
    d. 没超出了最大范围松开手: 拖拽没有超出最大范围松开手,则弹回去; (Up事件)

6.1. 拖动超出最大范围,则断开

public boolean onTouchEvent(MotionEvent event) {
        float radius;
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mOutOfRange = false;
        case MotionEvent.ACTION_MOVE:
            ...
            // a. 拖动超出最大范围, 则断开;
            radius = GeometryUtil.getDistanceBetween2Points(mDragCenter, mStickCenter);
            if (radius > mMaxRange) {
                mOutOfRange = true;
                invalidate();
            }
            break;
}
protected void onDraw(Canvas canvas) {
    ... 
    // 2. 画拖动圆
    canvas.drawCircle(mDragCenter.x, mDragCenter.y, mDragRadius, mPaint);
    // 断开了则不绘制固定圆和连接部分
    if (!mOutOfRange) {
        // 1. 画固定圆
        ...
        // 3. 画中间连接线
        ...
    }
    ...
}

6.2 超出最大范围松开则消失

public boolean onTouchEvent(MotionEvent event) {
        float radius;
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mDisappear = false;
        case MotionEvent.ACTION_MOVE:
            ...
        case MotionEvent.ACTION_UP:
            // b. 在最大范围外松手,则消失;
            radius = GeometryUtil.getDistanceBetween2Points(mDragCenter, mStickCenter);
            if (radius > mMaxRange) { // 超出了最大范围
                mDisappear = true; 
                invalidate();
            } else {}       
            break;
}
// onDraw方法中:
// 消失了就三部分都不绘制了
if (!mDisappear) {
    // 2、绘制拖动圆
    canvas.drawCircle(mDragCenter.x, mDragCenter.y, mDragRadius, mPaint);
    // 断开了则不绘制固定圆和连接部分
    if (!mOutOfRange) {
        // 1. 画固定圆
        canvas.drawCircle(mStickCenter.x, mStickCenter.y, mStickRadius, mPaint);
        // 3. 画中间连接线
        ...
    }
}

6.3. 超出了最大范围松开手又放回去

public boolean onTouchEvent(MotionEvent event) {
    ...
    case MotionEvent.ACTION_UP:
        // b. 在最大范围外松手,则消失;
        radius = GeometryUtil.getDistanceBetween2Points(mDragCenter, mStickCenter);
        if (radius > mMaxRange) { // 超出最大范围
            ...
        } else { // 没有超出了最大范围松开
            if (mOutOfRange) { // 断开
                // c. 断开后,又放回最大范围内松开,则恢复显示;
                mDragCenter.set(mStickCenter.x, mStickCenter.y);
                invalidate();
            } else { // d. 拖拽没有超出最大范围松开手,则弹回去;
            }
        }
        break;
    }
    return true;
}

6.4. 拖拽没有超出最大范围松开手则弹回去

public boolean onTouchEvent(MotionEvent event) {
    ...
    case MotionEvent.ACTION_UP:
        ...
        if (radius > mMaxRange) { // 超出了最大范围
            ...
        } else { // 没有超出了最大范围松开
            if (mOutOfRange) { // 
                // c. 断开后,又放回最大范围内松开,则恢复显示;
                ...
            } else {
                // d. 拖拽没有超出最大范围松开手,则弹回去;
                final PointF start = new PointF(mDragCenter.x, mDragCenter.y);
                final PointF end = mStickCenter;
                ValueAnimator animator = ValueAnimator.ofFloat(1);
                animator.addUpdateListener(new AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float percent = animation.getAnimatedFraction();
                        PointF pointByPercent = GeometryUtil.getPointByPercent(start, end, percent);
                        mDragCenter.set(pointByPercent);
                        invalidate();
                    }
                });
                animator.setInterpolator(new OvershootInterpolator(4));
                animator.setDuration(300);
                animator.start();           
           }
        }
        break;
    }
    return true;
}

7. 计算固定圆半径

protected void onDraw(Canvas canvas) {
    // 计算固定圆半径: 在一定范围内,拖动圆和固定圆的距离越远,固定圆的半径越小
    float radius = GeometryUtil.getDistanceBetween2Points(mDragCenter, mStickCenter); 
    float startRadius = mStickRadius;
    float endRadius = mStickRadius * 0.2f;
    if (radius > mMaxRange) {
        radius = mMaxRange;
    }
    float percent = radius / mMaxRange;
    float tempStickRadius = GeometryUtil.evaluateValue(percent, startRadius, endRadius);

      // 下面要改两个地方,替换为tempStickRadius
      ...
    }  

以上是实现的主要代码:
完整源码:点击下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值