首页 > 代码库 > [Android]仿新版QQ的tab下面拖拽标记为已读的效果
[Android]仿新版QQ的tab下面拖拽标记为已读的效果
以下内容为原创,欢迎转载,转载请注明
来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/4182929.html
可拖拽的红点,(仿新版QQ,tab下面拖拽标记为已读的效果),拖拽一定的距离可以消失回调。
GitHub:DraggableFlagView(https://github.com/wangjiegulu/DraggableFlagView)
实现原理:
当根据touch事件的移动,不断调用onDraw()方法进行刷新绘制。
*注意:这里原来的小红点称为红点A;根据手指移动绘制的小红点称为红点B。
touch事件移动的时候需要处理的逻辑:
1. 红点A的半径根据滑动的距离会不断地变小。
2. 红点B会紧随手指的位置移动。
3. 在红点A和红点B之间需要用贝塞尔曲线绘制连接区域。
4. 如果红点A和红点B之间的间距达到了设置的最大的距离,则表示,这次的拖拽会有效,一旦放手红点就会消失。
5. 如果达到了第4中情况,则红点A和中间连接的贝塞尔曲线不会被绘制。
6. 如果红点A和红点B之间的距离没有达到设置的最大的距离,则放手后,红点B消失,红点A从原来变小的半径使用反弹动画变换到原来最初的状态
一些工具类需要依赖 AndroidBucket(https://github.com/wangjiegulu/AndroidBucket),nineoldandroid
使用方式:
<com.wangjie.draggableflagview.DraggableFlagView xmlns:dfv="http://schemas.android.com/apk/res/com.wangjie.draggableflagview" android:id="@+id/main_dfv" android:layout_width="20dp" android:layout_height="20dp" android:layout_alignParentBottom="true" android:layout_margin="15dp" dfv:color="#FF3B30" />
1 public class MainActivity extends Activity implements DraggableFlagView.OnDraggableFlagViewListener, View.OnClickListener { 2 3 @Override 4 public void onCreate(Bundle savedInstanceState) { 5 super.onCreate(savedInstanceState); 6 setContentView(R.layout.main); 7 findViewById(R.id.main_btn).setOnClickListener(this); 8 9 DraggableFlagView draggableFlagView = (DraggableFlagView) findViewById(R.id.main_dfv);10 draggableFlagView.setOnDraggableFlagViewListener(this);11 draggableFlagView.setText("7");12 }13 14 @Override15 public void onFlagDismiss(DraggableFlagView view) {16 Toast.makeText(this, "onFlagDismiss", Toast.LENGTH_SHORT).show();17 }18 19 @Override20 public void onClick(View v) {21 switch (v.getId()) {22 case R.id.main_btn:23 Toast.makeText(this, "hello world", Toast.LENGTH_SHORT).show();24 break;25 }26 }27 }
DraggableFlagView代码:
1 /** 2 * Author: wangjie 3 * Email: tiantian.china.2@gmail.com 4 * Date: 12/23/14. 5 */ 6 public class DraggableFlagView extends View { 7 private static final String TAG = DraggableFlagView.class.getSimpleName(); 8 9 public static interface OnDraggableFlagViewListener { 10 /** 11 * 拖拽销毁圆点后的回调 12 * 13 * @param view 14 */ 15 void onFlagDismiss(DraggableFlagView view); 16 } 17 18 private OnDraggableFlagViewListener onDraggableFlagViewListener; 19 20 public void setOnDraggableFlagViewListener(OnDraggableFlagViewListener onDraggableFlagViewListener) { 21 this.onDraggableFlagViewListener = onDraggableFlagViewListener; 22 } 23 24 public DraggableFlagView(Context context) { 25 super(context); 26 init(context); 27 } 28 29 private int patientColor = Color.RED; 30 31 public DraggableFlagView(Context context, AttributeSet attrs) { 32 super(context, attrs); 33 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DraggableFlagView); 34 int indexCount = a.getIndexCount(); 35 for (int i = 0; i < indexCount; i++) { 36 int attrIndex = a.getIndex(i); 37 if (attrIndex == R.styleable.DraggableFlagView_color) { 38 patientColor = a.getColor(attrIndex, Color.RED); 39 } 40 } 41 a.recycle(); 42 init(context); 43 } 44 45 public DraggableFlagView(Context context, AttributeSet attrs, int defStyle) { 46 super(context, attrs, defStyle); 47 init(context); 48 } 49 50 private Context context; 51 private int originRadius; // 初始的圆的半径 52 private int originWidth; 53 private int originHeight; 54 55 private int maxMoveLength; // 最大的移动拉长距离 56 private boolean isArrivedMaxMoved; // 达到了最大的拉长距离(松手可以触发事件) 57 58 private int curRadius; // 当前点的半径 59 private int touchedPointRadius; // touch的圆的半径 60 private Point startPoint = new Point(); 61 private Point endPoint = new Point(); 62 63 private Paint paint; // 绘制圆形图形 64 private Paint textPaint; // 绘制圆形图形 65 private Paint.FontMetrics textFontMetrics; 66 67 private int[] location; 68 69 private boolean isTouched; // 是否是触摸状态 70 71 private Triangle triangle = new Triangle(); 72 73 private String text = ""; // 正常状态下显示的文字 74 75 private void init(Context context) { 76 this.context = context; 77 78 setBackgroundColor(Color.TRANSPARENT); 79 80 // 设置绘制flag的paint 81 paint = new Paint(); 82 paint.setColor(patientColor); 83 paint.setAntiAlias(true); 84 85 // 设置绘制文字的paint 86 textPaint = new Paint(); 87 textPaint.setAntiAlias(true); 88 textPaint.setColor(Color.WHITE); 89 textPaint.setTextSize(ABTextUtil.sp2px(context, 12)); 90 textPaint.setTextAlign(Paint.Align.CENTER); 91 textFontMetrics = paint.getFontMetrics(); 92 93 } 94 95 RelativeLayout.LayoutParams originLp; // 实际的layoutparams 96 RelativeLayout.LayoutParams newLp; // 触摸时候的LayoutParams 97 98 private boolean isFirst = true; 99 100 @Override101 protected void onSizeChanged(int w, int h, int oldw, int oldh) {102 super.onSizeChanged(w, h, oldw, oldh);103 // Logger.d(TAG, String.format("onSizeChanged, w: %s, h: %s, oldw: %s, oldh: %s", w, h, oldw, oldh));104 if (isFirst && w > 0 && h > 0) {105 isFirst = false;106 107 originWidth = w;108 originHeight = h;109 110 originRadius = Math.min(originWidth, originHeight) / 2;111 curRadius = originRadius;112 touchedPointRadius = originRadius;113 114 maxMoveLength = ABAppUtil.getDeviceHeight(context) / 6;115 116 refreshStartPoint();117 118 ViewGroup.LayoutParams lp = this.getLayoutParams();119 if (RelativeLayout.LayoutParams.class.isAssignableFrom(lp.getClass())) {120 originLp = (RelativeLayout.LayoutParams) lp;121 }122 newLp = new RelativeLayout.LayoutParams(lp.width, lp.height);123 }124 125 }126 127 @Override128 public void setLayoutParams(ViewGroup.LayoutParams params) {129 super.setLayoutParams(params);130 refreshStartPoint();131 }132 133 /**134 * 修改layoutParams后,需要重新设置startPoint135 */136 private void refreshStartPoint() {137 location = new int[2];138 this.getLocationInWindow(location);139 // Logger.d(TAG, "location on screen: " + Arrays.toString(location));140 // startPoint.set(location[0], location[1] + h);141 try {142 location[1] = location[1] - ABAppUtil.getTopBarHeight((Activity) context);143 } catch (Exception ex) {144 }145 146 startPoint.set(location[0], location[1] + getMeasuredHeight());147 // Logger.d(TAG, "startPoint: " + startPoint);148 }149 150 Path path = new Path();151 152 @Override153 protected void onDraw(Canvas canvas) {154 super.onDraw(canvas);155 canvas.drawColor(Color.TRANSPARENT);156 157 int startCircleX = 0, startCircleY = 0;158 if (isTouched) { // 触摸状态159 160 startCircleX = startPoint.x + curRadius;161 startCircleY = startPoint.y - curRadius;162 // 绘制原来的圆形(触摸移动的时候半径会不断变化)163 canvas.drawCircle(startCircleX, startCircleY, curRadius, paint);164 // 绘制手指跟踪的圆形165 int endCircleX = endPoint.x;166 int endCircleY = endPoint.y;167 canvas.drawCircle(endCircleX, endCircleY, originRadius, paint);168 169 if (!isArrivedMaxMoved) { // 没有达到拉伸最大值170 path.reset();171 double sin = triangle.deltaY / triangle.hypotenuse;172 double cos = triangle.deltaX / triangle.hypotenuse;173 174 // A点175 path.moveTo(176 (float) (startCircleX - curRadius * sin),177 (float) (startCircleY - curRadius * cos)178 );179 // B点180 path.lineTo(181 (float) (startCircleX + curRadius * sin),182 (float) (startCircleY + curRadius * cos)183 );184 // C点185 path.quadTo(186 (startCircleX + endCircleX) / 2, (startCircleY + endCircleY) / 2,187 (float) (endCircleX + originRadius * sin), (float) (endCircleY + originRadius * cos)188 );189 // D点190 path.lineTo(191 (float) (endCircleX - originRadius * sin),192 (float) (endCircleY - originRadius * cos)193 );194 // A点195 path.quadTo(196 (startCircleX + endCircleX) / 2, (startCircleY + endCircleY) / 2,197 (float) (startCircleX - curRadius * sin), (float) (startCircleY - curRadius * cos)198 );199 canvas.drawPath(path, paint);200 }201 202 203 } else { // 非触摸状态204 if (curRadius > 0) {205 startCircleX = curRadius;206 startCircleY = originHeight - curRadius;207 canvas.drawCircle(startCircleX, startCircleY, curRadius, paint);208 if (curRadius == originRadius) { // 只有在恢复正常的情况下才显示文字209 // 绘制文字210 float textH = textFontMetrics.bottom - textFontMetrics.top;211 canvas.drawText(text, startCircleX, startCircleY + textH / 2, textPaint);212 // canvas.drawText(text, startCircleX, startCircleY, textPaint);213 }214 }215 216 }217 218 // Logger.d(TAG, "circleX: " + startCircleX + ", circleY: " + startCircleY + ", curRadius: " + curRadius);219 220 221 }222 223 float downX = Float.MAX_VALUE;224 float downY = Float.MAX_VALUE;225 226 @Override227 public boolean onTouchEvent(MotionEvent event) {228 super.onTouchEvent(event);229 // Logger.d(TAG, "onTouchEvent: " + event);230 switch (event.getAction()) {231 case MotionEvent.ACTION_DOWN:232 isTouched = true;233 this.setLayoutParams(newLp);234 endPoint.x = (int) downX;235 endPoint.y = (int) downY;236 237 changeViewHeight(this, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);238 postInvalidate();239 240 downX = event.getX() + location[0];241 downY = event.getY() + location[1];242 // Logger.d(TAG, String.format("downX: %f, downY: %f", downX, downY));243 244 break;245 case MotionEvent.ACTION_MOVE:246 // 计算直角边和斜边(用于计算绘制两圆之间的填充去)247 triangle.deltaX = event.getX() - downX;248 triangle.deltaY = -1 * (event.getY() - downY); // y轴方向相反,所有需要取反249 double distance = Math.sqrt(triangle.deltaX * triangle.deltaX + triangle.deltaY * triangle.deltaY);250 triangle.hypotenuse = distance;251 // Logger.d(TAG, "triangle: " + triangle);252 refreshCurRadiusByMoveDistance((int) distance);253 254 endPoint.x = (int) event.getX();255 endPoint.y = (int) event.getY();256 257 postInvalidate();258 259 break;260 case MotionEvent.ACTION_UP:261 isTouched = false;262 this.setLayoutParams(originLp);263 264 if (isArrivedMaxMoved) { // 触发事件265 changeViewHeight(this, originWidth, originHeight);266 postInvalidate();267 if (null != onDraggableFlagViewListener) {268 onDraggableFlagViewListener.onFlagDismiss(this);269 }270 Logger.d(TAG, "触发事件...");271 resetAfterDismiss();272 } else { // 还原273 changeViewHeight(this, originWidth, originHeight);274 startRollBackAnimation(500/*ms*/);275 }276 277 downX = Float.MAX_VALUE;278 downY = Float.MAX_VALUE;279 break;280 }281 282 return true;283 }284 285 /**286 * 触发事件之后重置287 */288 private void resetAfterDismiss() {289 this.setVisibility(GONE);290 text = "";291 isArrivedMaxMoved = false;292 curRadius = originRadius;293 postInvalidate();294 }295 296 /**297 * 根据移动的距离来刷新原来的圆半径大小298 *299 * @param distance300 */301 private void refreshCurRadiusByMoveDistance(int distance) {302 if (distance > maxMoveLength) {303 isArrivedMaxMoved = true;304 curRadius = 0;305 } else {306 isArrivedMaxMoved = false;307 float calcRadius = (1 - 1f * distance / maxMoveLength) * originRadius;308 float maxRadius = ABTextUtil.dip2px(context, 2);309 curRadius = (int) Math.max(calcRadius, maxRadius);310 // Logger.d(TAG, "[refreshCurRadiusByMoveDistance]curRadius: " + curRadius + ", calcRadius: " + calcRadius + ", maxRadius: " + maxRadius);311 }312 313 }314 315 316 /**317 * 改变某控件的高度318 *319 * @param view320 * @param height321 */322 private void changeViewHeight(View view, int width, int height) {323 ViewGroup.LayoutParams lp = view.getLayoutParams();324 if (null == lp) {325 lp = originLp;326 }327 lp.width = width;328 lp.height = height;329 view.setLayoutParams(lp);330 }331 332 /**333 * 回滚状态动画334 */335 private ValueAnimator rollBackAnim;336 337 private void startRollBackAnimation(long duration) {338 if (null == rollBackAnim) {339 rollBackAnim = ValueAnimator.ofFloat(curRadius, originRadius);340 rollBackAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {341 @Override342 public void onAnimationUpdate(ValueAnimator animation) {343 float value = http://www.mamicode.com/(float) animation.getAnimatedValue();"Triangle{" +373 "deltaX=" + deltaX +374 ", deltaY=" + deltaY +375 ", hypotenuse=" + hypotenuse +376 ‘}‘;377 }378 }379 380 public String getText() {381 return text;382 }383 384 public void setText(String text) {385 this.text = text;386 postInvalidate();387 }388 }
[Android]仿新版QQ的tab下面拖拽标记为已读的效果
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。