package com.example.ndh.floatingball.sdk; import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.PixelFormat; import android.graphics.Point; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import com.example.ndh.floatingball.util.Utils; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * Created by ndh on 16/12/16. */ public class FloatingWindowManager { private static WeakReference<Context> mContext; //弱引用 private WindowManager.LayoutParams wmParams; private WindowManager mWindowManager; private int duration = Config.DURATION; private boolean canLayout = true;//避免快速点击圆球,展开和关闭的动画重复执行,这将导致界面布局异常 private Point p = new Point(); private List<View> attachToWindows = new ArrayList<>(); /** * true 表示可以开启 false 表示可以关闭 */ private boolean toggle = true; private int base = Config.BASE; private ObjectAnimator mOpenAnimator; private ObjectAnimator mCloseAnimator; private FloatingWindowManager() { Log.d("ndh--", "createFloatView"); wmParams = new WindowManager.LayoutParams(); //获取的是WindowManagerImpl.CompatModeWrapper mWindowManager = (WindowManager) mContext.get().getSystemService(mContext.get().WINDOW_SERVICE); //设置window type 搞成toast方式 不需要添加权限 wmParams.type = WindowManager.LayoutParams.TYPE_TOAST; //设置图片格式,效果为背景透明 wmParams.format = PixelFormat.RGBA_8888; //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作) wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; //调整悬浮窗显示的停靠位置为左侧置顶 wmParams.gravity = Gravity.START | Gravity.TOP; // 以屏幕左上角为原点,设置x、y初始值,相对于gravity p = Utils.getScreenSize(mContext.get()); base = Utils.dp2pix(mContext.get(), base); FloatingViewManager.create().register(new FloatingViewListener() { @Override public void onFinish(int flag) { switch (flag) { case FloatingViewListener.DOWN: ActionManager.create().doAction(mContext.get(), Config.MenuPosition.DOWN); break; case FloatingViewListener.UP: //回到桌面 ActionManager.create().doAction(mContext.get(), Config.MenuPosition.UP); break; case FloatingViewListener.LEFT: //静音 ActionManager.create().doAction(mContext.get(), Config.MenuPosition.LEFT); break; case FloatingViewListener.RIGHT: // 指定开启系统相机的Action ActionManager.create().doAction(mContext.get(), Config.MenuPosition.RIGHT); break; case FloatingViewListener.CLICK: int[] points = new int[2]; mFloatView.getLocationOnScreen(points); points[0] = points[0] + mFloatView.getMeasuredWidth() / 2; points[1] = points[1]; FloatingWindowManager.create(mContext.get()).toggle(points); } } }); } public static FloatingWindowManager create(Context context) { mContext = new WeakReference<Context>(context); return SingleInstance.INSTANCE; } @NotProguard public void init() { if (isAttachToWindow()) { removeAllView(); } ActionManager.create().initAction(mContext.get()); MenuItemManager.create(mContext.get()).createMenuItem(); createFloatView(); } private static class SingleInstance { public static final FloatingWindowManager INSTANCE = new FloatingWindowManager(); } @NotProguard public void toggle(int[] centerPositions) { if (!canLayout) { return; } List<View> list = MenuItemManager.create(mContext.get()).getListOfViews(); if (toggle) { open(list, centerPositions); } else { close(list); } toggle = !toggle; } private void attach(View v, int x, int y) { if (wmParams == null || mWindowManager == null || v == null) { throw new RuntimeException("windowManager not exists / view is null"); } //说明已经当前view已经添加到window上了 if (attachToWindows.contains(v)) { return; } wmParams.x = x; wmParams.y = y; //设置悬浮窗口长宽数据 wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT; wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; mWindowManager.addView(v, wmParams); attachToWindows.add(v); Log.d("ndh--", "attach--v=" + v + ",x=" + x + ",y=" + y); } private void setPosition(int x, int y) { if (null != wmParams) { wmParams.x = x; wmParams.y = y; } } private void update(View v) { if (!toggle) { return; } if (null != mWindowManager && null != v) mWindowManager.updateViewLayout(v, wmParams); } /** * 所有的view的移除都由该方法提供 * * @param v */ private void detach(View v) { Log.d("ndh--", "detach--v=" + v); if (attachToWindows.contains(v) && null != mWindowManager && null != v) { mWindowManager.removeView(v); attachToWindows.remove(v); } toggle = true; } private boolean isAttachToWindow() { return attachToWindows.size() > 0 ? true : false; } private int getExSize() { return base; } private void close(final List<View> list) { //只有5个menu 没个menu摆放距离相差角度45度 mCloseAnimator = ObjectAnimator.ofFloat(this, "", 0, 45 * 5f) .setDuration(duration); mCloseAnimator.setRepeatCount(0); mCloseAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { tempClose.clear(); canLayout = false; } @Override public void onAnimationEnd(Animator animation) { if (null != mCloseAnimator) { mCloseAnimator = null; } canLayout = true; } @Override public void onAnimationCancel(Animator animation) { if (null != mCloseAnimator) { mCloseAnimator = null; } canLayout = true; } @Override public void onAnimationRepeat(Animator animation) { } }); mCloseAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { removeView(animation, list); } }); mCloseAnimator.start(); } private void removeView(ValueAnimator animation, List<View> list) { float value = (float) animation.getAnimatedValue(); if (list.size() == 0) { return; } int count = (int) (value / (45 * 5 / list.size())); for (int i = 0; i < count; i++) { //避免重复移除view if (tempClose.contains(list.get(list.size() - i - 1))) { continue; } detach(list.get(list.size() - i - 1)); tempClose.add(list.get(list.size() - i - 1)); } } List<View> tempOpen = new ArrayList<View>(); List<View> tempClose = new ArrayList<View>(); private void open(final List<View> list, final int[] centerPositions) { mOpenAnimator = ObjectAnimator.ofFloat(this, "", 0f, 45 * 5) .setDuration(duration); mOpenAnimator.setRepeatCount(0); mOpenAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { // 动画前清空集合,在动画执行过程中会 为集合赋值 见addView方法 tempOpen.clear(); canLayout = false; } @Override public void onAnimationEnd(Animator animation) { //动画 if (null != mOpenAnimator) { mOpenAnimator = null; } canLayout = true; } @Override public void onAnimationCancel(Animator animation) { if (null != mOpenAnimator) { mOpenAnimator = null; } canLayout = true; } @Override public void onAnimationRepeat(Animator animation) { } }); mOpenAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { addView(animation, list, centerPositions); } }); mOpenAnimator.start(); } private void addView(ValueAnimator animation, List<View> list, int[] centerPositions) { float value = (float) animation.getAnimatedValue(); if (list.size() == 0) { return; } // 这里通过改变count实现依次展开效果 int count = (int) (value / (45 * 5 / list.size())); for (int i = 0; i < count; i++) { // 避免重复添加view if (tempOpen.contains(list.get(i))) { continue; } //必须先测量一下 否则拿不到宽/高 if (0 == list.get(i).getMeasuredWidth()) { list.get(i).measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec .makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); } if (p.x / 2 > centerPositions[0]) { //圆球在屏幕右方 则menu 在圆球左方展开 注意45为弧度,需要转换为角度才能使用 wmParams.x = centerPositions[0] - list.get(i).getMeasuredWidth() / 2 - (int) (base * Math.cos(Math.PI * (45 * i + 90) / 180)); } else { //圆球在屏幕左方 则menu 在圆球右方展开 wmParams.x = centerPositions[0] - list.get(i).getMeasuredWidth() / 2 + (int) (base * Math.cos(Math.PI * (45 * i + 90) / 180)); } wmParams.y = centerPositions[1] - list.get(i).getMeasuredHeight() / 2 + (int) (base * Math.sin(Math.PI * (i * 45 + 90) / 180)); attach(list.get(i), wmParams.x, wmParams.y); tempOpen.add(list.get(i)); } } @NotProguard public boolean isOpen() { return !toggle; } FloatingView mFloatView; @NotProguard public void createFloatView() { mFloatView = new FloatingView(mContext.get().getApplicationContext()); FloatingWindowManager.create(mContext.get().getApplicationContext()).attach(mFloatView, Utils.getScreenSize(mContext.get().getApplicationContext()).x, Utils.getScreenSize(mContext.get().getApplicationContext()).y / 2); mFloatView.setTips(ActionManager.create().getAction(Config.MenuPosition.LEFT), ActionManager.create().getAction(Config.MenuPosition.UP), ActionManager.create().getAction(Config.MenuPosition.RIGHT), ActionManager.create().getAction(Config.MenuPosition.DOWN)); final int width = mFloatView.getMeasuredWidth() * 2; final int height = mFloatView.getMeasuredHeight() * 2; //设置监听浮动窗口的触摸移动 mFloatView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (mFloatView.isMoving() || event.getEventTime() - event.getDownTime() < Config.WAITING_TIME) { //在非移动的情况下,将触摸事件给会floatingView return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: int[] posionts = new int[2]; mFloatView.getLocationOnScreen(posionts); //getRawX是触摸位置相对于屏幕的坐标 if (Math.abs(event.getRawX() - posionts[0]) > width || Math.abs(event.getRawY() - posionts[1]) > height) { setPosition((int) event.getRawX() - width, (int) event.getRawY() - height - 25); update(mFloatView); } break; case MotionEvent.ACTION_UP: int[] posionts1 = new int[2]; mFloatView.getLocationOnScreen(posionts1); // 屏幕上方预留 展开的高度 // if (Math.abs(posionts1[0]) < getExSize()) { // setPosition((int) event.getRawX() - width + getExSize(), getExSize()); // } // if (Utils.getScreenSize(mContext.get().getApplicationContext()).x - (Math.abs(posionts1[0])) - width < getExSize()) { // setPosition(Utils.getScreenSize(mContext.get().getApplicationContext()).x - (Math.abs(posionts1[0])) - getExSize(), getExSize()); // } if (Math.abs(posionts1[1]) < getExSize()) { setPosition((int) event.getRawX() - width, getExSize()); // update(mFloatView); } //屏幕下方预留 展开的高度 if (Utils.getScreenSize(mContext.get().getApplicationContext()).y - (Math.abs(posionts1[1]) + mFloatView.getMeasuredHeight()) < getExSize()) { setPosition((int) event.getRawX() - width, Utils.getScreenSize(mContext.get().getApplicationContext()).y - getExSize() - mFloatView.getMeasuredHeight() - MenuItemManager.create(mContext.get().getApplicationContext()).getItemHeight() / 2); // update(mFloatView); } update(mFloatView); } return true; } }); } @NotProguard public void removeAllView() { if (mFloatView != null) { detach(mFloatView); //移除悬浮窗口 mFloatView = null; } List<View> list = MenuItemManager.create(mContext.get()).getListOfViews(); for (int i = 0; i < list.size(); i++) { if (list.get(i) != null) { detach(list.get(i)); } } MenuItemManager.create(mContext.get()).clear(); } }