首页 > 代码库 > [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下面拖拽标记为已读的效果