一直不知道怎么排版,先凑合着弄下
实现效果,gif上传被压扁了
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; //被拖拽的view
private 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));
}
然后建立布局文件
引用代码
@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