在开始具体说该功能代码的之前,先重温下android中获得控件距屏幕、距父View、距控件边缘等距离的方法以及代表含义。网上有很多,这里先贴出来一张图片
其中
event.getX(): 表示触摸点距离自身左边界的距离。
event.getY():表示触摸点距离自身上边界的距离。
event.getRawX():表示触摸点距离屏幕左边界的距离。
event.getRawY():表示触摸点距离屏幕上边界的距离。
view.getWidth():表示当前控件的宽度,即getRight()-getLeft()。
view.getHeight():表示当前控件的高度,即getBottom()-getTop()。
view.getTop():表示子View的顶部到父View顶部的距离。
view.getBottom():表示子View的底部到父View顶部的距离。
view.getRight():表示子View的右边界到父View左边界的距离。
view.getLeft():表示子View的左边界到父View左边界的距离。
这里再具体说下操作,手指拖动图标,在任意位置松开手指后,该图标会自动按照设定的动画,自动贴到屏幕边缘处;从过程拖动到松开,对于view手指操作这里是经历了三个过程MotionEvent.ACTION_DOWN(点击),MotionEvent.ACTION_MOVE(滑动)和MotionEvent.ACTION_UP(抬起),本篇主要是围绕这三个动作去做处理的。
好了,重温过后,下面再来看代码,就好理解多了。
public class DragWeltView extends ImageView implements View.OnTouchListener {
private int screenWidth, screenHeight;
private Context mContext;
private onDragViewClickListener mLister;
private ViewGroup.MarginLayoutParams layoutParams;
private int lastX, lastY;//表示相对于left、top之前或者说最初当前view距左屏幕、距上屏幕的距离
private int left, top;//表示移动后当前view距左屏幕、距上屏幕的距离
private int startX;
private int endX;
private boolean isMoved = false;//表示是否滑动
public DragWeltView(Context context) {
this(context, null);
}
public DragWeltView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
screenWidth = displayMetrics.widthPixels;
screenHeight = displayMetrics.heightPixels - getStatusBarHeight();
init();
}
private void init() {
setOnTouchListener(this);
//初始化控件的位置
post(new Runnable() {
@Override
public void run() {
layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.topMargin = screenHeight / 2;
layoutParams.leftMargin = screenWidth / 2;
setLayoutParams(layoutParams);
}
});
}
@Override
public boolean onTouch(final View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
startX = lastX;
break;
case MotionEvent.ACTION_MOVE://滑动时,该处会不断的执行
isMoved = true;
int dx = (int) event.getRawX() - lastX;
int dy = (int) event.getRawY() - lastY;
Log.e("aaa", v.getLeft() + "");
//此处再做说明下,由于滑动时每一时刻都是瞬间执行的,为了好说明,这里分为滑动前某一时刻和
//滑动后某一时刻,那么此处v.getLeft、getTop、getRight、getBottom方法拿的值均为滑动前那
// 一时刻的值。
left = v.getLeft() + dx;
top = v.getTop() + dy;
int right = v.getRight() + dx;
int bottom = v.getBottom() + dy;
//设置不能出界
if (left < 0) {
left = 0;
right = left + v.getWidth();
}
if (right > screenWidth) {
right = screenWidth;
left = right - v.getWidth();
}
if (top < 0) {
top = 0;
bottom = top + v.getHeight();
}
if (bottom > screenHeight) {
bottom = screenHeight;
top = bottom - v.getHeight();
}
v.layout(left, top, right, bottom);
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
//只有滑动改变上边距时,抬起才进行设置
if (isMoved) {
layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.topMargin = top;
setLayoutParams(layoutParams);
}
endX = (int) event.getRawX();
//滑动距离比较小,当作点击事件处理
if (Math.abs(startX - endX) < 5) {
return false;
}
if (left + v.getWidth() / 2 < screenWidth / 2) {
startScroll(left, screenWidth / 2, true);
} else {
startScroll(left, screenWidth / 2, false);
}
break;
}
return true;
}
/**
* 监听回调
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
mLister.onDragViewClick();
return super.onTouchEvent(event);
}
/**
* 动画执行
* @param start
* @param end
* @param isLeft
*/
private void startScroll(final int start, int end, final boolean isLeft) {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end).setDuration(300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (isLeft) {
layoutParams.leftMargin = (int) (start * (1 - animation.getAnimatedFraction()));
} else {
layoutParams.leftMargin = (int) (start + (screenWidth - start - getWidth())
* (animation.getAnimatedFraction()));
}
setLayoutParams(layoutParams);
}
});
valueAnimator.start();
}
/**
* 获取状态栏的高度
*
* @return 状态栏高度
*/
private int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
public interface onDragViewClickListener {
void onDragViewClick();
}
public void setOnDragViewClickListener(onDragViewClickListener listener) {
this.mLister = listener;
}
}
看过代码后,里面的具体地方都已经注释了,应该都能看得比较明白了,这里在说明一下,在onTouch方法中我们设置了这么一个if条件语句
//滑动距离比较小,当作点击事件处理
if (Math.abs(startX - endX) < 5) {
return false;
}这里主要还是利用了事件分发机制,若是对事件分发机制比较了解的话,就会知道在view层面将对同一时间处理的优先级上onTouch()是要优于onTouchEvent(),故而这里若是监听到滑动的位置很小,则返回false,交给下面的我们写好的回调监听onTouchEvent()处理/消费该事件
/**
* 监听回调
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
mLister.onDragViewClick();
return super.onTouchEvent(event);
}
若是有对事件分发机制不太了解的,可以看我转载的一篇关于事件分发机制阐述的十分的详细、易懂事件分发机制
本来想贴上去运行好的例子,突然发现还不会如何把视频贴上去,请原谅我,表示写博客没多久,还没摸索出来呢。有需要的可以先运行下,看着运行好的例子去看代码也许会好点。
本文介绍了在Android中实现仿微信语音图标拖动后自动贴到屏幕边缘的功能。通过复习Android中获取控件位置的方法,如事件坐标、视图边距等,然后在ACTION_DOWN、ACTION_MOVE和ACTION_UP事件中处理拖放逻辑。在onTouch方法中设置条件判断,确保图标在松手后能正确动画贴边。虽然没有提供运行示例,但代码已充分注释,有助于理解实现过程。
3432

被折叠的 条评论
为什么被折叠?



