[Android]仿新版QQ的tab下面拖拽标记为已读的效果

以下内容为原创,欢迎转载,转载请注明

来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/4182929.html

可拖拽的红点,(仿新版QQ,tab下面拖拽标记为已读的效果),拖拽一定的距离可以消失回调。

  

 

GitHub:DraggableFlagViewhttps://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     @Override
15     public void onFlagDismiss(DraggableFlagView view) {
16         Toast.makeText(this, "onFlagDismiss", Toast.LENGTH_SHORT).show();
17     }
18
19     @Override
20     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     @Override
101     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     @Override
128     public void setLayoutParams(ViewGroup.LayoutParams params) {
129         super.setLayoutParams(params);
130         refreshStartPoint();
131     }
132
133     /**
134      * 修改layoutParams后,需要重新设置startPoint
135      */
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     @Override
153     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     @Override
227     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 distance
300      */
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 view
320      * @param height
321      */
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                 @Override
342                 public void onAnimationUpdate(ValueAnimator animation) {
343                     float value = (float) animation.getAnimatedValue();
344                     curRadius = (int) value;
345                     postInvalidate();
346                 }
347             });
348             rollBackAnim.setInterpolator(new BounceInterpolator()); // 反弹效果
349             rollBackAnim.addListener(new AnimatorListenerAdapter() {
350                 @Override
351                 public void onAnimationEnd(Animator animation) {
352                     super.onAnimationEnd(animation);
353                     DraggableFlagView.this.clearAnimation();
354                 }
355             });
356         }
357         rollBackAnim.setDuration(duration);
358         rollBackAnim.start();
359     }
360
361
362     /**
363      * 计算四个坐标的三角边关系
364      */
365     class Triangle {
366         double deltaX;
367         double deltaY;
368         double hypotenuse;
369
370         @Override
371         public String toString() {
372             return "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 }

 

时间: 2024-09-18 22:26:40

[Android]仿新版QQ的tab下面拖拽标记为已读的效果的相关文章

Android程序开发仿新版QQ锁屏下弹窗功能_Android

新版的qq,可以在锁屏下弹窗显示qq消息,正好目前在做的项目也需要这一功能.经过各种试验和资料查找,终于实现,过程不难,但是却有一些地方需要注意. 下面是实现过程. 1.使用Activity,而不是View QQ的弹窗一开始我以为是悬浮View,用WindowManager去添加,但是无论如何就是不显示,后来在朋友提示下换成Activity来实现,在锁屏状态下就能弹窗了. 2.Activity的设置 Activity需要进行以下设置,才可以在锁屏状态下弹窗. 首先是onCreate方法,需要添加

Android仿微信QQ设置图形头像裁剪功能_Android

最近在做毕业设计,想有一个功能和QQ一样可以裁剪头像并设置圆形头像,额,这是设计狮的一种潮流. 而纵观现在主流的APP,只要有用户系统这个功能,这个需求一般都是在(bu)劫(de)难(bu)逃(xue)! 图片裁剪实现方式有两种,一种是利用系统自带的裁剪工具,一种是使用开源工具Cropper.本节就为大家带来如何使用系统自带的裁剪工具进行图片裁剪~ 还是先来个简单的运行图. 额,简单说下,我待会会把代码写成小demo分享给大家,在文章末尾会附上github链接,需要的可以自行下载~ 下面来简单分

Android仿微信QQ设置图形头像裁剪功能

最近在做毕业设计,想有一个功能和QQ一样可以裁剪头像并设置圆形头像,额,这是设计狮的一种潮流. 而纵观现在主流的APP,只要有用户系统这个功能,这个需求一般都是在(bu)劫(de)难(bu)逃(xue)! 图片裁剪实现方式有两种,一种是利用系统自带的裁剪工具,一种是使用开源工具Cropper.本节就为大家带来如何使用系统自带的裁剪工具进行图片裁剪~ 还是先来个简单的运行图. 额,简单说下,我待会会把代码写成小demo分享给大家,在文章末尾会附上github链接,需要的可以自行下载~ 下面来简单分

Android仿手机QQ图案解锁功能

本文实例为大家分享了Android仿手机QQ图案解锁的具体代码,供大家参考,具体内容如下 ps:请不要再问我,为什么导入之后会乱码了. 其实,代码基本上都是从原生系统中提取的:LockPatternView.加密工具类,以及解锁逻辑等,我只是稍作修改,大家都知道,原生系统界面比较丑陋,因此,我特意把QQ的apk解压了,从中拿了几张图案解锁的图片,一个简单的例子就这样诞生了. 好了,废话不多说,我们来看看效果(最后两张是最新4.4系统,炫一下,呵呵): 1.最关健的就是那个自定义九宫格View,代

ios 像qq这样气泡可以拖拽的效果是如何实现的?

问题描述 ios 像qq这样气泡可以拖拽的效果是如何实现的? 有点好奇哈,倒是不知道如何实现,谢谢了-啊啊啊啊啊啊啊啊啊啊啊啊 解决方案 说下思路,监听点击与拖动事件,得到最新的坐标,再给view执行位移动画 解决方案二: 按压事件处理,让旗袍跟着用户触摸屏幕轨迹移动

Android DragVideo实现播放视频时任意拖拽的方法_Android

Android DragVideo实现播放视频时任意拖拽 DragVideo A Method to Drag the Video When Playing Video 一种在播放视频时,能够拖拽的方案 为什么有这个工程 经常在爱奇艺网站上看电影,看到如果滑动掩盖了播放窗口后,就后在最下面有一个小播放界面.并且这个播放界面,是可以任意拖拽的.感觉很酷 既然web端能实现,就想了想在移动端设备上,是否也能实现这个效果,于是就有了- 效果图: ------> 实现思路:1.播放视频的view选择Te

Android DragVideo实现播放视频时任意拖拽的方法

Android DragVideo实现播放视频时任意拖拽 DragVideo A Method to Drag the Video When Playing Video 一种在播放视频时,能够拖拽的方案 为什么有这个工程 经常在爱奇艺网站上看电影,看到如果滑动掩盖了播放窗口后,就后在最下面有一个小播放界面.并且这个播放界面,是可以任意拖拽的.感觉很酷 既然web端能实现,就想了想在移动端设备上,是否也能实现这个效果,于是就有了- 效果图: ------> 实现思路:1.播放视频的view选择Te

Watchhhh:让用户把3D家具商品模型拖拽到现实场景中检验展示效果

摘要: 服装电商是越来越讲究穿搭的场景了,但家具这种最需要套件化展示的商品却还远未实现类似的效果,Watchhhh( iPad)想要解决的问题就是让用户把3D家具商品模型拖拽到现实场景中检验展 服装电商是越来越讲究穿搭的场景了,但家具这种最需要套件化展示的商品却还远未实现类似的效果,Watchhhh( iPad)想要解决的问题就是让用户把3D家具商品模型拖拽到现实场景中检验展示效果.Watchhhh就是iPad上一个3D版家具卖场. watchhhh创建场景的方式主要两种,一是用户可以从多个角度

把3D家具商品模型拖拽到现实场景中检验展示效果

摘要: 服装电商是越来越讲究穿搭的场景了,但家具这种最需要套件化展示的商品却还远未实现类似的效果,Watchhhh( iPad)想要解决的问题就是让用户把3D家具商品模型拖拽到现实场景中检验展 服装电商是越来越讲究穿搭的场景了,但家具这种最需要套件化展示的商品却还远未实现类似的效果,Watchhhh( iPad)想要解决的问题就是让用户把3D家具商品模型拖拽到现实场景中检验展示效果.Watchhhh就是iPad上一个3D版家具卖场. watchhhh创建场景的方式主要两种,一是用户可以从多个角度