• Android自定义View之可拖拽悬浮控件 代码赏析


    网上一位大神写的控件, 觉得写得很好,再三品味,加上了一些自己的思考、注释和补充。

    1. import android.content.Context;
    2. import android.content.res.Configuration;
    3. import android.os.Handler;
    4. import android.os.Looper;
    5. import android.util.AttributeSet;
    6. import android.view.MotionEvent;
    7. import android.view.ViewConfiguration;
    8. import android.view.ViewGroup;
    9. import android.widget.FrameLayout;
    10. public class FloatingMagnetView extends FrameLayout {
    11. public static final int MARGIN_EDGE = 33;
    12. private float mOriginalRawX;
    13. private float mOriginalRawY;
    14. private float mOriginalX;
    15. private float mOriginalY;
    16. private MagnetViewListener mMagnetViewListener;
    17. private static final int TOUCH_TIME_THRESHOLD = 150;
    18. private long mLastTouchDownTime;
    19. protected MoveAnimator mMoveAnimator;
    20. protected int mScreenWidth;
    21. private int mScreenHeight;
    22. private int mStatusBarHeight;
    23. private boolean isNearestLeft = true;
    24. private float mPortraitY;
    25. private boolean dragEnable = true;
    26. private boolean autoMoveToEdge = true;
    27. private float originX;
    28. private float originY;
    29. private float moveX;
    30. private float moveY;
    31. public void setMagnetViewListener(MagnetViewListener magnetViewListener) {
    32. this.mMagnetViewListener = magnetViewListener;
    33. }
    34. public FloatingMagnetView(Context context) {
    35. this(context, null);
    36. }
    37. public FloatingMagnetView(Context context, AttributeSet attrs) {
    38. this(context, attrs, 0);
    39. }
    40. public FloatingMagnetView(Context context, AttributeSet attrs, int defStyleAttr) {
    41. super(context, attrs, defStyleAttr);
    42. init();
    43. }
    44. private void init() {
    45. mMoveAnimator = new MoveAnimator();
    46. mStatusBarHeight = SystemUtils.getStatusBarHeight(getContext());
    47. setClickable(true);
    48. // updateSize();
    49. }
    50. /**
    51. * @param dragEnable 是否可拖动
    52. */
    53. public void updateDragState(boolean dragEnable) {
    54. this.dragEnable = dragEnable;
    55. }
    56. /**
    57. * @param autoMoveToEdge 是否自动到边缘
    58. */
    59. public void setAutoMoveToEdge(boolean autoMoveToEdge) {
    60. this.autoMoveToEdge = autoMoveToEdge;
    61. }
    62. @Override
    63. public boolean onTouchEvent(MotionEvent event) {
    64. if (event == null) {
    65. return false;
    66. }
    67. switch (event.getAction()) {
    68. case MotionEvent.ACTION_DOWN:
    69. //记录下当前按下点的坐标相对父容器的位置
    70. originY = event.getY();
    71. originX = event.getX();
    72. dealDownEvent();
    73. break;
    74. case MotionEvent.ACTION_MOVE:
    75. updateViewPosition(event);
    76. break;
    77. case MotionEvent.ACTION_UP:
    78. clearPortraitY();
    79. if (autoMoveToEdge) {
    80. moveToEdge();
    81. }
    82. if (isOnClickEvent()) {
    83. dealClickEvent();
    84. } else {
    85. dealUpEvent();
    86. }
    87. break;
    88. }
    89. return true;
    90. }
    91. protected void dealUpEvent() {
    92. if (mMagnetViewListener != null) {
    93. mMagnetViewListener.onUp(this);
    94. }
    95. }
    96. protected void dealClickEvent() {
    97. if (mMagnetViewListener != null) {
    98. mMagnetViewListener.onClick(this);
    99. }
    100. }
    101. protected void dealDownEvent() {
    102. if (mMagnetViewListener != null) {
    103. mMagnetViewListener.onDown(this);
    104. }
    105. }
    106. protected boolean isOnClickEvent() {
    107. return System.currentTimeMillis() - mLastTouchDownTime < TOUCH_TIME_THRESHOLD;
    108. }
    109. private void updateViewPosition(MotionEvent event) {
    110. //dragEnable
    111. if (!dragEnable) return;
    112. // *********************移动ViewGroup方法一 :layout *****************/
    113. // float y = event.getY();
    114. // float x = event.getX();
    115. //
    116. // moveX = x - originX;
    117. // moveY = y - originY;
    118. //
    119. // //view移动前的上下左右位置
    120. // float left = getLeft() + moveX;
    121. // float top = getTop() + moveY;
    122. // float right = getRight() + moveX;
    123. // float bottom = getBottom() + moveY;
    124. //
    125. // if ((left) < 0) {
    126. // left = 0;
    127. // right = getWidth();
    128. // }
    129. //
    130. // if (top < mStatusBarHeight) {
    131. // top = mStatusBarHeight;
    132. // bottom = getHeight()+mStatusBarHeight;
    133. // }
    134. // layout((int) (left), (int) (top), (int) (right), (int) (bottom));
    135. // **********************移动ViewGroup方法二:setX,setY ******************//
    136. //限制不可超出屏幕宽度
    137. float desX = mOriginalX + event.getRawX() - mOriginalRawX;
    138. if (desX < 0) {
    139. desX = MARGIN_EDGE;
    140. }
    141. if (desX > mScreenWidth) {
    142. desX = mScreenWidth - MARGIN_EDGE;
    143. }
    144. setX(desX);
    145. // 限制不可超出屏幕高度
    146. float desY = mOriginalY + event.getRawY() - mOriginalRawY;
    147. if (desY < mStatusBarHeight) {
    148. desY = mStatusBarHeight;
    149. }
    150. if (desY > mScreenHeight - getHeight()) {
    151. desY = mScreenHeight - getHeight();
    152. }
    153. setY(desY);
    154. }
    155. private void changeOriginalTouchParams(MotionEvent event) {
    156. mOriginalX = getX();
    157. mOriginalY = getY();
    158. mOriginalRawX = event.getRawX();
    159. mOriginalRawY = event.getRawY();
    160. mLastTouchDownTime = System.currentTimeMillis();
    161. }
    162. protected void updateSize() {
    163. ViewGroup viewGroup = (ViewGroup) getParent();
    164. if (viewGroup != null) {
    165. mScreenWidth = viewGroup.getWidth() - getWidth();
    166. mScreenHeight = viewGroup.getHeight();
    167. }
    168. // mScreenWidth = (SystemUtils.getScreenWidth(getContext()) - this.getWidth());
    169. // mScreenHeight = SystemUtils.getScreenHeight(getContext());
    170. }
    171. public void moveToEdge() {
    172. //dragEnable
    173. if (!dragEnable) return;
    174. moveToEdge(isNearestLeft(), false);
    175. }
    176. public void moveToEdge(boolean isLeft, boolean isLandscape) {
    177. float moveDistance = isLeft ? MARGIN_EDGE : mScreenWidth - MARGIN_EDGE;
    178. float y = getY();
    179. if (!isLandscape && mPortraitY != 0) {
    180. y = mPortraitY;
    181. clearPortraitY();
    182. }
    183. mMoveAnimator.start(moveDistance, Math.min(Math.max(0, y), mScreenHeight - getHeight()));
    184. }
    185. private void clearPortraitY() {
    186. mPortraitY = 0;
    187. }
    188. protected boolean isNearestLeft() {
    189. int middle = mScreenWidth / 2;
    190. isNearestLeft = getX() < middle;
    191. return isNearestLeft;
    192. }
    193. public void onRemove() {
    194. if (mMagnetViewListener != null) {
    195. mMagnetViewListener.onRemove(this);
    196. }
    197. }
    198. //最赞的,用最基础的代码实现了动画。handler+setX()
    199. protected class MoveAnimator implements Runnable {
    200. private Handler handler = new Handler(Looper.getMainLooper());
    201. private float destinationX;
    202. private float destinationY;
    203. private long startingTime;
    204. void start(float x, float y) {
    205. this.destinationX = x;
    206. this.destinationY = y;
    207. startingTime = System.currentTimeMillis();
    208. handler.post(this);
    209. }
    210. @Override
    211. public void run() {
    212. if (getRootView() == null || getRootView().getParent() == null) {
    213. return;
    214. }
    215. //400ms
    216. float progress = Math.min(1, (System.currentTimeMillis() - startingTime) / 400f);
    217. float deltaX = (destinationX - getX()) * progress;
    218. float deltaY = (destinationY - getY()) * progress;
    219. move(deltaX, deltaY);
    220. if (progress < 1) {
    221. handler.post(this);
    222. }
    223. }
    224. private void stop() {
    225. handler.removeCallbacks(this);
    226. }
    227. }
    228. //最基础的代码实现的动画,通过handler 不断的 setX,setY来移动位置
    229. private void move(float deltaX, float deltaY) {
    230. setX(getX() + deltaX);
    231. setY(getY() + deltaY);
    232. }
    233. @Override
    234. protected void onConfigurationChanged(Configuration newConfig) {
    235. super.onConfigurationChanged(newConfig);
    236. if (getParent() != null) {
    237. final boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
    238. markPortraitY(isLandscape);
    239. ((ViewGroup) getParent()).post(new Runnable() {
    240. @Override
    241. public void run() {
    242. updateSize();
    243. moveToEdge(isNearestLeft, isLandscape);
    244. }
    245. });
    246. }
    247. }
    248. private void markPortraitY(boolean isLandscape) {
    249. if (isLandscape) {
    250. mPortraitY = getY();
    251. }
    252. }
    253. private float touchDownX;
    254. private void initTouchDown(MotionEvent ev) {
    255. changeOriginalTouchParams(ev);
    256. updateSize();
    257. mMoveAnimator.stop();
    258. }
    259. //判断是否拦截父容器
    260. @Override
    261. public boolean onInterceptTouchEvent(MotionEvent ev) {
    262. boolean intercepted = false;
    263. switch (ev.getActionMasked()) {
    264. case MotionEvent.ACTION_DOWN:
    265. intercepted = false;
    266. touchDownX = ev.getX();
    267. initTouchDown(ev);
    268. break;
    269. case MotionEvent.ACTION_MOVE:
    270. intercepted = Math.abs(touchDownX - ev.getX()) >= ViewConfiguration.get(getContext()).getScaledTouchSlop();
    271. break;
    272. case MotionEvent.ACTION_UP:
    273. intercepted = false;
    274. break;
    275. }
    276. return intercepted;
    277. }
    278. }

  • 相关阅读:
    STM32C0开发(1)----SPI 驱动WS2812灯珠
    功能基础篇3——Python中的输入输出、文件读写、序列化
    (附源码)php养老院管理系统 毕业设计 202026
    SpringBoot 开放HTTPS HTTP ,并且强制HTTP转HTTPS端口
    适应复杂环境的工业路由器钡铼R40在大型基础设施中的应用
    MySQL表的约束
    数据分享|函数型数据分析部分省市新冠疫情数据
    SpringBoot博客论坛管理系统
    函数及其应用(2)——湖南工商大学
    基于SSM的图书进销存管理系统
  • 原文地址:https://blog.csdn.net/nnmmbb/article/details/126266506