说到拖拽控件的使用,大家最熟悉的应该是iPhone桌面上的那个home功能的控件了,拖拽它想放哪儿就放哪儿。
今天就和大家一起探讨在Android上实现这种控件的原理。
下面我们来探讨一下具体的实现原理:
1,控件应该是一个ViewGroup,这个ViewGroup包含一个子视图(有点像ScrollView)。
2,控件里面的子视图在拖动的时候,其实是不停的在调用子视图的layout(int left, int top, int right, int bottom)方法。查看layout的文档说明,layout里面的参数是相对于父容器的坐标位置。所以下一步我们就可以监听子视图的拖动事件了。
3,这里监听子视图拖动事件,其实不是子视图自己事件的本身,而是获取当前手指在屏幕上的坐标位置。判断当前的坐标位置是否落在子视图的坐标范围内,如果在内,就执行拖动事件,否则不执行任何操作
4,完整代码如下,详细的实现请看源码和注释:
public class DragView extends ViewGroup {
//是否需要拖动目标
private boolean mIsTarget;
private int mLastScrollX;
private int mLastScrollY;
public DragView(Context context) {
this(context, null);
}
public DragView(Context context, AttributeSet set) {
this(context, set, 0);
}
public DragView(Context context, AttributeSet set, int style) {
super(context, set, style);
}
@Override
public void addView(View child) {
if (getChildCount() > 0) {
throw new IllegalStateException("DragView can host only one direct child");
}
super.addView(child);
}
@Override
public void addView(View child, int index) {
if (getChildCount() > 0) {
throw new IllegalStateException("DragView can host only one direct child");
}
super.addView(child, index);
}
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("DragView can host only one direct child");
}
super.addView(child, params);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("DragView can host only one direct child");
}
super.addView(child, index, params);
}
//这个方法主要控制视图拖动的位置
private void childLayout(int dx, int dy) {
View child = getChildAt(0);
int left = child.getLeft();
int top = child.getTop();
int layoutLeft = left + dx;
int layoutTop = top + dy;
//判断边界
if (layoutLeft < getPaddingLeft() || layoutLeft + child.getWidth() + getPaddingRight() >= getRight()) {
layoutLeft = left;
}
//判断边界
if (layoutTop < getPaddingTop() || layoutTop + child.getHeight() + getPaddingBottom() >= getBottom()) {
layoutTop = top;
}
child.layout(layoutLeft, layoutTop, layoutLeft + child.getWidth(), layoutTop + child.getHeight());
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
//当前是否在拖动,如果在拖动,则拦截当前事件
if (action == MotionEvent.ACTION_MOVE && mIsTarget) {
return true;
}
//如果是按下操作,判断当前坐标是否在拖动视图范围之内
if (action == MotionEvent.ACTION_DOWN) {
View child = getChildAt(0);
int x = (int) ev.getX();
int y = (int) ev.getY();
if (x >= child.getLeft() && x <= child.getRight() && y >= child.getTop() && y <= child.getBottom()) {
mIsTarget = true;
} else {
mIsTarget = false;
}
mLastScrollX = x;
mLastScrollY = y;
}
return super.onInterceptTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
if (!mIsTarget) {
return false;
}
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN :
mLastScrollX = (int) event.getX();
mLastScrollY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE :
int x = (int) event.getX();
int y = (int) event.getY();
int dx = x - mLastScrollX;
int dy = y - mLastScrollY;
childLayout(dx, dy);
mLastScrollX = (int) event.getX();
mLastScrollY = (int) event.getY();
break;
case MotionEvent.ACTION_UP :
case MotionEvent.ACTION_CANCEL :
mIsTarget = false;
break;
}
return true;
}
@Override
protected void onLayout(boolean b, int i, int i2, int i3, int i4) {
if (getChildCount() > 1) {
throw new IllegalStateException("DragView can host only one direct child");
}
if (getChildCount() > 0) {
View view = getChildAt(0);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
LayoutParams lp = (LayoutParams) view.getLayoutParams();
int childLeft = lp.leftMargin;
int childTop = lp.topMargin;
view.layout(childLeft + getPaddingLeft(), childTop + getPaddingTop(), childLeft + width + getPaddingLeft(), childTop + height + getPaddingTop());
}
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() > 0) {
View child = getChildAt(0);
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = 0;
int height = 0;
if (widthMode == MeasureSpec.EXACTLY) {
width = MeasureSpec.getSize(widthMeasureSpec);
} else {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
width = lp.leftMargin + lp.rightMargin + child.getMeasuredWidth() + getPaddingLeft() + getPaddingRight();
}
if (heightMode == MeasureSpec.EXACTLY) {
height = MeasureSpec.getSize(heightMeasureSpec);
} else {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
height = lp.topMargin + lp.bottomMargin + child.getMeasuredHeight() + getPaddingTop() + getPaddingBottom();
}
setMeasuredDimension(width, height);
}
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
}
}
布局代码如下:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/white"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.waqu.android.test.views.DragView
android:padding="10dp"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView
android:id="@+id/drag_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:src="@drawable/app_icon"/>
</com.waqu.android.test.views.DragView>
</RelativeLayout>
希望大家支持原创,转载请说明出处,谢谢