安卓悬浮窗口,  丝滑双指缩放视频窗口

该文详细介绍了如何在Android中结合跨应用浮动窗口和双指缩放功能,提供了ZoomView的源码,并讨论了事件分发问题,特别是在视频播放时的处理。文章通过改造ZoomView,实现了平移和缩放操作,并确保视图不会超出屏幕范围。最后,提到了在ZoomView中嵌入视频播放器时可能出现的问题及其解决方案。
摘要由CSDN通过智能技术生成

最重要的事情说前面: demo源码:

https://github.com/5800LDW/ProjectFloatingWindow

前言:

1.跨应用的浮动窗口在网上很多资料, 就不细说了。

2.双指缩放View 也很多资料, 可参考:

https://blog.csdn.net/zxq614/article/details/88873729

正文

下面进入正题, 如何把上述结合起来, 下面在前言2 的ZoomView 上进行改造。

下面帖一下修改后的ZoomView的代码:


public class ZoomView extends RelativeLayout {
    // 属性变量
    private float translationX; // 移动X
    private float translationY; // 移动Y
    private float scale = 1; // 伸缩比例

    // 移动过程中临时变量
    private float actionX;
    private float actionY;
    private float spacing;

    private int moveType; // 0=未选择,1=拖动,2=缩放

    /**
     * 初始化高度
     */
    private int originalHeight = 0;

    /**
     * 初始化宽度
     */
    private int originalWidth = 0;
    private int screenHeight = 0;
    private int screenWidth = 0;

    /**
     * 最低缩小比例>=0.1
     */
    private float minimumScale = 0.4f;

    private Context mContext;


    public ZoomView(Context context) {
        this(context, null);
        mContext = context;
    }

    public ZoomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        mContext = context;
    }

    public ZoomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        setClickable(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("TAG", "onDraw ");
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        Log.e("TAG", "onLayout ");

        if (originalHeight == 0) {
            originalHeight = getMeasuredHeight();
            originalWidth = getMeasuredWidth();

            if (mContext != null) {
                DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
                screenHeight = metrics.widthPixels;
                screenWidth = metrics.heightPixels;
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e("TAG", "onMeasure ");
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {


        Log.e("TAG", " onInterceptTouchEvent  ");
        getParent().requestDisallowInterceptTouchEvent(true);
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                moveType = 1;
                actionX = event.getRawX();
                actionY = event.getRawY();
                Log.e("TAG", ">>>>>>>>>>>ACTION_DOWN. actionX = " + actionX);
                Log.e("TAG", ">>>>>>>>>>>ACTION_DOWN. actionY = " + actionY);
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                moveType = 2;
                spacing = getSpacing(event);
//                degree = getDegree(event);
                break;
            case MotionEvent.ACTION_MOVE:

                if (moveType == 1) {

                    translationX = translationX + event.getRawX() - actionX;
                    ;
                    translationY = translationY + event.getRawY() - actionY;

                    //父view先变换位置就行了;
                    if (translationListener != null) {
                        translationListener.translation(translationX, translationY);
                    }

                    actionX = event.getRawX();
                    actionY = event.getRawY();


                } else if (moveType == 2) {

                    scale = scale * getSpacing(event) / spacing;

                    //这里的意思是, 让放大的最大高度不超过屏幕的宽度, 这样, 放大view后, 就不会超出屏幕, 无论横屏还是竖屏
                    int maxZoomViewHeight = screenHeight > screenWidth ? screenWidth : screenHeight;

                    //限制最小缩放比例
                    if (scale < getMinimumScale()) {
                        scale = getMinimumScale();
                    }


                    Log.e("TAG ", "1 scale = " + scale);
                    if (originalHeight * scale >= maxZoomViewHeight && originalHeight != 0) {
                        //这里需要乘以float, 因为int 除以float得出是int的值, 数据误差太大;
                        scale = maxZoomViewHeight * 1.0f / originalHeight * 1.0f;
                        Log.e("TAG ", "2 scale = " + scale);
                    }

                    //核心是先改变父类的位置, 然后再缩小子类, 关键是父类要用约束布局!!
                    if (translationListener != null) {
                        translationListener.scale(scale);
                    }

                    setScaleX(scale);
                    setScaleY(scale);

                    //下面部分是旋转的功能, 暂时不需要了;
//                    rotation = rotation + getDegree(event) - degree;
//                    if (rotation > 360) {
//                        rotation = rotation - 360;
//                    }
//                    if (rotation < -360) {
//                        rotation = rotation + 360;
//                    }
//                    setRotation(rotation);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                moveType = 0;
        }

        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }


    TranslationListener translationListener;

    public void setTranslationListener(TranslationListener translationListener) {
        this.translationListener = translationListener;
    }

    public interface TranslationListener {

        void translation(float actionX, float actionY);

        void scale(float scale);
    }


    // 触碰两点间距离
    private float getSpacing(MotionEvent event) {
        //通过三角函数得到两点间的距离
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    // 取旋转角度
    private float getDegree(MotionEvent event) {
        //得到两个手指间的旋转角度
        double delta_x = event.getX(0) - event.getX(1);
        double delta_y = event.getY(0) - event.getY(1);
        double radians = Math.atan2(delta_y, delta_x);
        return (float) Math.toDegrees(radians);
    }


    public float getMinimumScale() {
        return minimumScale;
    }

    public void setMinimumScale(float minimumScale) {
        if (minimumScale < 0.1f) {
            minimumScale = 0.1f;
        }
        this.minimumScale = minimumScale;
    }

    public int getOriginalWidth() {
        return originalWidth;
    }

    public int getOriginalHeight() {
        return originalHeight;
    }
}

具体思路

下面具体讲思路, 具体代码在文首:

1.我们要解决平移问题。

上文的setTranslationX是相对于父布局来实现的, 但是我们悬浮窗不会充满全屏, 而且后面我们要缩小/放大, 父布局的大小是要变化的, 所以上文的setTranslationX和translationY都要注释掉。

悬浮窗移动是通过WindowManager的updateViewLayout方法来实现的。所以我们加入一个内部接口, 提供给WindowManager进行调用updateViewLayout, 修改后的代码如下:

ZoomView的修改如下:

          case MotionEvent.ACTION_MOVE:

                if (moveType == 1) {

                    translationX = translationX + event.getRawX() - actionX;;
                    translationY = translationY + event.getRawY() - actionY;

                    //父view先变换位置就行了;
                    if (translationListener != null) {
                        translationListener.translation(translationX, translationY);
                    }
                  //setTranslationX(translationX);
                    //setTranslationY(translationY);

                    actionX = event.getRawX();
                    actionY = event.getRawY();

                } 

接口TranslationListener的translation方法的具体实现:

    @Override
    public void translation(float actionX, float actionY) {
        wmParams.x = (int) actionX;
        wmParams.y = (int) actionY;
        wm.updateViewLayout(view, wmParams);
    }
2.解决缩放的问题。

ZoomView的setScaleX(scale); setScaleY(scale); 是需要保留的, 同时, 父view也要进行缩放/放大。

所以我们同样引入一个接口方法提供给Params进行修改 width 和 height ,然后调用WindowManager的updateViewLayout。

ZoomView的修改如下:

  } else if (moveType == 2) {

                    scale = scale * getSpacing(event) / spacing;


                    //这里的意思是, 让放大的最大高度不超过屏幕的宽度, 这样, 放大view后, 就不会超出屏幕, 无论横屏还是竖屏
                    int maxZoomViewHeight = screenHeight > screenWidth ? screenWidth : screenHeight;

                    //限制最小缩放比例
                    if (scale < getMinimumScale()) {
                        scale = getMinimumScale();
                    }


                    Log.e("TAG ", "1 scale = " + scale);
                    if (originalHeight * scale >= maxZoomViewHeight && originalHeight != 0) {
                        //这里需要乘以float, 因为int 除以float得出是int的值, 数据误差太大;
                        scale = maxZoomViewHeight * 1.0f / originalHeight * 1.0f;
                        Log.e("TAG ", "2 scale = " + scale);
                    }

                    //核心是先改变父类的位置, 然后再缩小子类, 关键是父类要用约束布局!!
                    if (translationListener != null) {
                        translationListener.scale(scale);
                    }

                    setScaleX(scale);
                    setScaleY(scale);
                }

接口TranslationListener的scale方法的具体实现:

...
       @Override
            public void scale(float scale) {

                wmParams.width = (int)(zoomView.getOriginalWidth() * scale);
                wmParams.height = (int)(zoomView.getOriginalHeight() * scale);

                wm.updateViewLayout(view, wmParams);

            }
...

到这一步的时候, 能平移也能缩小/放大了, 但是, 会有个问题, 就是ZoomView不会在父view居中了, 经测试, 使用ConstraintLayout包裹ZoomView, 并设置ZoomView居中, 在缩放ZoomView后能居中在父布局中显示。这里可能跟约束布局的实现有关, 有了解的同学可以说一下...

3.视频播放。

下面我们就放入视频播放的view被ZoomView包裹 , 看看视频播放是否卡顿。

源码里面使用的是GSYVideoPlayer的播放控件。 图就不截图了, 具体看源码就OK。

4.视频播放后引入的问题。

视频播放的控件也是会获取触控事件的, 会导致ZoomView的onTouchEvent不生效, 所以这里涉及到事件分发的问题。但是, ZoomView处理View的位置、大小变化, 本身不跟播放的View的触摸事件有冲突的, 所以我们把ZoomView的onTouchEvent的变化位置、大小的代码改到onInterceptTouchEvent里面实现即可。

题外话: demo是基础实现, 具体很多细节, 还得需要进行处理的。

感谢并推荐阅读:

https://blog.csdn.net/zxq614/article/details/88873729

https://github.com/CarGuo/GSYVideoPlayer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值