Android 悬浮在Activity内的可拖动View

开发中经常会遇到要做一个可以悬浮在页面内的View,以便展示活动或者提醒什么的,跳出页面悬浮的View就消失。一种方法是利用纯代码后台生成的方式,利用WindowManager和Imageview实现图片的悬浮。但是这种方法需要SYSTEM_ALERT_WINDOW权限,国内很多深度定制的系统,像MIUI,EMUI,Flyme等会把这个权限关闭。因此这些系统下面就显示不出来了,需要一直提醒用户去开启这个权限,用户体验很不好。另一种比较简单的方法就是直接在布局里面把View放置在固定的位置,这种方法有个弊端,就是不能拖动,会挡住下面的内容。

最近忙里偷闲参考几个技术大神写的分散的功能,自己融合了一个可以自由拖动的悬浮窗效果,可以加载图片,吸附到屏幕边缘。这个只能在Activity内使用,App内和桌面悬浮还是建议使用系统的悬浮窗。话不多说,下面上代码。


首先在onMeasure中获取可拖动区域的宽和高

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(widthSize, heightSize);
        width = widthSize;
        heigh = heightSize;
        rect = new Rect(width-WIDTH, heigh/2-WIDTH/2, width, heigh/2+WIDTH/2);//绘制矩形的区域,靠拖动区域右边居中显示
    }
然后重写onDraw方法绘制出来一个色块,这时候是不能拖动

protected void onDraw(Canvas canvas) {
    if (mBitmap != null){
        Rect rectF = new Rect();
        rectF.set(0,0,mBitmap.getWidth(),mBitmap.getHeight());
        canvas.drawBitmap(mBitmap, rectF, rect, paint);
    }else {
        paint.setColor(Color.RED);//填充红色
        canvas.drawRect(rect, paint);//画矩形
    }
}
要想拖动就只能处理触摸事件了

public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch(event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            if(!rect.contains(x, y)) {
                return false;//没有在矩形上点击,不处理触摸消息
            }
            mStartX = x;
            mStartY = y;
            deltaX = x - rect.left;
            deltaY = y - rect.top;
            break;
        case MotionEvent.ACTION_MOVE:
            Rect old = new Rect(rect);
            //更新矩形的位置
            rect.left = x - deltaX;
            if (rect.left < 0){
                rect.left = 0;
            }
            rect.top = y - deltaY;
            if (rect.top < 0){
                rect.top = 0;
            }
            rect.right = rect.left + WIDTH;
            if (rect.right > width){
                rect.right = width;
                rect.left = width - WIDTH;
            }
            rect.bottom = rect.top + WIDTH;
            if (rect.bottom > heigh){
                rect.bottom = heigh;
                rect.top = heigh - WIDTH;
            }
            old.union(rect);//要刷新的区域,求新矩形区域与旧矩形区域的并集
            invalidate(old);//出于效率考虑,设定脏区域,只进行局部刷新,不是刷新整个view
            break;
        case MotionEvent.ACTION_UP:
            Rect oldl = new Rect(rect);
            //更新矩形的位置,吸附左右边界处理
            if (rect.left + WIDTH / 2 < width / 2){
                rect.left = 0;
            }else {
                rect.left = width - WIDTH;
            }
            rect.top = y - deltaY;
            if (rect.top < 0){
                rect.top = 0;
            }
            rect.right = rect.left + WIDTH;
            rect.bottom = rect.top + WIDTH;
            if (rect.bottom > heigh){
                rect.bottom = heigh;
                rect.top = heigh - WIDTH;
            }
            oldl.union(rect);
            invalidate(oldl);
            if (Math.abs(mStartX - x) < 10
                    && Math.abs(y - mStartY) < 10) {//捕捉点击事件
                if (mClickListener != null) {
                    mClickListener.onClick(this);
                }
            }
            break;
    }
    return true;//处理了触摸消息,消息不再传递
}
注释中都写了,我就不再讲解了,到这里色块就可以拖动了,我把它做了吸附在边界的处理,如果不需要可以把代码注释掉

后面就是加载图片的处理了,就不贴具体代码了,直接上完整代码

/**
 * Created by ton on 17/5/18.
 * 悬浮可拖拽View
 */

public class DragView extends View {

    private int WIDTH = 160;//拖动色块的大小

    private int heigh, width;
    private Rect rect = new Rect(0, 0, WIDTH, WIDTH);//绘制矩形的区域
    private int deltaX,deltaY;//点击位置和图形边界的偏移量
    private static Paint paint = new Paint();//画笔
    private Bitmap mBitmap = null;
    private String imgUrl;
    private OnClickListener mClickListener;
    private int mStartX,mStartY;

    public DragView(Context context, AttributeSet attrs) {
        super(context, attrs);
        WIDTH = 150;
        paint = new Paint();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(widthSize, heightSize);
        width = widthSize;
        heigh = heightSize;
        rect = new Rect(width-WIDTH, heigh/2-WIDTH/2, width, heigh/2+WIDTH/2);//绘制矩形的区域
    }

    /**
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    @Override
    protected void onDraw(Canvas canvas) {
        if (mBitmap != null){
            Rect rectF = new Rect();
            rectF.set(0,0,mBitmap.getWidth(),mBitmap.getHeight());
            canvas.drawBitmap(mBitmap, rectF, rect, paint);
        }else {
            paint.setColor(Color.RED);//填充红色
            canvas.drawRect(rect, paint);//画矩形
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch(event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if(!rect.contains(x, y)) {
                    return false;//没有在矩形上点击,不处理触摸消息
                }
                mStartX = x;
                mStartY = y;
                deltaX = x - rect.left;
                deltaY = y - rect.top;
                break;
            case MotionEvent.ACTION_MOVE:
                Rect old = new Rect(rect);
                //更新矩形的位置
                rect.left = x - deltaX;
                if (rect.left < 0){
                    rect.left = 0;
                }
                rect.top = y - deltaY;
                if (rect.top < 0){
                    rect.top = 0;
                }
                rect.right = rect.left + WIDTH;
                if (rect.right > width){
                    rect.right = width;
                    rect.left = width - WIDTH;
                }
                rect.bottom = rect.top + WIDTH;
                if (rect.bottom > heigh){
                    rect.bottom = heigh;
                    rect.top = heigh - WIDTH;
                }
                old.union(rect);//要刷新的区域,求新矩形区域与旧矩形区域的并集
                invalidate(old);//出于效率考虑,设定脏区域,只进行局部刷新,不是刷新整个view
                break;
            case MotionEvent.ACTION_UP:
                Rect oldl = new Rect(rect);
                //更新矩形的位置
                if (rect.left + WIDTH / 2 < width / 2){
                    rect.left = 0;
                }else {
                    rect.left = width - WIDTH;
                }
                rect.top = y - deltaY;
                if (rect.top < 0){
                    rect.top = 0;
                }
                rect.right = rect.left + WIDTH;
                rect.bottom = rect.top + WIDTH;
                if (rect.bottom > heigh){
                    rect.bottom = heigh;
                    rect.top = heigh - WIDTH;
                }
                oldl.union(rect);
                invalidate(oldl);
                if (Math.abs(mStartX - x) < 10
                        && Math.abs(y - mStartY) < 10) {//捕捉点击事件
                    if (mClickListener != null) {
                        mClickListener.onClick(this);
                    }
                }
                break;
        }
        return true;//处理了触摸消息,消息不再传递
    }

    @Override
    public void setOnClickListener(OnClickListener l) {
        this.mClickListener = l;
    }

    /***
     * 加载资源图片
     * @param resId
     */
    public void setImageResource(int resId){
        InputStream is = getContext().getResources().openRawResource(resId);
        Bitmap bmp = BitmapFactory.decodeStream(is);
        //图片重新裁剪,原图从中心点按显示大小裁剪
        int bw = bmp.getWidth(), bh = bmp.getHeight();
        int w = WIDTH, h = WIDTH;
        if (bw / w >= bh / h) {
            mBitmap = Bitmap.createBitmap(bmp,(bw-bh)/2,0,w*bh/h,bh);
        }
        else {
            mBitmap = Bitmap.createBitmap(bmp,0,(bh-bw)/2,bw,h*bw/w);
        }
        invalidate();
    }

    /****
     * 使用网络图片
     * @param imageUrl
     */
    public void setImageUrl(String imageUrl){
        if (imageUrl.startsWith("http://")||imageUrl.startsWith("https://")){
            imgUrl = imageUrl;
        }else {
            Log.d("DragViewDemo","image url error !");
            return;
        }
        if (mBitmap == null && imgUrl.length() > 0){
            ImageLoader.getInstance().loadImage(imgUrl, new ImageLoadingListener() {
                @Override
                public void onLoadingStarted(String s, View view) {

                }

                @Override
                public void onLoadingFailed(String s, View view, FailReason failReason) {

                }

                @Override
                public void onLoadingComplete(String s, View view, Bitmap bitmap) {

                    int bw = bitmap.getWidth(), bh = bitmap.getHeight();
                    int w = WIDTH, h = WIDTH;
                    if (bw / w >= bh / h) {
                        mBitmap = Bitmap.createBitmap(bitmap,(bw-bh)/2,0,w*bh/h,bh);
                    }
                    else {
                        mBitmap = Bitmap.createBitmap(bitmap,0,(bh-bw)/2,bw,h*bw/w);
                    }
                    invalidate();
                }

                @Override
                public void onLoadingCancelled(String s, View view) {

                }
            });
        }
    }
}
项目已上传到GitHub,有需要的自己去下载 Git地址。怕砖,各位轻点,多多指教~

参考资料:https://www.oschina.net/code/snippet_54100_6262

http://blog.csdn.net/tianjian4592/article/details/45031663

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(widthSize, heightSize);
        width = widthSize;
        heigh = heightSize;
        rect = new Rect(width-WIDTH, heigh/2-WIDTH/2, width, heigh/2+WIDTH/2);//绘制矩形的区域
    }

你可能感兴趣的:(技术,android,随笔)