zoukankan      html  css  js  c++  java
  • [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 }
  • 相关阅读:
    Android Studio 解决unspecified on project app resolves to an APK archive which is not supported
    IOS 一句代码搞定启动引导页
    iOS—最全的真机测试教程
    Hue的三大特点、三大功能和架构
    CentOS和Ubuntu系统下安装vsftp(助推大数据部署搭建)
    Starting vsftpd for vsftpd: [FAILED]问题的解决
    大数据搭建各个子项目时配置文件技巧(适合CentOS和Ubuntu系统)(博主推荐)
    CentOS6.5下Cloudera安装搭建部署大数据集群(图文分五大步详解)(博主强烈推荐)
    Hue的全局配置文件hue.ini(图文详解)
    Cloudera Hue是什么?
  • 原文地址:https://www.cnblogs.com/tiantianbyconan/p/4182929.html
Copyright © 2011-2022 走看看