Android使用ViewDragHelper实现简单的view拖拽和吸边功能
工作了几年,最开始做的是安卓开发,后面的做了一段时间逆向和sdk开发,一直没有系统的整理自己的知识,打算从本篇博客开始,陆续复习并记录一下自己的安卓知识
一直不知道怎么排版,先凑合着弄下
实现效果,gif上传被压扁了
ViewDragHelper的用法
viewDragHelper是一个安卓自带的处理拖拽的工具
先看一下viewDragHelper的创建步骤
public static ViewDragHelper create(@NonNull ViewGroup forParent, @NonNull ViewDragHelper.Callback cb)
ViewGroup传的就是需要操作的View容器,一般我们把代码写在自定义View内,这里也就直接传this
ViewDragHelper.Callback 这个是处理拖动逻辑的核心模块,具体的方法有
public boolean tryCaptureView(@NonNull View view, int i)
这是判定规则,只有return true的时候才会去执行后续的拖动操作
这里的view是容器内被touch到的字view,只有这个view和我们需要拖动的view为同一个的时候我们才认为是匹配的
也就是 return myView == view;
public int getViewHorizontalDragRange(@NonNull View child);public int getViewVerticalDragRange(@NonNull View child);
容器内可以拖动的区间,只有大于0的时候才可以执行相应方向的操作,一般没有特殊要求,我们会把这个返回值设为当期容器的宽和高
public int clampViewPositionHorizontal(@NonNull View child, int left, int dx); public int clampViewPositionVertical(@NonNull View child, int top, int dy);
这个返回的是被操作的view在横向或纵向所能滑出的最大距离,或者说,在x或y方向的最左或最右,最上或最下所能达到的位置,这个可以有正负,比如x方向负就直接超出左边屏幕了哈
public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel);
这个是view被释放时候执行的操作,我们可以操作view回到指定的位置或者保持不变等
在介绍ViewDragHelper的部分方法
public boolean settleCapturedViewAt(int finalLeft, int finalTop)
把我们所操作的view平滑的滑动到指定的位置
public boolean smoothSlideViewTo(@NonNull View child, int finalLeft, int finalTop)
和上面的settleCapturedViewAt效果类似,这个可以传被滑动的指定view
但是单纯用这两个方法会发现view并没有变动,这就需要搭配下面的方法一起使用
public boolean continueSettling(boolean deferCallbacks);public boolean shouldInterceptTouchEvent(@NonNull MotionEvent ev);public void processTouchEvent(@NonNull MotionEvent ev);
拖动处理三件套,需要让viewDragHelper处理手势和scroller圆滑过渡
实现
首先我们定义一些扩展的变量
private View dragView; //被拖拽的viewprivate ViewDragHelper viewDragHelper;private int mWidth; //容器的宽度private int mHeight; //容器的高度private int mChildWidth; //拖拽的View宽度private int mChildHeight; //拖拽的View高度private boolean onDrag = true; //是否正在被拖拽private boolean dragEnable = true; //是否是可以拖拽的private boolean sideEnable = true; //是否吸边private final int NONE = -1;private int topFinalOffset; //拖拽超出上边界后,释放后回到的top位置private int bottomFinalOffset; //拖拽超出下边界后,释放后回到的bottom位置private int leftFinalOffset; //拖拽超出左边界后,释放后回到的left位置private int rightFinalOffset; //拖拽超出右边界后,释放后回到的right位置 private int leftDragOffset = NONE; //能向左拖拽的最大距离private int rightDragOffset = NONE; //能向右拖拽的最大距离private int topDragOffset = NONE; //能向上拖拽的最大距离private int bottomDragOffset = NONE; //能向下拖拽的最大距离
先初始化并获取一些参数
private void init() { viewDragHelper = ViewDragHelper.create(this, new MyDragCallBack());} @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //获取装载容器的宽高以及拖拽view的宽高 mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight(); mChildHeight = dragView.getMeasuredHeight(); mChildWidth = dragView.getMeasuredWidth(); //默认最多可以拖拽1/2的view出屏幕 leftDragOffset = leftDragOffset == NONE ? mChildWidth / 2 : leftDragOffset; rightDragOffset = rightDragOffset == NONE ? mChildWidth / 2 : rightDragOffset; topDragOffset = topDragOffset == NONE ? mChildHeight / 2 : topDragOffset; bottomDragOffset = bottomDragOffset == NONE ? mChildHeight / 2 : bottomDragOffset; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (lastChildX == 0 && lastChildY == 0) { calLayoutOffset(); } //把view布局到相应的位置,当然第一次就是在左上角,后续位置会发生变化 dragView.layout(lastChildX, lastChildY, lastChildX + mChildWidth, lastChildY + mChildHeight); } public void calLayoutOffset() { //把x,y初始化设置为最终要停留在左上角的位置 lastChildX =leftFinalOffset; lastChildY =topFinalOffset; }@Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() != 1) { throw new RuntimeException("child size must be 1"); } dragView = getChildAt(0); dragView.bringToFront(); }
private Rect mRect = new Rect(); @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (dragEnable) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: int x = (int) ev.getX(); int y = (int) ev.getY(); dragView.getHitRect(mRect); onDrag = mRect.contains(x, y); //如果按下的点在dragView内,则认为是拖动有效,执行viewDragHelper的方法 break; } if (onDrag) return viewDragHelper.shouldInterceptTouchEvent(ev); } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { if (dragEnable) { if (onDrag) { viewDragHelper.processTouchEvent(event); return true; } } return super.onTouchEvent(event); } @Override public void computeScroll() { if (dragEnable) { if (viewDragHelper.continueSettling(true)) { invalidate(); } } } public static int dp2px(Context context, float dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, d
准备做完了,那么就要进行操作的代码了
private class MyDragCallBack extends ViewDragHelper.Callback { @Override public boolean tryCaptureView(@NonNull View view, int i) { return dragView == view; } //以横向拖动为例 //left是当前拖动view的左边的坐标 //我们要做的就是让 left >= 最左的距离 同时 left <= 最右的距离 //就是我们设置的leftDragOffset 和 rightDragOffset @Override public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) { leftDragOffset = leftDragOffset > mChildWidth ? mChildWidth : leftDragOffset; rightDragOffset = rightDragOffset > mChildWidth ? mChildWidth : rightDragOffset; return clamp(left, -leftDragOffset, mWidth - mChildWidth + rightDragOffset); } @Override public int clampViewPositionVertical(@NonNull View child, int top, int dy) { topDragOffset = topDragOffset > mChildHeight ? mChildHeight : topDragOffset; bottomDragOffset = bottomDragOffset > mChildHeight ? mChildHeight : bottomDragOffset; return clamp(top, -topDragOffset, mHeight - mChildHeight + bottomDragOffset); } @Override public int getViewVerticalDragRange(@NonNull View child) {// return super.getViewVerticalDragRange(child); return mHeight; } @Override public int getViewHorizontalDragRange(@NonNull View child) {// return super.getViewHorizontalDragRange(child); return mWidth; } @Override public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) { if (sideEnable) { super.onViewReleased(releasedChild, xvel, yvel); //如果top小于topFinOffset则取topFinalOffset //如果bottom大于最大的offset则取限制的最大bottom int finalTop = dragView.getTop() <= topFinalOffset ? topFinalOffset : dragView.getBottom() >= mHeight - bottomFinalOffset ? mHeight - dragView.getMeasuredHeight() - bottomFinalOffset : dragView.getTop(); lastChildY = finalTop; //根据left和view的一半进行界定,选择是最终停留在左边还是右边 if (Math.abs(dragView.getLeft()) <= (getMeasuredWidth() - dragView.getMeasuredWidth()) / 2) { lastChildX = leftFinalOffset; //平滑过渡到相应位置 viewDragHelper.settleCapturedViewAt(lastChildX, finalTop); } else { lastChildX = getMeasuredWidth() - dragView.getMeasuredWidth() - rightFinalOffset; viewDragHelper.settleCapturedViewAt(lastChildX, finalTop); } invalidate(); } else { lastChildX = dragView.getLeft(); lastChildY = dragView.getTop(); } //把拖拽的标记定为false onDrag = false; } } //其实就是 当前和最大值取最小 同时和最小值取最大 //如果value在两者之间直接返回value //如果value比最小值小,则返回min //如果value比最大值大,则返回max 所以是满足我们的条件的 private int clamp(int value, int min, int max) { return Math.max(min, Math.min(max, value)); }
然后建立布局文件
<?xml version="1.0" encoding="utf-8"?>
引用代码
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); floatLayout = findViewById(R.id.layout); mView = findViewById(R.id.view); floatLayout.enableDrag(true); floatLayout.enableSide(true); //设置最大可拖拽的偏移量 floatLayout.setFinalDragOffsets(80,80,80,80); //设置最终停留的位置偏移 floatLayout.setFinalOffsets(-50); floatLayout.requestLayout(); floatLayout.invalidate(); mView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(FloatLaytoutActivity.this,"view被点击了",Toast.LENGTH_LONG).show(); } }); }
完整的代码
public class FloatLayout extends FrameLayout { private final int NONE = -1; private View dragView; private ViewDragHelper viewDragHelper; private int mWidth; private int mHeight; private int mChildWidth; private int mChildHeight; private boolean onDrag = true; private boolean dragEnable = true; private boolean sideEnable = true; //是否吸边 private int lastChildX; private int lastChildY; private int topFinalOffset; private int bottomFinalOffset; private int leftFinalOffset; private int rightFinalOffset; private int leftDragOffset = NONE; private int rightDragOffset = NONE; private int topDragOffset = NONE; private int bottomDragOffset = NONE; public FloatLayout(@NonNull Context context) { this(context, null); } public FloatLayout(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public FloatLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { viewDragHelper = ViewDragHelper.create(this, new MyDragCallBack()); } public void setBottomDragOffset(int dpValue) { this.bottomDragOffset = dp2px(getContext(), dpValue); } public void setTopDragOffset(int dpValue) { this.topDragOffset = dp2px(getContext(), dpValue); } public void setLeftDragOffset(int dpValue) { this.leftDragOffset = dp2px(getContext(), dpValue); } public void setRightDragOffset(int dpValue) { this.rightDragOffset = dp2px(getContext(), dpValue); } public void setFinalOffsets(int value) { setFinalOffsets(value, value, value, value); } //拖拽能偏移出父容器的值,取正数 public void setFinalDragOffsets(int left, int top, int right, int bottom) { setLeftDragOffset(left); setTopDragOffset(top); setRightDragOffset(right); setBottomDragOffset(bottom); } public void setFinalOffsets(int left, int top, int right, int bottom) { setLeftFinalOffset(left); setTopFinalOffset(top); setRightFinalOffset(right); setBottomFinalOffset(bottom);// calLayoutOffset(); } public void setLeftFinalOffset(int dpValue) { this.leftFinalOffset = dp2px(getContext(), dpValue); } public void setRightFinalOffset(int dpValue) { this.rightFinalOffset = dp2px(getContext(), dpValue); } public void setBottomFinalOffset(int dpValue) { this.bottomFinalOffset = dp2px(getContext(), dpValue); } public void setTopFinalOffset(int dpValue) { this.topFinalOffset = dp2px(getContext(), dpValue); } public void enableDrag(boolean value) { dragEnable = value; } public void enableSide(boolean value) { sideEnable = value; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight(); mChildHeight = dragView.getMeasuredHeight(); mChildWidth = dragView.getMeasuredWidth(); leftDragOffset = leftDragOffset == NONE ? mChildWidth / 2 : leftDragOffset; rightDragOffset = rightDragOffset == NONE ? mChildWidth / 2 : rightDragOffset; topDragOffset = topDragOffset == NONE ? mChildHeight / 2 : topDragOffset; bottomDragOffset = bottomDragOffset == NONE ? mChildHeight / 2 : bottomDragOffset; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (lastChildX == 0 && lastChildY == 0) { calLayoutOffset(); } dragView.layout(lastChildX, lastChildY, lastChildX + mChildWidth, lastChildY + mChildHeight); } public void calLayoutOffset() { lastChildX =leftFinalOffset; lastChildY =topFinalOffset; } @Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() != 1) { throw new RuntimeException("child size must be 1"); } dragView = getChildAt(0); dragView.bringToFront(); } private class MyDragCallBack extends ViewDragHelper.Callback { @Override public boolean tryCaptureView(@NonNull View view, int i) { return dragView == view; } @Override public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) { leftDragOffset = leftDragOffset > mChildWidth ? mChildWidth : leftDragOffset; rightDragOffset = rightDragOffset > mChildWidth ? mChildWidth : rightDragOffset; return clamp(left, -leftDragOffset, mWidth - mChildWidth + rightDragOffset); } @Override public int clampViewPositionVertical(@NonNull View child, int top, int dy) { topDragOffset = topDragOffset > mChildHeight ? mChildHeight : topDragOffset; bottomDragOffset = bottomDragOffset > mChildHeight ? mChildHeight : bottomDragOffset; return clamp(top, -topDragOffset, mHeight - mChildHeight + bottomDragOffset); } @Override public int getViewVerticalDragRange(@NonNull View child) {// return super.getViewVerticalDragRange(child); return mHeight; } @Override public int getViewHorizontalDragRange(@NonNull View child) {// return super.getViewHorizontalDragRange(child); return mWidth; } @Override public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) { if (sideEnable) { super.onViewReleased(releasedChild, xvel, yvel); int finalTop = dragView.getTop() <= topFinalOffset ? topFinalOffset : dragView.getBottom() >= mHeight - bottomFinalOffset ? mHeight - dragView.getMeasuredHeight() - bottomFinalOffset : dragView.getTop(); lastChildY = finalTop; if (Math.abs(dragView.getLeft()) <= (getMeasuredWidth() - dragView.getMeasuredWidth()) / 2) { lastChildX = leftFinalOffset; viewDragHelper.settleCapturedViewAt(lastChildX, finalTop); } else { lastChildX = getMeasuredWidth() - dragView.getMeasuredWidth() - rightFinalOffset; viewDragHelper.settleCapturedViewAt(lastChildX, finalTop); } invalidate(); } else { lastChildX = dragView.getLeft(); lastChildY = dragView.getTop(); } onDrag = false; } } private int clamp(int value, int min, int max) { return Math.max(min, Math.min(max, value)); } private Rect mRect = new Rect(); @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (dragEnable) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: int x = (int) ev.getX(); int y = (int) ev.getY(); dragView.getHitRect(mRect); onDrag = mRect.contains(x, y); break; } if (onDrag) return viewDragHelper.shouldInterceptTouchEvent(ev); } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { if (dragEnable) { if (onDrag) { viewDragHelper.processTouchEvent(event); return true; } } return super.onTouchEvent(event); } @Override public void computeScroll() { if (dragEnable) { if (viewDragHelper.continueSettling(true)) { invalidate(); } } } public static int dp2px(Context context, float dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics()); }}
附上demo地址 https://github.com/gouptosee/ViewDragHelperDemo
更多相关文章
- 图解Android(安卓)View的scrollTo(),scrollBy(),getScrollX(), g
- [android] 百度地图开发 (三).定位当前位置及getLastKnownLocati
- android中引用项目工程中的sqlite文件
- Android的PopupWindow的使用,根据点击位置显示弹窗
- [置顶] AndroidUI组件4- ProgressBar、SeekBar、ImageView、TabH
- 使用Android(安卓)RatingBar时踩过的坑
- 【Android】高仿大众点评中的范围选择控件之RangeSeekBar
- Android(安卓)特色开发,基于位置的服务
- Android实现贪吃蛇游戏一:游戏界面及控制