仿微信语音图标拖动并松开时实现自动贴边

本文介绍了在Android中实现仿微信语音图标拖动后自动贴到屏幕边缘的功能。通过复习Android中获取控件位置的方法,如事件坐标、视图边距等,然后在ACTION_DOWN、ACTION_MOVE和ACTION_UP事件中处理拖放逻辑。在onTouch方法中设置条件判断,确保图标在松手后能正确动画贴边。虽然没有提供运行示例,但代码已充分注释,有助于理解实现过程。

在开始具体说该功能代码的之前,先重温下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);
    }

若是有对事件分发机制不太了解的,可以看我转载的一篇关于事件分发机制阐述的十分的详细、易懂事件分发机制


本来想贴上去运行好的例子,突然发现还不会如何把视频贴上去,请原谅我,表示写博客没多久,还没摸索出来呢。有需要的可以先运行下,看着运行好的例子去看代码也许会好点。




评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值