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
...
}
以上是實現的主要代碼:
完整源碼:點擊下載