【app】1.2 悬浮窗_移动

前言

当前实现了直接通过paint绘制一个圆显示在最上层。

尝试新增内容:
1 增加权限申请,因为如果未打开权限,点击绘制圆按钮,app会异常退出。
2 尝试通过layout.xml文件绘制悬浮窗,并设置悬浮球的view的范围为圆,而非长方形
3 新增view的移动和点击双事件区分
4 移动时view重新显示

1 增加权限申请

在activity创建中直接申请

代码来源:Android 可任意位置移动的悬浮窗

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //申请悬浮权限
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(getApplicationContext())) {
                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                startActivityForResult(intent, 0);
            } else {
//                mIntent = new Intent(MainActivity.this/*需要启动service的activity*/, FloatWindowService.class/*需要启动的service*/);
//                bindService(mIntent, serviceConnection, Context.BIND_AUTO_CREATE);//直接启动服务方式启动
                Toast.makeText(getApplicationContext(), "open permission", Toast.LENGTH_SHORT).show();

            }
        }
   	}

在这里插入图片描述
在这里插入图片描述

2 使用layout定义view,代替paint

定义一个layout文件,设置float_window_small.xml
id为small_window_layout

float_window_small.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/small_window_layout"
    >

    <TextView
        android:id="@+id/percent"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center"
        android:text="@string/small_floatBall_text"
        android:textColor="#FF0000"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</LinearLayout>

布局效果
在这里插入图片描述

参考代码:Android 可任意位置移动的悬浮窗
仿照以下代码

LayoutInflater inflater = LayoutInflater.from(getApplication());
mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_window, null, false);
View view = mFloatLayout.findViewById(R.id.playMonitor);//自定义悬浮窗布局
view.setOnTouchListener(this);//设置移动监听,此处view是自定义充满布局的控件
mWindowManager.addView(mFloatLayout, wp);

1 获取应用的布局服务
2 使用inflate()函数初始化定义布局mFloatLayout,注意参数为layout信息
3 查找到对应view的id
4 监听到移动
5 添加view?存疑,在监听中使用到了updateViewLayout,这里又用了addview

2.1 显示大小问题

layout和view关系:我理解的是一个activity对应一个window,window中可以包含多个view,而view的内容就包含布局layout,布局可分为5种,布局中又包含多个控件。
参考:android layout与view机制
activity–window–view–layout–textview/button

需要注意两点。
view/layout的长宽定义在layoutparam中,即代码中看到的viewparams,类型为WindowManager.LayoutParams。
在set_viewparams中有定义,view的长宽为100,view的起始点(左上角)在(200,200)位置。

            //窗口长宽,注意与view一致
            viewparams.width = 100;
            viewparams.height = 100;
            //子控件,居左居上
            viewparams.gravity = Gravity.LEFT | Gravity.TOP;
            //起始位置      // 相对于创建的窗口位置
            viewparams.x = 200;
            viewparams.y = 200;

在strings.xml文件中定义了textview的显示内容

<resources>
    <string name="app_name">demo_v3_addmove</string>
    <string name="small_floatBall_text">11111111</string>
</resources>

所以,呈现的效果是
在这里插入图片描述
我们尝试将显示内容增多

<resources>
    <string name="app_name">demo_v3_addmove</string>
    <string name="small_floatBall_text">111111111111</string>
</resources>

效果一致。

尝试将viewparams参数的长宽变大
viewparams.width=200;
viewparams.height=200;
在这里插入图片描述
这时候发现显示变了,第一行显示9个1,第二行

2.2 更新view函数

updateViewLayout和addview

2.2.1 LayoutInflater布局服务

参考文章:LayoutInflater(布局服务)

获取LayoutInflater:

LayoutInflater inflater1 = LayoutInflater.from(this);  
LayoutInflater inflater2 = getLayoutInflater();  
LayoutInflater inflater3 = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); 

2.3 差异

使用layout创建悬浮球。
执行修改颜色函数时,显示的颜色就改变了,不需要wm移除后添加。
存疑:是否自定义view也可以实现。不适用,再找其他方法
设定颜色时会调用updateTextColors(),更新text颜色。

    public void setTextColor(int color) {
        mTextColor = ColorStateList.valueOf(color);
        updateTextColors();
    }

测试发现,第一次显示layout时,点击layout,颜色无法改变,应该是未识别到点击事件?
原因:我把创建点击layout监听放在了closebutton里了,移动到openbutton就可以。

待办:如何实现在layout上画图,然后填充颜色自定义。

3 尝试使用windowmanager自定义函数更新view

之前已有的一项功能时点击view时,颜色变为绿色,使用的方法时先移除view,再重新添加。
打算使用下WindowManager.updateViewLayout(),看看是否有效。

在click监听中新增


    public class touchfloatballview_youkaiListener implements View.OnClickListener {
        @Override
        public void onClick(View arg0) {
            if (mWindowManager != null) {
                Toast.makeText(getApplicationContext(), "change ball color", Toast.LENGTH_SHORT).show();
//                ball.paint.setColor(Color.GREEN);//设置颜色为红
//                ball.cx = 50;
                ball.cy = 50;
//                ball.();
//                mWindowManager.updateViewLayout(ball, viewparams);

                if (ball_status == false) {
                    record_on();
                } else if (ball_status == true) {
                    record_off();
                } else
                    Log.d("", "error value");

				//方法1.先移除再添加
                remove_view();
                add_view();
                //方法2.直接使用WindowManager.updateViewLayout
                mWindowManager.updateViewLayout(ball,viewparams);

                if (ball.getVisibility() == View.VISIBLE) {
                    Toast.makeText(getApplicationContext(), "view display", Toast.LENGTH_SHORT).show();
                }
            }
            //(ball,viewparams);
        }
    }

测试失败。

3.1 参考文章

view 的绘制和刷新

Android屏幕刷新机制

3.2 更新view成功

之前只调用updateViewLayout(),没有更新ball,因为ball修改了颜色,所以需要调用invalidate()更新ball,然后再用WM更新显示。

    public void updata_ball(){
        // judge if need redraw floatingball due to color maybe change
        ball.invalidate();
        // update display ball
        mWindowManager.updateViewLayout(ball,viewparams_parent);
    }

4 拖动悬浮球

4.1 参考文章

Android 可任意位置移动的悬浮窗

关于View的setOnTouchListener和setOnClickListener冲突

用户手势检测-GestureDetector使用详解

Android MotionEvent详解

4.2 分析功能

view监听触摸事件,其中主要是三种:按下ACTION_DOWN,移动ACTION_MOVE,弹起ACTION_UP。

与此同时还有一点是view的坐标,若改变则需要重新绘制view,进行刷新。

刷新条件:
1 监听到up事件
2 监听到移动,同步刷新

参考文章:可拖拽悬浮窗、对话框悬浮窗的简单实现

这里需要注意的一点,参考文章使用的是touch监听,而我之前写过一个click监听,主要是监听悬浮框的点击,来实现录制和停止。

4.3 实现


    public class touchfloatballview_youkaiListener implements View.OnClickListener {
        @Override
        public void onClick(View arg0) {
            if (mWindowManager != null) {
                if (ball_status == false) {
                    record_on();
                } else if (ball_status == true) {
                    record_off();
                } else
                    Log.d("", "error value");

                remove_view();
                add_view();
            }
        }
    }

// 代码来源:https://www.cnblogs.com/tianzhijiexian/p/3994546.html
    private class FloatingListener implements View.OnTouchListener {
        @Override
        public boolean onTouch(View arg0, MotionEvent event) {

            int action = event.getAction();
            switch(action){
                case MotionEvent.ACTION_DOWN:
                    isMove = false;
                    mTouchStartX = (int)event.getRawX();
                    mTouchStartY = (int)event.getRawY();
                    mStartX = (int)event.getX();
                    mStartY = (int)event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    mTouchCurrentX = (int) event.getRawX();
                    mTouchCurrentY = (int) event.getRawY();
                    viewparams_layout.x += mTouchCurrentX - mTouchStartX;
                    viewparams_layout.y += mTouchCurrentY - mTouchStartY;
                    mWindowManager.updateViewLayout(mlayout, viewparams_layout);

                    mTouchStartX = mTouchCurrentX;
                    mTouchStartY = mTouchCurrentY;
                    break;
                case MotionEvent.ACTION_UP:
                    mStopX = (int)event.getX();
                    mStopY = (int)event.getY();
                    //System.out.println("|X| = "+ Math.abs(mStartX - mStopX));
                    //System.out.println("|Y| = "+ Math.abs(mStartY - mStopY));
                    if(Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1){
                        isMove = true;
                    }
                    break;
            }
            return mGestureDetector.onTouchEvent(event);  //此处必须返回false,否则OnClickListener获取不到监听
        }
    }

在打开layout view的button监听中创建上述两个监听,分别是click监听录制和播放,touch监听移动


    public class openlayoutviewListener implements OnClickListener {
        @Override
        public void onClick(View arg0) {
            // TODO Auto-generated method stub
            // display status in textview "button on"
            text = (TextView) findViewById(R.id.textView);
            text.setText("layoutview on");

            Toast.makeText(getApplicationContext(), "layoutview on", Toast.LENGTH_SHORT).show();

            if(status_layoutview == false){
                mWindowManager.addView(mlayout, viewparams_layout);
                status_layoutview = true;
            }

            // listen for floatingtextview touch
            mlayout.setOnClickListener(new touchlayoutview_youkaiListener());
            mlayout.setOnTouchListener(new FloatingListener());
        }
    }

5 增加长按功能

5.1 监听长按事件

设计功能是当弹出悬浮框时
1 点击,开始录制/停止录制
2 拖动则更新位置
3 长按则弹出一个按钮,该按钮监听到点击事件则关闭悬浮球应用

监听长按短按可以通过获取up和down的时间来计算。

    private class FloatingListener implements View.OnTouchListener {
        @Override
        public boolean onTouch(View arg0, MotionEvent event) {
            int action = event.getAction();
            switch(action){
            case MotionEvent.ACTION_DOWN:
            	time1 = System.currentTimeMillis();
            	break;
            case MotionEvent.ACTION_UP:
            	time2 = System.currentTimeMillis();
            	long temp_time = time2 - time1;
            	// 判定temp_time若在0.5s内
            	if(temp_time > 500){
            	
            	}
			}
		}
	}

注意点:
当拖动悬浮框mlayout时,如果只是监听up和down事件时间差,容易发生误触。
所以需要区分开move还是单纯长按。

完整的悬浮框layouttext监听代码如下。

    private class FloatingListener implements View.OnTouchListener {
        @Override
        public boolean onTouch(View arg0, MotionEvent event) {
            int action = event.getAction();
            switch(action){
                case MotionEvent.ACTION_DOWN:
                    isMove = false;
                    mTouchStartX = (int)event.getRawX();
                    mTouchStartY = (int)event.getRawY();
                    mStartX = (int)event.getX();
                    mStartY = (int)event.getY();
                    time1 = System.currentTimeMillis();
                    break;

                case MotionEvent.ACTION_MOVE:
                    mTouchCurrentX = (int) event.getRawX();
                    mTouchCurrentY = (int) event.getRawY();
                    mTouchStartX = mTouchCurrentX;
                    mTouchStartY = mTouchCurrentY;
                    break;

                case MotionEvent.ACTION_UP:
                    // judge move
                    mStopX = (int)event.getX();
                    mStopY = (int)event.getY();
                    //System.out.println("|X| = "+ Math.abs(mStartX - mStopX));
                    //System.out.println("|Y| = "+ Math.abs(mStartY - mStopY));
                    if(Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1){
                        isMove = true;
                    }

                    // no move:
                    //          case1: long touch
                    //          case2: start/stop record
                    // move:  change movestatus isMove
                    if (isMove == false){
                        //judge long time
                        time2 = System.currentTimeMillis();
                        long temp_time = time2 - time1;
                        if(temp_time > 500){
                            // long touch event
                            Toast.makeText(getApplicationContext(), "long time touch", Toast.LENGTH_SHORT).show();
                        }
                        else
                        {
                            //judge layout view change status -- start/stop recording
                            if (mWindowManager == null) {
                                Toast.makeText(getApplicationContext(), "no wm", Toast.LENGTH_SHORT).show();
                            }

                            if(status_layoutview == true){
//                              Toast.makeText(getApplicationContext(), "change layoutview color", Toast.LENGTH_SHORT).show();

                                if(status_layoutview_text == false) {
                                    layoutview_on();
                                }
                                else if (status_layoutview_text == true){
                                    layoutview_off();
                                }
//                                  mlayout.updateViewLayout(mlayout,viewparams_layout);
                            }

                        }
                    }

                    break;
            }
            return mGestureDetector.onTouchEvent(event);  //此处必须返回false,否则OnClickListener获取不到监听
        }
    }

5.2 长按显示按钮

上述代码中temp_time超过500未实现内容,只是弹出一个提示。

需要实现的功能是,再显示一个悬浮按钮,并为这个按钮添加监听,监听实现关闭应用。

// long touch event
Toast.makeText(getApplicationContext(), "long time touch", Toast.LENGTH_SHORT).show();

// add mlayout_exitbt
if(mlayout_exitbt.isAttachedToWindow() == false) {
    mWindowManager.addView(mlayout_exitbt, viewparams);
    Toast.makeText(getApplicationContext(), "display exit button", Toast.LENGTH_SHORT).show();

    //func1: add layoutexit btn lister
    btn_ballexit= mlayout_exitbt.findViewById(R.id.btn_exit);
    if (btn_ballexit != null){
        Toast.makeText(getApplicationContext(), "find exit button", Toast.LENGTH_SHORT).show();
        btn_ballexit.setOnClickListener(new closeappListener());
    }
    else {
        Toast.makeText(getApplicationContext(), "don't find exit button", Toast.LENGTH_SHORT).show();

    }

    //add layoutexit lister
// 	mlayout_exitbt.setOnTouchListener(new closeappListener_layoutexitbtn());

// 	mlayout_exitbt.callOnClick();
}
else
    mWindowManager.removeView(mlayout_exitbt);

PS:需要注意的是,我是重新定义了一个linerlayout,并放置了一个按键。
所以在代码中需要先获取到这个linerlayout,然后再执行findViewById,否则会出现闪退现象。

1 先获取到layout

	//定义
    // add long time display exit button
    LayoutInflater inflater_exitbt;
    LinearLayout mlayout_exitbt;      // floating text (layout to replace ball)
    Button btn_ballexit;

	//使用1 获取layout
    inflater_exitbt = LayoutInflater.from(getApplication());
    mlayout_exitbt = (LinearLayout) inflater_exitbt.inflate(R.layout.longtouch_exit, null);

2 查找到该按键

	btn_ballexit= mlayout_exitbt.findViewById(R.id.btn_exit);
	if (btn_ballexit != null){
	    Toast.makeText(getApplicationContext(), "find exit button", Toast.LENGTH_SHORT).show();
	    //创建监听关闭应用
	    btn_ballexit.setOnClickListener(new closeappListener());
	}

到这一步的代码参见:
https://github.com/monsterLang/floatingball/commit/2eab5ecdc32b48c20174eacebca4973d6152c57f#

6 添加按键自动创建悬浮球

预设功能:长按悬浮框后,弹出一个按钮退出,一个按钮添加,添加按钮监听到点击事件,则自动创建悬浮框并弹出输入框,允许输入命令以及显示文字。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值