前言:自定义FlaotView不需要任何权限,继承FrameLayout,通过WindowManager实现悬浮。
资源文件:drawable、drawable-hdpi、layout
菜单背景(menu_bg.xml):
-
悬浮视图相关布局(float_view.xml、float_left_menu_view.xml、float_right_menu_view.xml、float_menu_view_list_item.xml):
float_view.xml:
float_left_menu_view.xml:
float_right_menu_view.xml:
float_menu_view_list_item.xml:
图片资源:
工具类:
ScreenUtils.java
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.ViewConfiguration;
import java.lang.reflect.Method;
/**
* 屏幕管理工具类
*/
public class ScreenUtils {
/**
* 获取虚拟按键的高度
* @param context
* @return
*/
public static int getNavigationBarHeight(Context context) {
int result = 0;
if (hasNavBar(context)) {
Resources res = context.getResources();
int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
result = res.getDimensionPixelSize(resourceId);
}
}
return result;
}
/**
* 检查是否存在虚拟按键栏
* @param context
* @return
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static boolean hasNavBar(Context context) {
Resources res = context.getResources();
int resourceId = res.getIdentifier("config_showNavigationBar", "bool", "android");
if (resourceId != 0) {
boolean hasNav = res.getBoolean(resourceId);
// check override flag
String sNavBarOverride = getNavBarOverride();
if ("1".equals(sNavBarOverride)) {
hasNav = false;
} else if ("0".equals(sNavBarOverride)) {
hasNav = true;
}
return hasNav;
} else { // fallback
return !ViewConfiguration.get(context).hasPermanentMenuKey();
}
}
/**
* 判断虚拟按键栏是否重写
* @return
*/
private static String getNavBarOverride() {
String sNavBarOverride = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
Class c = Class.forName("android.os.SystemProperties");
Method m = c.getDeclaredMethod("get", String.class);
m.setAccessible(true);
sNavBarOverride = (String) m.invoke(null, "qemu.hw.mainkeys");
} catch (Throwable e) {
}
}
return sNavBarOverride;
}
/**
* 通过反射,获取包含虚拟键的整体屏幕高度
* @return
*/
public static int getHasVirtualHight(Context context) {
int dpi = 0;
Display display = ((Activity)context).getWindowManager().getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
@SuppressWarnings("rawtypes")
Class c;
try {
c = Class.forName("android.view.Display");
@SuppressWarnings("unchecked")
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, dm);
dpi = dm.heightPixels;
} catch (Exception e) {
e.printStackTrace();
}
return dpi;
}
/**
* 通过反射,获取包含虚拟键的整体屏幕宽度
* @return
*/
public static int getHasVirtualWidth(Context context) {
int dpi = 0;
Display display = ((Activity)context).getWindowManager().getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
@SuppressWarnings("rawtypes")
Class c;
try {
c = Class.forName("android.view.Display");
@SuppressWarnings("unchecked")
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, dm);
dpi = dm.widthPixels;
} catch (Exception e) {
e.printStackTrace();
}
return dpi;
}
/**
* 隐藏虚拟按键,并且全屏
*/
public static void hideBottomUIMenu(Context context) {
//隐藏虚拟按键,并且全屏
if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api
View v = ((Activity)context).getWindow().getDecorView();
v.setSystemUiVisibility(View.GONE);
} else if (Build.VERSION.SDK_INT >= 19) {
//for new api versions.
View decorView = ((Activity)context).getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
}
}
CommonUtils.java
import android.content.Context;
/**
* 公共工具类
*/
public class CommonUtils {
public static int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
}
水平ListView:
HorizontalListView.java
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.Scroller;
import java.util.LinkedList;
import java.util.Queue;
/**
* 水平ListView
*/
public class HorizontalListView extends AdapterView {
private Context mContext = null;
public boolean mAlwaysOverrideTouch = true;
protected ListAdapter mAdapter;
private int mLeftViewIndex = -1;
private int mRightViewIndex = 0;
protected int mCurrentX;
protected int mNextX;
private int mMaxX = Integer.MAX_VALUE;
private int mDisplayOffset = 0;
protected Scroller mScroller;
private GestureDetector mGesture;
private Queue mRemovedViewQueue = new LinkedList();
private OnItemSelectedListener mOnItemSelected;
private OnItemClickListener mOnItemClicked;
private OnItemLongClickListener mOnItemLongClicked;
private boolean mDataChanged = false;
public HorizontalListView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
initView();
}
private synchronized void initView() {
mLeftViewIndex = -1;
mRightViewIndex = 0;
mDisplayOffset = 0;
mCurrentX = 0;
mNextX = 0;
mMaxX = Integer.MAX_VALUE;
mScroller = new Scroller(mContext);
mGesture = new GestureDetector(mContext, mOnGesture);
}
@Override
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
mOnItemSelected = listener;
}
@Override
public void setOnItemClickListener(OnItemClickListener listener){
mOnItemClicked = listener;
}
@Override
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClicked = listener;
}
private DataSetObserver mDataObserver = new DataSetObserver() {
@Override
public void onChanged() {
synchronized(HorizontalListView.this){
mDataChanged = true;
}
invalidate();
requestLayout();
}
@Override
public void onInvalidated() {
reset();
invalidate();
requestLayout();
}
};
@Override
public ListAdapter getAdapter() {
return mAdapter;
}
@Override
public View getSelectedView() {
return null;
}
@Override
public void setAdapter(ListAdapter adapter) {
if(mAdapter != null) {
mAdapter.unregisterDataSetObserver(mDataObserver);
}
mAdapter = adapter;
mAdapter.registerDataSetObserver(mDataObserver);
reset();
}
private synchronized void reset(){
initView();
removeAllViewsInLayout();
requestLayout();
}
@Override
public void setSelection(int position) {
}
private void addAndMeasureChild(final View child, int viewPos) {
LayoutParams params = child.getLayoutParams();
if(params == null) {
params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
}
addViewInLayout(child, viewPos, params, true);
child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
}
@Override
protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if(mAdapter == null){
return;
}
if(mDataChanged){
int oldCurrentX = mCurrentX;
initView();
removeAllViewsInLayout();
mNextX = oldCurrentX;
mDataChanged = false;
}
if(mScroller.computeScrollOffset()){
int scrollx = mScroller.getCurrX();
mNextX = scrollx;
}
if(mNextX <= 0){
mNextX = 0;
mScroller.forceFinished(true);
}
if(mNextX >= mMaxX) {
mNextX = mMaxX;
mScroller.forceFinished(true);
}
int dx = mCurrentX-mNextX;
removeNonVisibleItems(dx);
fillList(dx);
positionItems(dx);
mCurrentX = mNextX;
if(!mScroller.isFinished()){
post(new Runnable(){
@Override
public void run() {
requestLayout();
}
});
}
}
private void fillList(final int dx) {
int edge = 0;
View child = getChildAt(getChildCount()-1);
if(child != null) {
edge = child.getRight();
}
fillListRight(edge, dx);
edge = 0;
child = getChildAt(0);
if(child != null) {
edge = child.getLeft();
}
fillListLeft(edge, dx);
}
private void fillListRight(int rightEdge, final int dx) {
while(rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) {
View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this);
addAndMeasureChild(child, -1);
rightEdge += child.getMeasuredWidth();
if(mRightViewIndex == mAdapter.getCount()-1) {
mMaxX = mCurrentX + rightEdge-getWidth();
}
if (mMaxX < 0) {
mMaxX = 0;
}
mRightViewIndex++;
}
}
private void fillListLeft(int leftEdge, final int dx) {
while(leftEdge + dx > 0 && mLeftViewIndex >= 0) {
View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this);
addAndMeasureChild(child, 0);
leftEdge -= child.getMeasuredWidth();
mLeftViewIndex--;
mDisplayOffset -= child.getMeasuredWidth();
}
}
private void removeNonVisibleItems(final int dx) {
View child = getChildAt(0);
while(child != null && child.getRight() + dx <= 0) {
mDisplayOffset += child.getMeasuredWidth();
mRemovedViewQueue.offer(child);
removeViewInLayout(child);
mLeftViewIndex++;
child = getChildAt(0);
}
child = getChildAt(getChildCount()-1);
while(child != null && child.getLeft() + dx >= getWidth()) {
mRemovedViewQueue.offer(child);
removeViewInLayout(child);
mRightViewIndex--;
child = getChildAt(getChildCount()-1);
}
}
private void positionItems(final int dx) {
if(getChildCount() > 0){
mDisplayOffset += dx;
int left = mDisplayOffset;
for(int i=0;i
弹出菜单:
MenuViewPopupWindow.java
import android.content.Context;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
/**
* 菜单弹出窗
*/
public class MenuViewPopupWindow extends PopupWindow implements View.OnClickListener{
private Context context;
private View view;
private ImageView iv_sdk_icon;
private HorizontalListView listView;
private int[] ImageIconList = new int[]{R.drawable.person, R.drawable.warn};
private MenuItemClick menuItemClick = null;
private IconOnClick iconOnClick = null;
private static MenuViewPopupWindow instance = null;
public static MenuViewPopupWindow getInstance(final Context context){
if(instance == null){
synchronized (MenuViewPopupWindow.class) {
if(instance == null) {
instance = new MenuViewPopupWindow(context);
}
}
}
return instance;
}
public MenuViewPopupWindow addMenuItemClick(MenuItemClick itemClick){
menuItemClick = itemClick;
return this;
}
public MenuViewPopupWindow addIconOnClick(IconOnClick click){
iconOnClick = click;
return this;
}
public MenuViewPopupWindow removeMenuItemClick(){
menuItemClick = null;
return this;
}
public MenuViewPopupWindow removeIconOnClick(){
iconOnClick = null;
return this;
}
public MenuViewPopupWindow(Context context) {
this(context, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
public MenuViewPopupWindow(Context context, int with, int height) {
this.context = context;
setWidth(CommonUtils.dip2px(context, (ImageIconList.length + 1) * 58));
setHeight(CommonUtils.dip2px(context, 58));
//如果设置可以获得焦点,按返回键可以令菜单消失
//setFocusable(true);
//设置弹窗内可点击
setTouchable(true);
//设置弹窗外可点击
setOutsideTouchable(true);
//设置dismiss监听事件
setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss() {
instance = null;
}
});
//允许弹出窗口超出屏幕范围。默认情况下,窗口被夹到屏幕边界。设置为false将允许Windows精确定位。
if(Build.VERSION.SDK_INT >= 16) {
setClippingEnabled(false);
}
}
public MenuViewPopupWindow setMenuView(int viewLayout){
view = LayoutInflater.from(context).inflate(viewLayout,null);
setContentView(view);
initData();
return this;
};
private void initData() {
iv_sdk_icon = (ImageView) view.findViewById(R.id.iv_sdk_icon);
listView = (HorizontalListView) view.findViewById(R.id.lv_menu);
ViewGroup.LayoutParams params = iv_sdk_icon.getLayoutParams();
params.width = CommonUtils.dip2px(context,58);
params.height = CommonUtils.dip2px(context,58);
iv_sdk_icon.setLayoutParams(params);
iv_sdk_icon.setImageResource(R.drawable.float_logo);
//设置列表的适配器
listView.setAdapter(listAdapter);
iv_sdk_icon.setOnClickListener(this);
}
private BaseAdapter listAdapter = new BaseAdapter() {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View rootView = null;
if (convertView == null) {
rootView = View.inflate(context,R.layout.float_menu_view_list_item,null);
} else {
rootView = convertView;
}
RelativeLayout ll_item = (RelativeLayout) rootView.findViewById(R.id.ll_item);
ImageView iv_item = (ImageView) rootView.findViewById(R.id.iv_item);
ViewGroup.LayoutParams llParams = ll_item.getLayoutParams();
llParams.width = CommonUtils.dip2px(context,58);
llParams.height = CommonUtils.dip2px(context,58);
ll_item.setLayoutParams(llParams);
ViewGroup.LayoutParams ivParams = iv_item.getLayoutParams();
ivParams.width = CommonUtils.dip2px(context,50);
ivParams.height = CommonUtils.dip2px(context,50);
iv_item.setLayoutParams(ivParams);
iv_item.setImageResource(ImageIconList[position]);
ll_item.setTag(position);
//解决三星手机A8等部分手机在popupWindow里ListView不兼容点击事件,因此在ListView的item设置点击事件,而不是监听OnItemClickListener()
ll_item.setOnClickListener(clickListener);
return rootView;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return ImageIconList[position];
}
@Override
public int getCount() {
return ImageIconList.length;
}
};
public MenuViewPopupWindow setViewAnimationStyle(String stype){
//setAnimationStyle(ResourcesUtil.getStyleId(context,stype));
return this;
}
public void Dismiss(){
if(instance != null && isShowing())
dismiss();
}
private View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = (int) v.getTag();
if(menuItemClick != null)
menuItemClick.OnItemClick(position);
}
};
@Override
public void onClick(View v) {
if(iconOnClick != null)
iconOnClick.OnClick();
}
public interface MenuItemClick{
void OnItemClick(int position);
}
public interface IconOnClick{
void OnClick();
}
}
核心类(FloatView):
FloatView.java
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.Build;
import android.support.annotation.Dimension;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.util.Timer;
import java.util.TimerTask;
/**
* 游戏悬浮窗视图
*/
public class FloatView extends FrameLayout implements MenuViewPopupWindow.MenuItemClick,MenuViewPopupWindow.IconOnClick,View.OnTouchListener{
// 悬浮栏位置
private final static int LEFT = 0;
private final static int RIGHT = 1;
private final static int TOP = 3;
private final static int BUTTOM = 4;
private int dpi;
private int screenHeight;
private int screenWidth;
private WindowManager.LayoutParams wmParams;
private WindowManager wm;
private float eventDownX, eventDownY;
private float eventMoveX, eventMoveY;
private float eventDownStartX;
private float eventDownStartY;
private float eventUpEndX;
private float eventUpEndY;
private volatile boolean hasEventDown;//判断是否有ACTION_DOWN事件触发的flag
private volatile boolean isScroll;//判断悬浮窗口是否移动的flag
private volatile boolean isLongClick;//判断触摸事件是否长按的flag
private Context mContext = null;
private int[] location = new int[2];//悬浮窗口在屏幕的x、y位置
private ImageView mFloatIcon;//悬浮窗图片icon
private int mFloatX,mFloatY;
private volatile AnimatorSet animatorSet = null;//组合动画
private int speed = 10;//移动速度
private int timeValue = 0; //移动计步
private volatile boolean isRunSide = false;//(开始/停止)平移悬浮窗flag
private volatile boolean isAutoHideRun = true;//(开始/停止)隐藏悬浮窗flag
private Timer mTimer;//平移悬浮窗定时器
private Timer autohideTimer = null;//隐藏悬浮窗定时器
private Toast mToast = null;
private static FloatView instance = null;//单例模式
public static FloatView getInstance(final Context context){
if(instance == null) {
synchronized (FloatView.class) {
if(instance == null) {
instance = new FloatView(context);
}
}
}
return instance;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public FloatView(Context context) {
super(context);
mContext = context;
wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
//通过像素密度来设置按钮的大小
dpi = dpi(dm.densityDpi);
//屏幕宽度(包含虚拟键的整体屏幕宽度)
screenWidth = ScreenUtils.getHasVirtualWidth(context);
//屏幕高度(包含虚拟键的整体屏幕高度)
screenHeight = ScreenUtils.getHasVirtualHight(context);
//布局设置
wmParams = new WindowManager.LayoutParams();
// 设置window type
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
wmParams.format = PixelFormat.RGBA_8888; // 设置图片格式,效果为背景透明
wmParams.gravity = 51;
// 设置Window flag WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION:虚拟按键透明 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS:标题栏透明
wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
wmParams.width = LayoutParams.WRAP_CONTENT;
wmParams.height = LayoutParams.WRAP_CONTENT;
getStartPosition();
wmParams.x = mFloatX;
wmParams.y = mFloatY;
addView(createView(context));
setOnTouchListener(this);
}
/**
* 获取上次保存的最后位置作为开始位置显示
*/
private void getStartPosition(){
try{
SharedPreferences sharedPreferences = mContext.getSharedPreferences("location", Context.MODE_PRIVATE);
String strPoint = sharedPreferences.getString("float_location", "");
mFloatX = 0;
mFloatY = screenHeight / 2;
if(!strPoint.equals("")) {
String[] po = strPoint.split("\\|");
try {
mFloatX = Integer.parseInt(po[0]);
mFloatY = Integer.parseInt(po[1]);
}catch (NumberFormatException e){
mFloatX = 0;
mFloatY = screenHeight / 2;
}
}
}catch (Exception ex){
ex.printStackTrace();
}
}
/**
* 创建Float view
* @param context
* @return
*/
private View createView(final Context context) {
LayoutInflater inflater = LayoutInflater.from(context);
// 从布局文件获取浮动窗口视图
View rootFloatView = inflater.inflate(R.layout.float_view, null);
mFloatIcon = (ImageView) rootFloatView.findViewById(R.id.iv_floatview);
//设置icon
mFloatIcon.setImageResource(R.drawable.float_logo);
ViewGroup.LayoutParams params = mFloatIcon.getLayoutParams();
params.width = CommonUtils.dip2px(context, 58);
params.height = CommonUtils.dip2px(context, 58);
mFloatIcon.setLayoutParams(params);
rootFloatView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
return rootFloatView;
}
/**
* 根据密度选择控件大小
*/
private int dpi(int densityDpi) {
if (densityDpi <= 120) {
return 36;
} else if (densityDpi <= 160) {
return 48;
} else if (densityDpi <= 240) {
return 72;
} else if (densityDpi <= 320) {
return 96;
}
return 108;
}
/**
* 显示悬浮窗
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void show() {
if(SupportAndroidVersion()) {
if (isShown()) {
return;
}
if(wm != null)
wm.addView(this, wmParams);
//通过屏幕的宽度和高度的大小比较判别
if(screenWidth < screenHeight) {//竖屏
if (wmParams.x < screenWidth / 2 - getMeasuredWidth() / 2) {
autoHideViewToSide(LEFT, 3000);
} else {
autoHideViewToSide(RIGHT, 3000);
}
}else {//横屏
if (wmParams.x <= screenWidth / 4 || (wmParams.y >= screenHeight / 2 && wmParams.x <= screenWidth / 2)) {
autoHideViewToSide(LEFT, 3000);
} else if (wmParams.x >= screenWidth * 3 / 4 || (wmParams.y >= screenHeight / 2 && wmParams.x > screenWidth / 2)) {
autoHideViewToSide(RIGHT, 3000);
} else {
autoHideViewToSide(TOP, 3000);
}
}
}
}
/**
* 销毁悬浮窗
*/
public void destory() {
if(isShown()) {
if (wm != null)
wm.removeViewImmediate(this);
}
instance = null;
}
/**
* 判断当前设备android版本是否符合要求
* @return boolean
*/
public boolean SupportAndroidVersion() {
int curApiVersion = Build.VERSION.SDK_INT;
// This work only for android 4.0+
if (curApiVersion >= 14) {
return true;
}
return false;
}
/**
* 悬浮窗Touch事件
* @param v
* @param event
* @return
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public boolean onTouch(View v, MotionEvent event) {
// 获取相对屏幕的坐标, 以屏幕左上角为原点
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
hasEventDown = true;
/*
* 触发ACTION_DOWN把所有的作用及效果全部取消并复原
*/
if(mTimer != null){
isRunSide = false;
mTimer.cancel();
mTimer = null;
}
if(autohideTimer != null){
isAutoHideRun = false;
autohideTimer.cancel();
autohideTimer = null;
}
eventDownX = event.getX();
eventDownY = event.getY();
eventDownStartX = event.getRawX();
eventDownStartY = event.getRawY();
if(animatorSet != null && (animatorSet.isStarted() || animatorSet.isRunning())) {
animatorSet.end();
animatorSet.cancel();
animatorSet = null;
}
mFloatIcon.setAlpha(1.0f);
ObjectAnimator translationAnimator = null;
//得到view在屏幕中的位置
v.getLocationOnScreen(location);
//判断位于屏幕的位置
if(screenWidth < screenHeight) {//竖屏
if (location[0] < screenWidth / 2 - getMeasuredWidth() / 2) {
translationAnimator = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",-v.getMeasuredWidth()*2/3,0);//左侧
} else {
translationAnimator = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",v.getMeasuredWidth()*2/3,0);//右侧
}
}else {//横屏
if (location[0] <= screenWidth / 4 || (location[1] >= screenHeight / 2 && location[0] <= screenWidth / 2)) {
translationAnimator = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",-v.getMeasuredWidth()*2/3,0);//左侧
} else if (location[0] >= screenWidth * 3 / 4 || (location[1] >= screenHeight / 2 && location[0] > screenWidth / 2)) {
translationAnimator = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",v.getMeasuredWidth()*2/3,0);//右侧
} else {
translationAnimator = new ObjectAnimator().ofFloat(mFloatIcon,"translationY",-v.getMeasuredWidth()*2/3,0);//上侧
}
}
animatorSet = new AnimatorSet(); //组合动画
animatorSet.playTogether(translationAnimator); //设置动画
animatorSet.setDuration(0); //设置动画时间
animatorSet.start(); //启动
break;
case MotionEvent.ACTION_MOVE:
if(!hasEventDown)
return false;
eventMoveX = event.getRawX();
eventMoveY = event.getRawY();
if(!isLongClick) {
isLongClick = isLongPressed(event.getDownTime(), event.getEventTime(), 1000);
}
if(isScroll || isLongClick || (Math.abs(eventMoveX - eventDownStartX) + Math.abs(eventMoveY - eventDownStartY)) >= (v.getMeasuredWidth()+v.getMeasuredHeight())/3) {
isScroll = true;
updateViewPosition();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if(!hasEventDown)
return false;
eventUpEndX = event.getRawX();
eventUpEndY = event.getRawY();
v.getLocationOnScreen(location);
if(!isScroll && ((Math.abs(eventUpEndX - eventDownStartX) + Math.abs(eventUpEndY - eventDownStartY)) < (v.getMeasuredWidth()+v.getMeasuredHeight())/3)){
v.getLocationOnScreen(location);
//判断位于屏幕的位置
if(screenWidth < screenHeight) {//竖屏
if((location[0] == 0 || location[0] == (screenWidth - getMeasuredWidth()))) {
if (location[0] < screenWidth / 2 - getMeasuredWidth() / 2) {//左侧
MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_left_menu_view).setViewAnimationStyle("").
addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getLeft(), v.getTop());
} else {//右侧
MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_right_menu_view).setViewAnimationStyle("").
addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getRight() - MenuViewPopupWindow.getInstance(mContext).getWidth(), v.getTop());
}
}
}else {//横屏
if ((location[0] == 0 || location[0] == (screenWidth - getMeasuredWidth())) && (location[0] <= screenWidth / 4 || (location[1] >= screenHeight / 2 && location[0] <= screenWidth / 2))) {//左侧
MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_left_menu_view).setViewAnimationStyle("").
addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getLeft(), v.getTop());
} else if ((location[0] == 0 || location[0] == (screenWidth - getMeasuredWidth())) && (location[0] >= screenWidth * 3 / 4 || (location[1] >= screenHeight / 2 && location[0] > screenWidth / 2))) {//右侧
MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_right_menu_view).setViewAnimationStyle("").
addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getRight() - MenuViewPopupWindow.getInstance(mContext).getWidth(), v.getTop());
} else {//上侧
if(location[1] == 0) {
if (location[0] <= screenWidth / 2) {//从右侧显示菜单
MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_left_menu_view).setViewAnimationStyle("").
addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getLeft(), v.getTop());
} else {//从左侧显示菜单
MenuViewPopupWindow.getInstance(mContext).setMenuView(R.layout.float_right_menu_view).setViewAnimationStyle("").
addIconOnClick(this).addMenuItemClick(this).showAtLocation(v, Gravity.NO_GRAVITY, v.getRight() - MenuViewPopupWindow.getInstance(mContext).getWidth(), v.getTop());
}
}
}
}
autoViewRunToSide(v,5000);
}else{
autoViewRunToSide(v,3000);
}
isScroll = false;
isLongClick = false;
hasEventDown = false;
break;
}
return false;
}
/**
* 判断是否有长按动作发生
* @param lastDownTime 按下时间
* @param thisEventTime 移动时间
* @param longPressTime 判断长按时间的阀值
*/
private boolean isLongPressed(long lastDownTime, long thisEventTime, long longPressTime){
long intervalTime = thisEventTime - lastDownTime;
if(intervalTime >= longPressTime){
return true;
}
return false;
}
/**
* 更新悬浮窗窗口位置参数
*/
private void updateViewPosition() {
wmParams.x = (int) (eventMoveX - eventDownX );
wmParams.y = (int) (eventMoveY - eventDownY );
wm.updateViewLayout(this, wmParams);
}
/**
* 自动移动位置
*/
private void autoViewRunToSide(View v, int autohideTime) {
if(v == null)
return;
//得到view在屏幕中的位置
v.getLocationOnScreen(location);
//判断位于屏幕的位置
if(screenWidth < screenHeight) {//竖屏
if (location[0] < screenWidth / 2 - v.getMeasuredWidth() / 2) {
RunToSide(LEFT,autohideTime);//左侧
} else {
RunToSide(RIGHT,autohideTime);//右侧
}
}else {//横屏
if (location[0] <= screenWidth / 4 || (location[1] >= screenHeight / 2 && location[0] <= screenWidth / 2)) {//左侧
RunToSide(LEFT, autohideTime);
} else if (location[0] >= screenWidth * 3 / 4 || (location[1] >= screenHeight / 2 && location[0] > screenWidth / 2)) {//右侧
RunToSide(RIGHT, autohideTime);
} else {//上侧
RunToSide(TOP, autohideTime);
}
}
}
/**
* 自动隐藏悬浮窗
* @param side
* @param time
*/
private void autoHideViewToSide(final int side,int time){
TimerTask autohideTimerTask = new TimerTask() {
@Override
public void run() {
if(!isAutoHideRun) {
if(autohideTimer != null){
autohideTimer.cancel();
autohideTimer = null;
}
this.cancel();
return;
}
((Activity)mContext).runOnUiThread(new Runnable() {
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void run() {
if(!isAutoHideRun)
return;
mFloatIcon.setAlpha(0.7f);
if(animatorSet != null && (animatorSet.isStarted() || animatorSet.isRunning())) {
animatorSet.end();
animatorSet.cancel();
animatorSet = null;
}
ObjectAnimator animatorTranslation = null;
ObjectAnimator animatorRotation = null;
switch (side){
case LEFT:
animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",0,-mFloatIcon.getMeasuredWidth()*2/3);
animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,-360);
break;
case RIGHT:
animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",0,mFloatIcon.getMeasuredWidth()*2/3);
animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,360);
break;
case TOP:
animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationY",0,-mFloatIcon.getMeasuredWidth()*2/3);
animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,-360);
break;
case BUTTOM:
break;
}
animatorSet = new AnimatorSet(); //组合动画
animatorSet.playTogether(animatorTranslation,animatorRotation); //设置动画
animatorSet.setDuration(1000); //设置动画时间
animatorSet.start(); //启动
}
});
}
};
isAutoHideRun = true;
autohideTimer = new Timer();
autohideTimer.schedule(autohideTimerTask,time);//隐藏浮标:延迟时间为time(秒)
}
/**
* 手指释放更新悬浮窗位置
*/
public void RunToSide(final int side,final int autohideTime){
if(side != TOP && screenWidth > screenHeight)//横屏且非上侧
speed = 15;
else
speed = 10;
int runLen = 0;
switch (side){
case LEFT:
runLen = location[0];
break;
case RIGHT:
runLen = screenWidth - location[0];
break;
case TOP:
runLen = location[1];
break;
case BUTTOM:
break;
}
timeValue = runLen/speed;
final TimerTask task = new TimerTask() {
@Override
public void run() {
if(!isRunSide) {
if(mTimer != null){
mTimer.cancel();
mTimer = null;
}
this.cancel();
return;
}
timeValue--;
if (timeValue >= 0) {
switch (side){
case LEFT:
wmParams.x = timeValue * speed;
break;
case RIGHT:
wmParams.x = screenWidth - timeValue*speed;
break;
case TOP:
wmParams.y = timeValue * speed;
break;
case BUTTOM:
break;
}
((Activity)mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
if(!isRunSide)
return;
wm.updateViewLayout(FloatView.this,wmParams);
}
});
}else{
isRunSide = false;
if(mTimer!=null) {
mTimer.cancel();
}
switch (side){
case LEFT:
wmParams.x = 0;
break;
case RIGHT:
wmParams.x = screenWidth - getMeasuredWidth();
break;
case TOP:
wmParams.y = 0;
break;
case BUTTOM:
break;
}
((Activity)mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
wm.updateViewLayout(FloatView.this,wmParams);
SharedPreferences sharedPreferences = mContext.getSharedPreferences("location", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sharedPreferences.edit();
edit.putString("float_location", wmParams.x + "|" + wmParams.y);
edit.commit();
}
});
TimerTask autohideTimerTask = new TimerTask() {
@Override
public void run() {
if(!isAutoHideRun) {
if(autohideTimer != null){
autohideTimer.cancel();
autohideTimer = null;
}
this.cancel();
return;
}
((Activity)mContext).runOnUiThread(new Runnable() {
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void run() {
if(!isAutoHideRun)
return;
mFloatIcon.setAlpha(0.7f);
MenuViewPopupWindow.getInstance(mContext).removeMenuItemClick().removeIconOnClick().Dismiss();
if(animatorSet != null && (animatorSet.isStarted() || animatorSet.isRunning())) {
animatorSet.end();
animatorSet.cancel();
}
ObjectAnimator animatorTranslation = null;
ObjectAnimator animatorRotation = null;
switch (side){
case LEFT:
animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",0,-mFloatIcon.getMeasuredWidth()*2/3);
animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,-360);
break;
case RIGHT:
animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationX",0,mFloatIcon.getMeasuredWidth()*2/3);
animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,360);
break;
case TOP:
animatorTranslation = new ObjectAnimator().ofFloat(mFloatIcon,"translationY",0,-mFloatIcon.getMeasuredWidth()*2/3);
animatorRotation = new ObjectAnimator().ofFloat(mFloatIcon,"rotation",0,-360);
break;
case BUTTOM:
break;
}
animatorSet = new AnimatorSet(); //组合动画
animatorSet.playTogether(animatorTranslation,animatorRotation); //设置动画
animatorSet.setDuration(1000); //设置动画时间
animatorSet.start(); //启动
}
});
}
};
isAutoHideRun = true;
autohideTimer = new Timer();
autohideTimer.schedule(autohideTimerTask,autohideTime);//隐藏浮标:延迟autohideTime(秒)
}
((Activity)mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
FloatView.this.invalidate();
}
});
}
};
isRunSide = true;
mTimer = new Timer();
mTimer.schedule(task, 0, 15); // 平移动画效果:隔15毫秒执行一次
}
/**
* 菜单展示后,点击icon事件回调,implement回调接口MenuViewPopupWindow.SDKIconOnClick
*/
@Override
public void OnClick() {
if(mTimer != null){
isRunSide = false;
mTimer.cancel();
mTimer = null;
}
if(autohideTimer != null){
isAutoHideRun = false;
autohideTimer.cancel();
autohideTimer = null;
}
MenuViewPopupWindow.getInstance(mContext).removeMenuItemClick().removeIconOnClick().Dismiss();
getLocationOnScreen(location);
//判断位于屏幕的位置
if(screenWidth < screenHeight) {//竖屏
if (location[0] < screenWidth / 2 - getMeasuredWidth() / 2) {
autoHideViewToSide(LEFT,3000);
}else{
autoHideViewToSide(RIGHT,3000);
}
}else {//横屏
if (location[0] <= screenWidth / 4 || (location[1] >= screenHeight / 2 && location[0] <= screenWidth / 2)) {
autoHideViewToSide(LEFT, 3000);
} else if (location[0] >= screenWidth * 3 / 4 || (location[1] >= screenHeight / 2 && location[0] > screenWidth / 2)) {
autoHideViewToSide(RIGHT, 3000);
} else {
autoHideViewToSide(TOP, 3000);
}
}
}
/**
* 菜单选项item的选择回调,implement回调接口MenuViewPopupWindow.SDKMenuItemClick
* @param position
*/
@Override
public void OnItemClick(final int position) {
if(mTimer != null){
isRunSide = false;
mTimer.cancel();
mTimer = null;
}
if(autohideTimer != null){
isAutoHideRun = false;
autohideTimer.cancel();
autohideTimer = null;
}
MenuViewPopupWindow.getInstance(mContext).removeMenuItemClick().removeIconOnClick().Dismiss();
getLocationOnScreen(location);
//判断位于屏幕的位置
if (screenWidth < screenHeight) {//竖屏
if (location[0] < screenWidth / 2 - getMeasuredWidth() / 2) {
autoHideViewToSide(LEFT, 0);
} else {
autoHideViewToSide(RIGHT, 0);
}
} else {//横屏
if (location[0] <= screenWidth / 4 || (location[1] >= screenHeight / 2 && location[0] <= screenWidth / 2)) {
autoHideViewToSide(LEFT, 0);
} else if (location[0] >= screenWidth * 3 / 4 || (location[1] >= screenHeight / 2 && location[0] > screenWidth / 2)) {
autoHideViewToSide(RIGHT, 0);
} else {
autoHideViewToSide(TOP, 0);
}
}
try {
((Activity) mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
switch (position){
case 0:
showToast(mContext, "选项一", Gravity.CENTER, 3500);
break;
case 1:
showToast(mContext, "选项二", Gravity.CENTER, 3500);
break;
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭悬浮窗口
*/
private void closeFloatView(){
destory();
}
/**
* 提示信息
* @param context
* @param msg
* @param gravity
* @param time Toast.LENGTH_SHORT(2000ms) <= time <= Toast.LENGTH_LONG(3500ms)
*/
private void showToast(Context context, String msg, int gravity, int time){
RelativeLayout rl = new RelativeLayout(context);
TextView tv_toast = new TextView(context);
tv_toast.setText(msg);
tv_toast.setTextColor(Color.WHITE);
tv_toast.setTextSize(Dimension.SP, 18.0f);
tv_toast.setPadding(CommonUtils.dip2px(context, 10), CommonUtils.dip2px(context, 10), CommonUtils.dip2px(context, 10), CommonUtils.dip2px(context, 10));
rl.addView(tv_toast);
rl.setBackgroundColor(Color.parseColor("#b0000000"));
mToast = new Toast(context);
mToast.setGravity(gravity, 0, 0);
mToast.setDuration(time);
mToast.setView(rl);
if(mToast != null)
mToast.show();
}
}
调用方法:
FloatView.getInstance(this).show();//显示
FloatView.getInstance(this).destory();//销毁
注:水平ListView和弹出菜单仅用于提供演示,主要的是核心类(FloatView),核心类的实现原理有兴趣可研究一下。
效果图:
GitHub项目地址:https://github.com/KingToAce/FloatView