工作了幾年,最開始做的是安卓開發,後面的做了一段時間逆向和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; //被拖拽的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));
}
然後建立佈局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.viewdemo.view.FloatLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/view"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#f00"
/>
</com.example.viewdemo.view.FloatLayout>
</LinearLayout>
引用代碼
@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