多加了一点功能的可拖动的浮动按钮,点击可弹出包含按钮的控制框
很小的一个Demo,希望能有点用处。Demo代码在文末。
已经有不少关于浮动按钮的例子,在本例中加入了一点功能,并对一些小问题进行了修正。
Demo中共有三个类文件:FloatingView、FloatingService和MainActivity,其中FloatingView是浮动控制按钮的实现,其他两个类主要为能够长时间显示浮动按钮所做。
MainActivity是启动界面,点击该Activity中间的按钮就会启动FloatingService。
FloatingService的onStartCommand(Intent, int, int)方法中会调用new FloatingView(this).showFloatingBtn() 用于显示浮动控制按钮。
FloatingView继承自View,它的内部类OnCtrlViewTouchListener继承自OnTouchListener,用于处理对控制按钮的触控事件。
之前所看的例子中有一个问题是:根据浮动按钮的位置来判断用户的操作是否是点击事件,这会造成即使是浮动按钮发生了微小的移动,也不会触发点击事件,所以需要点击很多次,才会触发一次点击。所以本例中设定了一个用于计时的变量(OnCtrlViewTouchListener类中的mTouchDur),在用户点击按钮时开始计时,抬起时停止,如果小于100毫秒(例子中定义为MAX_MILLI_TREAT_AS_CLICK常量),就认为这次操作用户是想触发点击事件,而非移动浮动按钮。
另外,FloatingView类的removeCtrlViewByTopActivityChag会通过ActivityManager定时检测当前正运行的应用程序是否发生了变化,如果发生了变化就会移除浮动控制按钮,这主要用于放置在实际项目中使用时,浮动按钮不会自我移除的问题。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
private class OnCtrlViewTouchListener implements OnTouchListener { private static final long MAX_MILLI_TREAT_AS_CLICK = 100; //当用户触控控制按钮的时间小于该常量毫秒时,就算控制按钮的位置发生了变化,也认为这是一次点击事件 private WindowManager mWindowManager; private WindowManager.LayoutParams mLayoutParams; // 触屏监听 float mLastX, mLastY; int mOldOffsetX, mOldOffsetY; int mRecordFlag = 0; // 用于重新记录CtrlView位置的标志 long mTouchDur; //记录用户触控控制按钮的时间 boolean hasShowedDetail = false; public OnCtrlViewTouchListener(WindowManager windowManager, WindowManager.LayoutParams layoutParams) { mWindowManager = windowManager; mLayoutParams = layoutParams; } @Override public boolean onTouch(View v, MotionEvent event) { final int action = event.getAction(); float x = event.getX(); float y = event.getY(); if (mRecordFlag == 0) { mOldOffsetX = mLayoutParams.x; // 偏移量 mOldOffsetY = mLayoutParams.y; // 偏移量 } if (action == MotionEvent.ACTION_DOWN) { mLastX = x; mLastY = y; mTouchDur = System.currentTimeMillis(); } else if (action == MotionEvent.ACTION_MOVE) { mLayoutParams.x += (int) (x - mLastX); // 偏移量 mLayoutParams.y += (int) (y - mLastY); // 偏移量 mRecordFlag = 1; mWindowManager.updateViewLayout(mCtrlView, mLayoutParams); } else if (action == MotionEvent.ACTION_UP) { mTouchDur = System.currentTimeMillis() - mTouchDur; int newOffsetX = mLayoutParams.x; int newOffsetY = mLayoutParams.y; if (mTouchDur < MAX_MILLI_TREAT_AS_CLICK || (mOldOffsetX == newOffsetX && mOldOffsetY == newOffsetY)) { if (hasShowedDetail == false) { if (mDetailView == null) { showDetailView(mWindowManager); } else { mDetailView.setVisibility(VISIBLE); } hasShowedDetail = true; } else { mDetailView.setVisibility(INVISIBLE); hasShowedDetail = false; } } else { mRecordFlag = 0; } } return true; } /** * 该方法会显示详情视图 * * @param windowManager * 用于控制通话状态标示出现的初始位置(默认居中)、大小以及属性 * @return 会返回所创建的控制按钮的WindowManager.LayoutParams型对象。 */ private WindowManager.LayoutParams showDetailView( WindowManager windowManager) { mDetailView = LayoutInflater.from(mContext).inflate( R.layout.detail_window, null); mDetailView.setBackgroundColor(Color.TRANSPARENT); setDetailBtnsListener(); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.type = 2002; // type是关键,这里的2002表示系统级窗口,你也可以试试2003。 layoutParams.flags = 40;// 这句设置桌面可控 layoutParams.format = -3; // 透明 layoutParams.width = 400; layoutParams.height = 230; windowManager.addView(mDetailView, layoutParams); return layoutParams; } private void setDetailBtnsListener() { Button chgBtn = (Button) mDetailView .findViewById(R.id.btn_chg_stat); // 设置改变通话状态按钮的监听器 chgBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { changCallingStat((Button) v); } }); Button hideBtn = (Button) mDetailView.findViewById(R.id.btn_hide); // 设置隐藏按钮的监听器 hideBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mDetailView.setVisibility(INVISIBLE); hasShowedDetail = false; } }); Button removeBtn = (Button) mDetailView.findViewById(R.id.btn_remove); removeBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mWindowManager.removeView(mDetailView); mWindowManager.removeView(mCtrlView); } }); } private void changCallingStat(Button button) { TextView currStat = (TextView) mDetailView .findViewById(R.id.tv_curr_stat); if (mContext.getString(R.string.stat_gen).equals( currStat.getText().toString())) { button.setEnabled(false); sendUpdateMsg(mUpdateStatusHandler, STAT_OPER_BUILD, 0); sendUpdateMsg(mUpdateStatusHandler, STAT_OPER_CHECK, 2); sendUpdateMsg(mUpdateStatusHandler, STAT_READY_SECURE, 3); } else { button.setEnabled(false); sendUpdateMsg(mUpdateStatusHandler, STAT_OPER_DES, 0); sendUpdateMsg(mUpdateStatusHandler, STAT_READY_GENERAL, 4); } } /** * 该方法用于向更新通话状态的Handler发送消息 * * @param handler * @param status 所发送的状态信息 * @param seconds * 几秒后向handler发送消息 */ private void sendUpdateMsg(Handler handler, int status, int seconds) { final Message msg = Message.obtain(handler); msg.what = status; handler.postDelayed(new Runnable() { @Override public void run() { msg.sendToTarget(); } }, seconds * 1000); } }