FloatView.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /*
  2. * Copyright (C) 2015 pengjianbo([email protected]), Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package cn.finalteam.floatviewfinal.widget;
  17. import android.content.Context;
  18. import android.content.res.Configuration;
  19. import android.graphics.PixelFormat;
  20. import android.os.Build;
  21. import android.os.Handler;
  22. import android.os.Message;
  23. import android.util.DisplayMetrics;
  24. import android.util.TypedValue;
  25. import android.view.Gravity;
  26. import android.view.LayoutInflater;
  27. import android.view.MotionEvent;
  28. import android.view.View;
  29. import android.view.View.OnTouchListener;
  30. import android.view.WindowManager;
  31. import android.view.animation.Animation;
  32. import android.view.animation.AnimationUtils;
  33. import android.view.animation.LinearInterpolator;
  34. import android.widget.FrameLayout;
  35. import android.widget.ImageView;
  36. import android.widget.LinearLayout;
  37. import android.widget.TextView;
  38. import cn.finalteam.floatviewfinal.utils.ResourceUtils;
  39. import java.util.Timer;
  40. import java.util.TimerTask;
  41. /**
  42. * Desction:悬浮窗
  43. * Author:pengjianbo
  44. * Date:15/10/26 下午8:39
  45. */
  46. public class FloatView extends FrameLayout implements OnTouchListener {
  47. private final int HANDLER_TYPE_HIDE_LOGO = 100;//隐藏LOGO
  48. private final int HANDLER_TYPE_CANCEL_ANIM = 101;//退出动画
  49. private WindowManager.LayoutParams mWmParams;
  50. private WindowManager mWindowManager;
  51. private Context mContext;
  52. //private View mRootFloatView;
  53. private ImageView mIvFloatLogo;
  54. private ImageView mIvFloatLoader;
  55. private LinearLayout mLlFloatMenu;
  56. private TextView mTvAccount;
  57. private TextView mTvFeedback;
  58. private FrameLayout mFlFloatLogo;
  59. private boolean mIsRight;//logo是否在右边
  60. private boolean mCanHide;//是否允许隐藏
  61. private float mTouchStartX;
  62. private float mTouchStartY;
  63. private int mScreenWidth;
  64. private int mScreenHeight;
  65. private boolean mDraging;
  66. private boolean mShowLoader = true;
  67. private Timer mTimer;
  68. private TimerTask mTimerTask;
  69. final Handler mTimerHandler = new Handler() {
  70. public void handleMessage(Message msg) {
  71. if ( msg.what == HANDLER_TYPE_HIDE_LOGO ) {
  72. // 比如隐藏悬浮框
  73. if (mCanHide) {
  74. mCanHide = false;
  75. if (mIsRight) {
  76. mIvFloatLogo.setImageResource(ResourceUtils.getDrawableId(mContext, "pj_image_float_right"));
  77. } else {
  78. mIvFloatLogo.setImageResource(ResourceUtils.getDrawableId(mContext, "pj_image_float_left"));
  79. }
  80. mWmParams.alpha = 0.7f;
  81. mWindowManager.updateViewLayout(FloatView.this, mWmParams);
  82. refreshFloatMenu(mIsRight);
  83. mLlFloatMenu.setVisibility(View.GONE);
  84. }
  85. } else if ( msg.what == HANDLER_TYPE_CANCEL_ANIM ) {
  86. mIvFloatLoader.clearAnimation();
  87. mIvFloatLoader.setVisibility(View.GONE);
  88. mShowLoader = false;
  89. }
  90. super.handleMessage(msg);
  91. }
  92. };
  93. public FloatView(Context context) {
  94. super(context);
  95. init(context);
  96. }
  97. private void init(Context mContext) {
  98. this.mContext = mContext;
  99. mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
  100. // 更新浮动窗口位置参数 靠边
  101. DisplayMetrics dm = new DisplayMetrics();
  102. // 获取屏幕信息
  103. mWindowManager.getDefaultDisplay().getMetrics(dm);
  104. mScreenWidth = dm.widthPixels;
  105. mScreenHeight = dm.heightPixels;
  106. this.mWmParams = new WindowManager.LayoutParams();
  107. // 设置window type
  108. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  109. mWmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
  110. } else {
  111. mWmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
  112. }
  113. // 设置图片格式,效果为背景透明
  114. mWmParams.format = PixelFormat.RGBA_8888;
  115. // 设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
  116. mWmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
  117. // 调整悬浮窗显示的停靠位置为左侧置�?
  118. mWmParams.gravity = Gravity.LEFT | Gravity.TOP;
  119. mScreenHeight = mWindowManager.getDefaultDisplay().getHeight();
  120. // 以屏幕左上角为原点,设置x、y初始值,相对于gravity
  121. mWmParams.x = 0;
  122. mWmParams.y = mScreenHeight / 2;
  123. // 设置悬浮窗口长宽数据
  124. mWmParams.width = LayoutParams.WRAP_CONTENT;
  125. mWmParams.height = LayoutParams.WRAP_CONTENT;
  126. addView(createView(mContext));
  127. mWindowManager.addView(this, mWmParams);
  128. mTimer = new Timer();
  129. hide();
  130. }
  131. @Override
  132. protected void onConfigurationChanged(Configuration newConfig) {
  133. super.onConfigurationChanged(newConfig);
  134. // 更新浮动窗口位置参数 靠边
  135. DisplayMetrics dm = new DisplayMetrics();
  136. // 获取屏幕信息
  137. mWindowManager.getDefaultDisplay().getMetrics(dm);
  138. mScreenWidth = dm.widthPixels;
  139. mScreenHeight = dm.heightPixels;
  140. int oldX = mWmParams.x;
  141. int oldY = mWmParams.y;
  142. switch (newConfig.orientation) {
  143. case Configuration.ORIENTATION_LANDSCAPE://横屏
  144. if ( mIsRight ) {
  145. mWmParams.x = mScreenWidth;
  146. mWmParams.y = oldY;
  147. } else {
  148. mWmParams.x = oldX;
  149. mWmParams.y = oldY;
  150. }
  151. break;
  152. case Configuration.ORIENTATION_PORTRAIT://竖屏
  153. if ( mIsRight ) {
  154. mWmParams.x = mScreenWidth;
  155. mWmParams.y = oldY;
  156. } else {
  157. mWmParams.x = oldX;
  158. mWmParams.y = oldY;
  159. }
  160. break;
  161. }
  162. mWindowManager.updateViewLayout(this, mWmParams);
  163. }
  164. /**
  165. * 创建Float view
  166. * @param context
  167. * @return
  168. */
  169. private View createView(final Context context) {
  170. LayoutInflater inflater = LayoutInflater.from(context);
  171. // 从布局文件获取浮动窗口视图
  172. View rootFloatView = inflater.inflate(ResourceUtils.getLayoutId(context, "pj_widget_float_view"), null);
  173. mFlFloatLogo = (FrameLayout) rootFloatView.findViewById(ResourceUtils.getId(context, "pj_float_view"));
  174. mIvFloatLogo = (ImageView) rootFloatView.findViewById(ResourceUtils.getId(context,
  175. "pj_float_view_icon_imageView"));
  176. mIvFloatLoader = (ImageView) rootFloatView.findViewById(ResourceUtils.getId(
  177. context, "pj_float_view_icon_notify"));
  178. mLlFloatMenu = (LinearLayout) rootFloatView.findViewById(ResourceUtils.getId(
  179. context, "ll_menu"));
  180. mTvAccount = (TextView) rootFloatView.findViewById(ResourceUtils.getId(
  181. context, "tv_account"));
  182. mTvAccount.setOnClickListener(new OnClickListener() {
  183. @Override
  184. public void onClick(View arg0) {
  185. mLlFloatMenu.setVisibility(View.GONE);
  186. openUserCenter();
  187. }
  188. });
  189. mTvFeedback = (TextView) rootFloatView.findViewById(ResourceUtils.getId(
  190. context, "tv_feedback"));
  191. mTvFeedback.setOnClickListener(new OnClickListener() {
  192. @Override
  193. public void onClick(View arg0) {
  194. openFeedback();
  195. mLlFloatMenu.setVisibility(View.GONE);
  196. }
  197. });
  198. rootFloatView.setOnTouchListener(this);
  199. rootFloatView.setOnClickListener(new OnClickListener() {
  200. @Override
  201. public void onClick(View v) {
  202. if ( !mDraging ) {
  203. if (mLlFloatMenu.getVisibility() == View.VISIBLE) {
  204. mLlFloatMenu.setVisibility(View.GONE);
  205. } else {
  206. mLlFloatMenu.setVisibility(View.VISIBLE);
  207. }
  208. }
  209. }
  210. });
  211. rootFloatView.measure(View.MeasureSpec.makeMeasureSpec(0,
  212. View.MeasureSpec.UNSPECIFIED), View.MeasureSpec
  213. .makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
  214. return rootFloatView;
  215. }
  216. @Override
  217. public boolean onTouch(View v, MotionEvent event) {
  218. removeTimerTask();
  219. // 获取相对屏幕的坐标,即以屏幕左上角为原点
  220. int x = (int) event.getRawX();
  221. int y = (int) event.getRawY();
  222. switch (event.getAction()) {
  223. case MotionEvent.ACTION_DOWN:
  224. mTouchStartX = event.getX();
  225. mTouchStartY = event.getY();
  226. mIvFloatLogo.setImageResource(ResourceUtils.getDrawableId(
  227. mContext, "pj_image_float_logo"));
  228. mWmParams.alpha = 1f;
  229. mWindowManager.updateViewLayout(this, mWmParams);
  230. mDraging = false;
  231. break;
  232. case MotionEvent.ACTION_MOVE:
  233. float mMoveStartX = event.getX();
  234. float mMoveStartY = event.getY();
  235. // 如果移动量大于3才移动
  236. if (Math.abs(mTouchStartX - mMoveStartX) > 3
  237. && Math.abs(mTouchStartY - mMoveStartY) > 3) {
  238. mDraging = true;
  239. // 更新浮动窗口位置参数
  240. mWmParams.x = (int) (x - mTouchStartX);
  241. mWmParams.y = (int) (y - mTouchStartY);
  242. mWindowManager.updateViewLayout(this, mWmParams);
  243. mLlFloatMenu.setVisibility(View.GONE);
  244. return false;
  245. }
  246. break;
  247. case MotionEvent.ACTION_UP:
  248. case MotionEvent.ACTION_CANCEL:
  249. if (mWmParams.x >= mScreenWidth / 2) {
  250. mWmParams.x = mScreenWidth;
  251. mIsRight = true;
  252. } else if (mWmParams.x < mScreenWidth / 2) {
  253. mIsRight = false;
  254. mWmParams.x = 0;
  255. }
  256. mIvFloatLogo.setImageResource(ResourceUtils.getDrawableId(
  257. mContext, "pj_image_float_logo"));
  258. refreshFloatMenu(mIsRight);
  259. timerForHide();
  260. mWindowManager.updateViewLayout(this, mWmParams);
  261. // 初始化
  262. mTouchStartX = mTouchStartY = 0;
  263. break;
  264. }
  265. return false;
  266. }
  267. private void removeTimerTask() {
  268. if (mTimerTask != null) {
  269. mTimerTask.cancel();
  270. mTimerTask = null;
  271. }
  272. }
  273. private void removeFloatView() {
  274. try {
  275. mWindowManager.removeView(this);
  276. } catch (Exception ex) {
  277. ex.printStackTrace();
  278. }
  279. }
  280. /**
  281. * 隐藏悬浮窗
  282. */
  283. public void hide() {
  284. setVisibility(View.GONE);
  285. Message message = mTimerHandler.obtainMessage();
  286. message.what = HANDLER_TYPE_HIDE_LOGO;
  287. mTimerHandler.sendMessage(message);
  288. removeTimerTask();
  289. }
  290. /**
  291. * 显示悬浮窗
  292. */
  293. public void show() {
  294. if (getVisibility() != View.VISIBLE) {
  295. setVisibility(View.VISIBLE);
  296. if (mShowLoader) {
  297. mIvFloatLogo.setImageResource(ResourceUtils.getDrawableId(
  298. mContext, "pj_image_float_logo"));
  299. mWmParams.alpha = 1f;
  300. mWindowManager.updateViewLayout(this, mWmParams);
  301. timerForHide();
  302. mShowLoader = false;
  303. Animation rotaAnimation = AnimationUtils.loadAnimation(mContext,
  304. ResourceUtils.getAnimId(mContext, "pj_loading_anim"));
  305. rotaAnimation.setInterpolator(new LinearInterpolator());
  306. mIvFloatLoader.startAnimation(rotaAnimation);
  307. mTimer.schedule(new TimerTask() {
  308. @Override
  309. public void run() {
  310. mTimerHandler.sendEmptyMessage(HANDLER_TYPE_CANCEL_ANIM);
  311. }
  312. }, 3000);
  313. }
  314. }
  315. }
  316. /**
  317. * 刷新float view menu
  318. * @param right
  319. */
  320. private void refreshFloatMenu(boolean right) {
  321. if (right) {
  322. FrameLayout.LayoutParams paramsFloatImage = (FrameLayout.LayoutParams) mIvFloatLogo.getLayoutParams();
  323. paramsFloatImage.gravity = Gravity.RIGHT;
  324. mIvFloatLogo.setLayoutParams(paramsFloatImage);
  325. FrameLayout.LayoutParams paramsFlFloat = (FrameLayout.LayoutParams) mFlFloatLogo.getLayoutParams();
  326. paramsFlFloat.gravity = Gravity.RIGHT;
  327. mFlFloatLogo.setLayoutParams(paramsFlFloat);
  328. int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, mContext.getResources().getDisplayMetrics());
  329. int padding52 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 52, mContext.getResources().getDisplayMetrics());
  330. LinearLayout.LayoutParams paramsMenuAccount = (LinearLayout.LayoutParams) mTvAccount.getLayoutParams();
  331. paramsMenuAccount.rightMargin = padding;
  332. paramsMenuAccount.leftMargin = padding;
  333. mTvAccount.setLayoutParams(paramsMenuAccount);
  334. LinearLayout.LayoutParams paramsMenuFb = (LinearLayout.LayoutParams) mTvFeedback.getLayoutParams();
  335. paramsMenuFb.rightMargin = padding52;
  336. paramsMenuFb.leftMargin = padding;
  337. mTvFeedback.setLayoutParams(paramsMenuFb);
  338. } else {
  339. FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mIvFloatLogo.getLayoutParams();
  340. params.setMargins(0, 0, 0, 0);
  341. params.gravity = Gravity.LEFT;
  342. mIvFloatLogo.setLayoutParams(params);
  343. FrameLayout.LayoutParams paramsFlFloat = (FrameLayout.LayoutParams) mFlFloatLogo.getLayoutParams();
  344. paramsFlFloat.gravity = Gravity.LEFT;
  345. mFlFloatLogo.setLayoutParams(paramsFlFloat);
  346. int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, mContext.getResources().getDisplayMetrics());
  347. int padding52 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 52, mContext.getResources().getDisplayMetrics());
  348. LinearLayout.LayoutParams paramsMenuAccount = (LinearLayout.LayoutParams) mTvAccount.getLayoutParams();
  349. paramsMenuAccount.rightMargin = padding;
  350. paramsMenuAccount.leftMargin = padding52;
  351. mTvAccount.setLayoutParams(paramsMenuAccount);
  352. LinearLayout.LayoutParams paramsMenuFb = (LinearLayout.LayoutParams) mTvFeedback.getLayoutParams();
  353. paramsMenuFb.rightMargin = padding;
  354. paramsMenuFb.leftMargin = padding;
  355. mTvFeedback.setLayoutParams(paramsMenuFb);
  356. }
  357. }
  358. /**
  359. * 定时隐藏float view
  360. */
  361. private void timerForHide() {
  362. mCanHide = true;
  363. //结束任务
  364. if (mTimerTask != null) {
  365. try {
  366. mTimerTask.cancel();
  367. mTimerTask = null;
  368. } catch (Exception e){}
  369. }
  370. mTimerTask = new TimerTask() {
  371. @Override
  372. public void run() {
  373. Message message = mTimerHandler.obtainMessage();
  374. message.what = HANDLER_TYPE_HIDE_LOGO;
  375. mTimerHandler.sendMessage(message);
  376. }
  377. };
  378. if (mCanHide) {
  379. mTimer.schedule(mTimerTask, 6000, 3000);
  380. }
  381. }
  382. /**
  383. * 打开用户中心
  384. */
  385. private void openUserCenter() {
  386. }
  387. /**
  388. * 打开客服页面
  389. */
  390. private void openFeedback() {
  391. }
  392. /**
  393. * 是否Float view
  394. */
  395. public void destroy() {
  396. hide();
  397. removeFloatView();
  398. removeTimerTask();
  399. if (mTimer != null) {
  400. mTimer.cancel();
  401. mTimer = null;
  402. }
  403. try {
  404. mTimerHandler.removeMessages(1);
  405. } catch (Exception e){}
  406. }
  407. }