微仿QQ消息提示点拖拽功能

转载请注明出处 http://blog.csdn.net/u011453163/article/details/51598731

很久以前,发现QQ有一个很有趣的功能,就是未读消息的红点是可以拖拽的,而且在任何地方都可以随意拖拽,并且有一个弹性的动画,非常有趣,而且也是一个非常方便的功能,于是总像仿制一个,虽说仿制,但也只是他的拖拽功能,弹性效果还是能力有限。

不多说 先上效果

这里写图片描述

一个自定义的view 使用方式也很简单

<com.weizhenbin.show.widget.VanishView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:text="5"
        android:layout_alignParentBottom="true"
        android:gravity="center"
        android:textColor="#fff"
        android:id="@+id/vv"
        android:layout_marginBottom="35dp"
        android:layout_marginLeft="80dp"
        android:background="@drawable/shape_red_bg"/>

然后先看下源码

**
 * Created by weizhenbin on 16/6/1.
 * <p/>
 * 一个可以随意拖动的view
 */
public class VanishView extends TextView {
    private Context context;
    /**窗口管理器*/
    private WindowManager windowManager;

    /**用来存储镜像的imageview*/
    private ImageView iv;

    /** 状态栏高度*/
    private int statusHeight = 0;

    /**按下的坐标x 相对于view自身*/
    private int dx = 0;

    /**按下的坐标y 相对于view自身*/
    private int dy = 0;

    /**镜像bitmap*/
    private Bitmap tmp;

    /**按下的坐标x 相对于屏幕*/
    private float downX = 0;

    /**按下的坐标y 相对于屏幕*/
    private float downY = 0;

    /**属性动画 用于回弹效果*/
    private ValueAnimator animator;

    /**窗口参数*/
    private WindowManager.LayoutParams mWindowLayoutParams;

    /**接口对象*/
    private OnListener listener;
    public VanishView(Context context) {
        super(context);
        init(context);
    }

    public VanishView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public VanishView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        this.context = context;
        windowManager = ((Activity) context).getWindowManager();
        statusHeight = getStatusHeight(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                dx = (int) event.getX();
                dy = (int) event.getY();
                downX = event.getRawX();
                downY = event.getRawY();
                addWindow(context, event.getRawX(), event.getRawY());
                setVisibility(INVISIBLE);
                break;
            case MotionEvent.ACTION_MOVE:
                mWindowLayoutParams.x = (int) (event.getRawX() - dx);
                mWindowLayoutParams.y = (int) (event.getRawY() - statusHeight - dy);
                windowManager.updateViewLayout(iv, mWindowLayoutParams);
                break;
            case MotionEvent.ACTION_UP:
                int distance=distance(new MyPoint(event.getRawX(), event.getRawY()), new MyPoint(downX, downY));
                if(distance<400) {
                    scroll(new MyPoint(event.getRawX(), event.getRawY()), new MyPoint(downX, downY));
                }else {
                    if(listener!=null){
                        listener.onDismiss();
                    }
                    windowManager.removeView(iv);
                }
                break;
        }
        return true;
    }

    /**
     * 构建一个窗口 用于存放和移动镜像
     * */
    private void addWindow(Context context, float downX, float dowmY) {
        mWindowLayoutParams = new WindowManager.LayoutParams();
        mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        iv = new ImageView(context);
        mWindowLayoutParams.format = PixelFormat.RGBA_8888;
        mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        mWindowLayoutParams.x = (int) (downX - dx);
        mWindowLayoutParams.y = (int) (dowmY - statusHeight - dy);
        //获取view的镜像bitmap
        this.setDrawingCacheEnabled(true);
        tmp = Bitmap.createBitmap(this.getDrawingCache());
        //释放缓存
        this.destroyDrawingCache();
        iv.setImageBitmap(tmp);
        windowManager.addView(iv, mWindowLayoutParams);
    }


    /**
     * 使用属性动画 实现缓慢回弹效果
     * */
    private void scroll(MyPoint start, MyPoint end) {
        animator = ValueAnimator.ofObject(new MyTypeEvaluator(), start, end);
        animator.setDuration(200);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                MyPoint point = (MyPoint) animation.getAnimatedValue();
                mWindowLayoutParams.x = (int) (point.x - dx);
                mWindowLayoutParams.y = (int) (point.y - statusHeight - dy);
                windowManager.updateViewLayout(iv, mWindowLayoutParams);
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                windowManager.removeView(iv);
                setVisibility(VISIBLE);
            }

        });
        animator.start();
    }

    /**
     * 计算两点的距离
     */
    private int distance(MyPoint point1, MyPoint point2) {
        int distance = 0;
        if (point1 != null && point2 != null) {
            float dx = point1.x - point2.x;
            float dy = point1.y - point2.y;
            distance = (int) Math.sqrt(dx * dx + dy * dy);
        }
        return distance;

    }

    /**
     * 获取状态栏的高度
     */
    private static int getStatusHeight(Context context) {
        int statusHeight = 0;
        Rect localRect = new Rect();
        ((Activity) context).getWindow().getDecorView().getWindowVisibleDisplayFrame(localRect);
        statusHeight = localRect.top;
        if (0 == statusHeight) {
            Class<?> localClass;
            try {
                localClass = Class.forName("com.android.internal.R$dimen");
                Object localObject = localClass.newInstance();
                int i5 = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString());
                statusHeight = context.getResources().getDimensionPixelSize(i5);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return statusHeight;
    }

    class MyPoint {
        float x;
        float y;

        public MyPoint(float x, float y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public String toString() {
            return "MyPoint{" +
                    "x=" + x +
                    ", y=" + y +
                    '}';
        }
    }

    class MyTypeEvaluator implements TypeEvaluator<MyPoint> {

        @Override
        public MyPoint evaluate(float fraction, MyPoint startValue, MyPoint endValue) {
            MyPoint point = startValue;
            point.x = startValue.x + fraction * (endValue.x - startValue.x);
            point.y = startValue.y + fraction * (endValue.y - startValue.y);
            return point;
        }
    }

    /**事件回调借口*/
   public interface OnListener{
        void onDismiss();
    }

    public void setListener(OnListener listener) {
        this.listener = listener;
    }

实现这一功能其实也不难,这个功能涉及到以下几个知识点

使用WindowManager添加一个view
使用ValueAnimator属性动画实现回弹效果
getX和getRawX,getY和getRawY的区别

1.使用WindowManager添加一个view

 /**
     * 构建一个窗口 用于存放和移动镜像
     * */
    private void addWindow(Context context, float downX, float dowmY) {
        mWindowLayoutParams = new WindowManager.LayoutParams();
        mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        iv = new ImageView(context);
        mWindowLayoutParams.format = PixelFormat.RGBA_8888;
        mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        mWindowLayoutParams.x = (int) (downX - dx);
        mWindowLayoutParams.y = (int) (dowmY - statusHeight - dy);
        //获取view的镜像bitmap
        this.setDrawingCacheEnabled(true);
        tmp = Bitmap.createBitmap(this.getDrawingCache());
        //释放缓存
        this.destroyDrawingCache();
        iv.setImageBitmap(tmp);
        windowManager.addView(iv, mWindowLayoutParams);
    }

这一步是为了投影一个镜像来达到拖动view的一个假像效果,使用imageview来显示。这里为了使投影没用偏移需要了解getX getRawX getY getRawY的区别

这里写图片描述

getX和getY 是相对于view自身的,getRawX和getRawY是像对屏幕的,这里还要扣除掉状态栏的高度。

2.移动

 windowManager.updateViewLayout(iv, mWindowLayoutParams);

3.使用ValueAnimator属性动画实现回弹效果

这里自定义了TypeEvaluator实现点的位移动画

 class MyTypeEvaluator implements TypeEvaluator<MyPoint> {

        @Override
        public MyPoint evaluate(float fraction, MyPoint startValue, MyPoint endValue) {
            MyPoint point = startValue;
            point.x = startValue.x + fraction * (endValue.x - startValue.x);
            point.y = startValue.y + fraction * (endValue.y - startValue.y);
            return point;
        }
    }


  /**
     * 使用属性动画 实现缓慢回弹效果
     * */
    private void scroll(MyPoint start, MyPoint end) {
        animator = ValueAnimator.ofObject(new MyTypeEvaluator(), start, end);
        animator.setDuration(200);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                MyPoint point = (MyPoint) animation.getAnimatedValue();
                mWindowLayoutParams.x = (int) (point.x - dx);
                mWindowLayoutParams.y = (int) (point.y - statusHeight - dy);
                windowManager.updateViewLayout(iv, mWindowLayoutParams);
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                windowManager.removeView(iv);
                setVisibility(VISIBLE);
            }

        });
        animator.start();
    }

通过属性动画实现一个回弹效果

4.触发消失的时机

  /**
     * 计算两点的距离
     */
    private int distance(MyPoint point1, MyPoint point2) {
        int distance = 0;
        if (point1 != null && point2 != null) {
            float dx = point1.x - point2.x;
            float dy = point1.y - point2.y;
            distance = (int) Math.sqrt(dx * dx + dy * dy);
        }
        return distance;
    }

计算两点之间的距离来触发一个回调事件。

  int distance=distance(new MyPoint(event.getRawX(), event.getRawY()), new MyPoint(downX, downY));
                if(distance<400) {
                    scroll(new MyPoint(event.getRawX(), event.getRawY()), new MyPoint(downX, downY));
                }else {
                    if(listener!=null){
                        listener.onDismiss();
                    }
                    windowManager.removeView(iv);
                }

代码分析就到这里,实现这个功能的核心代码都在这里。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值