[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: [email protected]
  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-10-12 19:53:54

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

腾讯大牛动态教学:Android 仿微信 QQ 图片裁剪,赶紧收藏起来!

前言 在平时开发中,经常需要实现这样的功能,拍照 - 裁剪,相册 - 裁剪.当然,系统也有裁剪的功能,但是由于机型,系统兼容性等问题,在实际开发当中,我们通常会自己进行实现.今天,就让我们一起来看看怎样实现. 这篇博客实现的功能主要有仿微信,QQ 上传图像裁剪功能,包括拍照,从相册选取.裁剪框的样式有圆形,正方形,九宫格. 主要讲解的功能点 使用说明 整体的实现思路 裁剪框的实现 图片缩放的实现,包括放大,缩小,移动,裁剪等 我们先来看看我们实现的效果图 使用说明 有两种调用方式 第一种 第一种

Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮

前言 之前写过屏蔽系统导航栏功能的文章,具体可看Android6.0 源码修改之屏蔽导航栏虚拟按键(Home和RecentAPP)/动态显示和隐藏NavigationBar 在某些特殊定制的版本中要求完全去掉导航栏,那么当用户点进一些系统自带的应用界面如设置.联系人等,就没法退出了,虽然可以在actionBar中添加back按钮,但总不能每一个app都去添加吧.所以灵机一动我们就给系统添加一个全屏可拖拽的浮窗按钮,点击的时候处理返回键的逻辑.它大概长这样(审美可能丑了点,你们可以自由发挥) 图1

android开发技巧——仿新版QQ锁屏下弹窗(转)

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

android开发技巧——仿新版QQ锁屏下弹窗

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

Android仿苹果版QQ下拉刷新实现(一) ——打造简单平滑的通用下拉刷新控件

前言: 因为公司人员变动原因,导致了博主四个月没有动安卓,一直在做IOS开发,如今接近年前,终于可以花一定的时间放在安卓上了.好了,废话不多说,今天我们要带来的效果是苹果版本的QQ下拉刷新.首先看一下目标效果以及demo效果:      因为此效果实现的步骤较多,所以今天博主要实现以上效果的第一步——打造一个通用的下拉刷新控件,具体效果如下: GIF图片比较大,还希望读者能耐心等待一下下从效果图中可以看出,我们的下拉刷新的滑动还是很流畅的,可能大多数开发者用的是XListview或者PullTo

Android:仿手机QQ好友动态的ListView

1.介绍: 本博客使用XListView模仿Android版QQ好友动态的ListView效果.效果截图例如以下: 效果图1 效果图2 这里面主要涉及的是ListView的布局问题,让我们看一下Item的布局文件吧. <?xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&

Android 仿微信QQ聊天界面

一些IM聊天软件的展现形式是左右分开的形式.比如说,别人给你发的信息全部靠左显示,你自己发给别人的信息全部靠右显示. 而我们的ListView很多时候是显示同一个布局,其实BaseAdapter中有2个重要的方法在大多数情况下我们并未使用到,一个是public int getViewTypeCount(),显示ListView中有多少种布局(默认是显示是1),像微信那样聊天界面,是有2种布局方式:另外一个getItemViewType(),可以让不同item条目加载不同的布局,下面就简单的模拟下

Android 仿映客直播间给主播发送礼物(实现连击效果)

效果图 类库的介绍 org.dync.giftlibrary.widget GiftAnimationUtil.java 动画类GiftControl.java 给外部调用的类(核心)GiftFrameLayout.java 礼物布局类GiftModel.java 给礼物布局填充数据类以上是礼物动画一(推荐使用礼物动画一,在demo中的Gift1Activity.java使用) LeftGiftControl.java 给外部调用的类(核心)LeftGiftsItemLayout.java 礼物

Android自定义控件:类QQ未读消息拖拽效果

QQ的未读消息,算是一个比较好玩的效果,趁着最近时间比较多,参考了网上的一些资料之后,本次实现一个仿照QQ未读消息的拖拽小红点,最终完成效果如下: 首先我们从最基本的原理开始分析,看一张图: 这个图该怎么绘制呢?实际上我们这里是先绘制两个圆,然后将两个圆的切点通过贝塞尔曲线连接起来就达到这个效果了.至于贝塞尔曲线的概念,这里就不多做解释了,百度一下就知道了. 切点怎么算呢,这里我们稍微复习一些初中的数学知识.看了这个图之后,求出四个切点应该是轻而易举了. 现在思路已经很清晰了,按照我们的思路,开