zoukankan      html  css  js  c++  java
  • Android 仿新版QQ的tab下面拖拽标记为已读的效果

    可拖拽的红点,(仿新版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"
                />
    public class MainActivity extends Activity implements DraggableFlagView.OnDraggableFlagViewListener, View.OnClickListener {
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            findViewById(R.id.main_btn).setOnClickListener(this);
    
            DraggableFlagView draggableFlagView = (DraggableFlagView) findViewById(R.id.main_dfv);
            draggableFlagView.setOnDraggableFlagViewListener(this);
            draggableFlagView.setText("7");
        }
    
        @Override
        public void onFlagDismiss(DraggableFlagView view) {
            Toast.makeText(this, "onFlagDismiss", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.main_btn:
                    Toast.makeText(this, "hello world", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    }

    DraggableFlagView代码:

    /**
     * Author: wangjie
     * Email: tiantian.china.2@gmail.com
     * Date: 12/23/14.
     */
    public class DraggableFlagView extends View {
        private static final String TAG = DraggableFlagView.class.getSimpleName();
    
        public static interface OnDraggableFlagViewListener {
            /**
             * 拖拽销毁圆点后的回调
             *
             * @param view
             */
            void onFlagDismiss(DraggableFlagView view);
        }
    
        private OnDraggableFlagViewListener onDraggableFlagViewListener;
    
        public void setOnDraggableFlagViewListener(OnDraggableFlagViewListener onDraggableFlagViewListener) {
            this.onDraggableFlagViewListener = onDraggableFlagViewListener;
        }
    
        public DraggableFlagView(Context context) {
            super(context);
            init(context);
        }
    
        private int patientColor = Color.RED;
    
        public DraggableFlagView(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DraggableFlagView);
            int indexCount = a.getIndexCount();
            for (int i = 0; i < indexCount; i++) {
                int attrIndex = a.getIndex(i);
                if (attrIndex == R.styleable.DraggableFlagView_color) {
                    patientColor = a.getColor(attrIndex, Color.RED);
                }
            }
            a.recycle();
            init(context);
        }
    
        public DraggableFlagView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init(context);
        }
    
        private Context context;
        private int originRadius; // 初始的圆的半径
        private int originWidth;
        private int originHeight;
    
        private int maxMoveLength; // 最大的移动拉长距离
        private boolean isArrivedMaxMoved; // 达到了最大的拉长距离(松手可以触发事件)
    
        private int curRadius; // 当前点的半径
        private int touchedPointRadius; // touch的圆的半径
        private Point startPoint = new Point();
        private Point endPoint = new Point();
    
        private Paint paint; // 绘制圆形图形
        private Paint textPaint; // 绘制圆形图形
        private Paint.FontMetrics textFontMetrics;
    
        private int[] location;
    
        private boolean isTouched; // 是否是触摸状态
    
        private Triangle triangle = new Triangle();
    
        private String text = ""; // 正常状态下显示的文字
    
        private void init(Context context) {
            this.context = context;
    
            setBackgroundColor(Color.TRANSPARENT);
    
            // 设置绘制flag的paint
            paint = new Paint();
            paint.setColor(patientColor);
            paint.setAntiAlias(true);
    
            // 设置绘制文字的paint
            textPaint = new Paint();
            textPaint.setAntiAlias(true);
            textPaint.setColor(Color.WHITE);
            textPaint.setTextSize(ABTextUtil.sp2px(context, 12));
            textPaint.setTextAlign(Paint.Align.CENTER);
            textFontMetrics = paint.getFontMetrics();
    
        }
    
        RelativeLayout.LayoutParams originLp; // 实际的layoutparams
        RelativeLayout.LayoutParams newLp; // 触摸时候的LayoutParams
    
        private boolean isFirst = true;
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
    //        Logger.d(TAG, String.format("onSizeChanged, w: %s, h: %s, oldw: %s, oldh: %s", w, h, oldw, oldh));
            if (isFirst && w > 0 && h > 0) {
                isFirst = false;
    
                originWidth = w;
                originHeight = h;
    
                originRadius = Math.min(originWidth, originHeight) / 2;
                curRadius = originRadius;
                touchedPointRadius = originRadius;
    
                maxMoveLength = ABAppUtil.getDeviceHeight(context) / 6;
    
                refreshStartPoint();
    
                ViewGroup.LayoutParams lp = this.getLayoutParams();
                if (RelativeLayout.LayoutParams.class.isAssignableFrom(lp.getClass())) {
                    originLp = (RelativeLayout.LayoutParams) lp;
                }
                newLp = new RelativeLayout.LayoutParams(lp.width, lp.height);
            }
    
        }
    
        @Override
        public void setLayoutParams(ViewGroup.LayoutParams params) {
            super.setLayoutParams(params);
            refreshStartPoint();
        }
    
        /**
         * 修改layoutParams后,需要重新设置startPoint
         */
        private void refreshStartPoint() {
            location = new int[2];
            this.getLocationInWindow(location);
    //        Logger.d(TAG, "location on screen: " + Arrays.toString(location));
    //            startPoint.set(location[0], location[1] + h);
            try {
                location[1] = location[1] - ABAppUtil.getTopBarHeight((Activity) context);
            } catch (Exception ex) {
            }
    
            startPoint.set(location[0], location[1] + getMeasuredHeight());
    //        Logger.d(TAG, "startPoint: " + startPoint);
        }
    
        Path path = new Path();
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.TRANSPARENT);
    
            int startCircleX = 0, startCircleY = 0;
            if (isTouched) { // 触摸状态
    
                startCircleX = startPoint.x + curRadius;
                startCircleY = startPoint.y - curRadius;
                // 绘制原来的圆形(触摸移动的时候半径会不断变化)
                canvas.drawCircle(startCircleX, startCircleY, curRadius, paint);
                // 绘制手指跟踪的圆形
                int endCircleX = endPoint.x;
                int endCircleY = endPoint.y;
                canvas.drawCircle(endCircleX, endCircleY, originRadius, paint);
    
                if (!isArrivedMaxMoved) { // 没有达到拉伸最大值
                    path.reset();
                    double sin = triangle.deltaY / triangle.hypotenuse;
                    double cos = triangle.deltaX / triangle.hypotenuse;
    
                    // A点
                    path.moveTo(
                            (float) (startCircleX - curRadius * sin),
                            (float) (startCircleY - curRadius * cos)
                    );
                    // B点
                    path.lineTo(
                            (float) (startCircleX + curRadius * sin),
                            (float) (startCircleY + curRadius * cos)
                    );
                    // C点
                    path.quadTo(
                            (startCircleX + endCircleX) / 2, (startCircleY + endCircleY) / 2,
                            (float) (endCircleX + originRadius * sin), (float) (endCircleY + originRadius * cos)
                    );
                    // D点
                    path.lineTo(
                            (float) (endCircleX - originRadius * sin),
                            (float) (endCircleY - originRadius * cos)
                    );
                    // A点
                    path.quadTo(
                            (startCircleX + endCircleX) / 2, (startCircleY + endCircleY) / 2,
                            (float) (startCircleX - curRadius * sin), (float) (startCircleY - curRadius * cos)
                    );
                    canvas.drawPath(path, paint);
                }
    
    
            } else { // 非触摸状态
                if (curRadius > 0) {
                    startCircleX = curRadius;
                    startCircleY = originHeight - curRadius;
                    canvas.drawCircle(startCircleX, startCircleY, curRadius, paint);
                    if (curRadius == originRadius) { // 只有在恢复正常的情况下才显示文字
                        // 绘制文字
                        float textH = textFontMetrics.bottom - textFontMetrics.top;
                        canvas.drawText(text, startCircleX, startCircleY + textH / 2, textPaint);
    //                    canvas.drawText(text, startCircleX, startCircleY, textPaint);
                    }
                }
    
            }
    
    //        Logger.d(TAG, "circleX: " + startCircleX + ", circleY: " + startCircleY + ", curRadius: " + curRadius);
    
    
        }
    
        float downX = Float.MAX_VALUE;
        float downY = Float.MAX_VALUE;
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            super.onTouchEvent(event);
    //        Logger.d(TAG, "onTouchEvent: " + event);
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    isTouched = true;
                    this.setLayoutParams(newLp);
                    endPoint.x = (int) downX;
                    endPoint.y = (int) downY;
    
                    changeViewHeight(this, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
                    postInvalidate();
    
                    downX = event.getX() + location[0];
                    downY = event.getY() + location[1];
    //                Logger.d(TAG, String.format("downX: %f, downY: %f", downX, downY));
    
                    break;
                case MotionEvent.ACTION_MOVE:
                    // 计算直角边和斜边(用于计算绘制两圆之间的填充去)
                    triangle.deltaX = event.getX() - downX;
                    triangle.deltaY = -1 * (event.getY() - downY); // y轴方向相反,所有需要取反
                    double distance = Math.sqrt(triangle.deltaX * triangle.deltaX + triangle.deltaY * triangle.deltaY);
                    triangle.hypotenuse = distance;
    //                Logger.d(TAG, "triangle: " + triangle);
                    refreshCurRadiusByMoveDistance((int) distance);
    
                    endPoint.x = (int) event.getX();
                    endPoint.y = (int) event.getY();
    
                    postInvalidate();
    
                    break;
                case MotionEvent.ACTION_UP:
                    isTouched = false;
                    this.setLayoutParams(originLp);
    
                    if (isArrivedMaxMoved) { // 触发事件
                        changeViewHeight(this, originWidth, originHeight);
                        postInvalidate();
                        if (null != onDraggableFlagViewListener) {
                            onDraggableFlagViewListener.onFlagDismiss(this);
                        }
                        Logger.d(TAG, "触发事件...");
                        resetAfterDismiss();
                    } else { // 还原
                        changeViewHeight(this, originWidth, originHeight);
                        startRollBackAnimation(500/*ms*/);
                    }
    
                    downX = Float.MAX_VALUE;
                    downY = Float.MAX_VALUE;
                    break;
            }
    
            return true;
        }
    
        /**
         * 触发事件之后重置
         */
        private void resetAfterDismiss() {
            this.setVisibility(GONE);
            text = "";
            isArrivedMaxMoved = false;
            curRadius = originRadius;
            postInvalidate();
        }
    
        /**
         * 根据移动的距离来刷新原来的圆半径大小
         *
         * @param distance
         */
        private void refreshCurRadiusByMoveDistance(int distance) {
            if (distance > maxMoveLength) {
                isArrivedMaxMoved = true;
                curRadius = 0;
            } else {
                isArrivedMaxMoved = false;
                float calcRadius = (1 - 1f * distance / maxMoveLength) * originRadius;
                float maxRadius = ABTextUtil.dip2px(context, 2);
                curRadius = (int) Math.max(calcRadius, maxRadius);
    //            Logger.d(TAG, "[refreshCurRadiusByMoveDistance]curRadius: " + curRadius + ", calcRadius: " + calcRadius + ", maxRadius: " + maxRadius);
            }
    
        }
    
    
        /**
         * 改变某控件的高度
         *
         * @param view
         * @param height
         */
        private void changeViewHeight(View view, int width, int height) {
            ViewGroup.LayoutParams lp = view.getLayoutParams();
            if (null == lp) {
                lp = originLp;
            }
            lp.width = width;
            lp.height = height;
            view.setLayoutParams(lp);
        }
    
        /**
         * 回滚状态动画
         */
        private ValueAnimator rollBackAnim;
    
        private void startRollBackAnimation(long duration) {
            if (null == rollBackAnim) {
                rollBackAnim = ValueAnimator.ofFloat(curRadius, originRadius);
                rollBackAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float value = (float) animation.getAnimatedValue();
                        curRadius = (int) value;
                        postInvalidate();
                    }
                });
                rollBackAnim.setInterpolator(new BounceInterpolator()); // 反弹效果
                rollBackAnim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        DraggableFlagView.this.clearAnimation();
                    }
                });
            }
            rollBackAnim.setDuration(duration);
            rollBackAnim.start();
        }
    
    
        /**
         * 计算四个坐标的三角边关系
         */
        class Triangle {
            double deltaX;
            double deltaY;
            double hypotenuse;
    
            @Override
            public String toString() {
                return "Triangle{" +
                        "deltaX=" + deltaX +
                        ", deltaY=" + deltaY +
                        ", hypotenuse=" + hypotenuse +
                        '}';
            }
        }
    
        public String getText() {
            return text;
        }
    
        public void setText(String text) {
            this.text = text;
            postInvalidate();
        }
    }
  • 相关阅读:
    锻炼意志力
    iOS 函数积累
    iOS8新特性 计算 cell 的高度
    iOS 国际化支持 设置
    CALayer 方法属性整理
    好的 blog 整理
    xcode7 打包上传至 app store
    ios 适配 (字体 以及 控件)
    iOS 适配设计与切图
    iOS app 切图
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/5353851.html
Copyright © 2011-2022 走看看