可全局拖拽、点击的View

先上图

DragView.gif
  • 这个效果和IOS的Assistive Touch效果类似,可以实现全局拖拽,设置点击事件,并且点击和拖拽不冲突。
  • Demo的GitHub地址:https://github.com/icecreammmm/DragView

使用方法

  • FloatTouchListener里传入的第二个为父布局的Layout需要设置为FrameLayout。
    第三个参数,也就是我们需要拖拽的View,需要给这个View外面包上一层FrameLayout,即可实现我们所需要的效果。
    private void setTouchListener() {
        margin = (int) (10 * getResources().getDisplayMetrics().density + 0.5f);
        mFloatTouchListener = new FloatTouchListener(this, flParent, flChild, margin);
        flChild.setOnTouchListener(mFloatTouchListener);
        flChild.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "点击事件", Toast.LENGTH_SHORT).show();
            }
        });
    }

实现方法

从事件分发机制中我们知道,就优先级而言:onTouchListener>onClickListenr。

上面的拖拽事件已经消费了onTouchListener(即onTouch方法中返回true),那么就不会下发到onClickListenr,自然就不会产生点击事件。

也许你想让onTouchListener不消费,然后不就下发到onClickListenr了么?

确实这样可以实现点击事件,但是拖拽功能又实现不了了。

  • 通过上面的分析,最终的解决办法就是:
    onTouch方法中,在接收到ACTION_DOWN后,返回false,交给onClickListenr处理。剩下的ACTION_MOVE/ACTION_UP等事件,返回true,交给onTouchListener处理。这样自然就可以既实现拖拽效果又实现点击效果了。
public class FloatTouchListener implements View.OnTouchListener {
    private View mParentView;
    private View mFloatView;
    private FrameLayout.LayoutParams mFloatViewWindowParam;
    private float mPreviousX = -1;
    private float mPreviousY = -1;
    private boolean mHasMoved = false;
    private int mTouchSlop;
    private int mDownPointerId = -1;
    private Interpolator mInterpolator;
    private FloatAnimatorUpdateListener mUpdateListener;
    private int mMargin;

    public FloatTouchListener(Context context, View parentView, View floatView, int margin) {
        mParentView = parentView;
        mFloatView = floatView;
        mFloatViewWindowParam = (FrameLayout.LayoutParams) floatView.getLayoutParams();
        mInterpolator = new DecelerateInterpolator();
        ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = configuration.getScaledTouchSlop();
        mMargin = margin;
    }

    private boolean adjustMarginParams(View v, MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        float deltaX = x - mPreviousX;
        float deltaY = y - mPreviousY;
        if (!mHasMoved) {
            if (Math.abs(deltaX) < mTouchSlop && Math.abs(deltaY) < mTouchSlop) {
                return false;
            }
        }

        if ((mFloatViewWindowParam.gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
            mFloatViewWindowParam.topMargin = mParentView.getBottom() - mMargin - mFloatView
                    .getHeight();
        }

        if ((mFloatViewWindowParam.gravity & Gravity.RIGHT) == Gravity.RIGHT) {
            mFloatViewWindowParam.leftMargin = mParentView.getRight() - mMargin - mFloatView
                    .getWidth();
        }
        mFloatViewWindowParam.gravity = Gravity.NO_GRAVITY;

        //左上角位置
        int newX = (int) (mFloatViewWindowParam.leftMargin + deltaX);
        int newY = (int) (mFloatViewWindowParam.topMargin + deltaY);
        newX = Math.max(newX, mParentView.getLeft() + mMargin);
        newX = Math.min(newX, mParentView.getRight() - mMargin - mFloatView.getWidth());
        newY = Math.max(newY, mParentView.getTop() + mMargin);
        newY = Math.min(newY, mParentView.getBottom() - mMargin - mFloatView.getHeight());
        mFloatViewWindowParam.leftMargin = newX;
        mFloatViewWindowParam.topMargin = newY;

        return true;
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        int action = MotionEventCompat.getActionMasked(event);
        boolean result = false;
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mDownPointerId = MotionEventCompat.getPointerId(event, 0);
                mPreviousX = event.getX();
                mPreviousY = event.getY();
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (mDownPointerId >= 0) {
                    int index = MotionEventCompat.getActionIndex(event);
                    int id = MotionEventCompat.getPointerId(event, index);
                    if (id == mDownPointerId) {
                        boolean update = adjustMarginParams(view, event);
                        if (!update) {
                            break;
                        }
                        mFloatView.requestLayout();
                        mHasMoved = true;
                        result = true;
                    }
                }
                break;
            }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL: {
                if (mDownPointerId >= 0 && mHasMoved) {
                    event.setAction(MotionEvent.ACTION_CANCEL);
                    adjustMarginParams(view, event);
                    mFloatView.requestLayout();
                    int center = (mParentView.getWidth() - mFloatView.getWidth()) / 2;
                    int x = mFloatViewWindowParam.leftMargin;
                    int destX = 0;
                    if (x < center) {
                        destX = mParentView.getLeft() + mMargin;
                    } else {
                        destX = mParentView.getRight() - mMargin - mFloatView.getWidth();
                    }
                    int deltaHorizon = destX - x;
                    if (Math.abs(deltaHorizon) < 100) {
                        mFloatViewWindowParam.leftMargin = destX;
                        mFloatView.requestLayout();
                    } else {
                        ValueAnimator animator = ValueAnimator.ofInt(x, destX);
                        animator.setInterpolator(mInterpolator);
                        if (mUpdateListener == null) {
                            mUpdateListener = new FloatAnimatorUpdateListener();
                            mUpdateListener.setUpdateView(FloatTouchListener.this);
                        }
                        animator.addUpdateListener(mUpdateListener);
                        animator.setDuration(300);
                        animator.start();
                    }
                }
                resetStatus();
                break;
            }
            default:
                break;
        }
        return result;
    }

    private void resetStatus() {
        mDownPointerId = -1;
        mPreviousX = -1;
        mPreviousY = -1;
        mHasMoved = false;
    }

    private class FloatAnimatorUpdateListener implements ValueAnimator.AnimatorUpdateListener {

        private WeakReference<FloatTouchListener> mListener;

        public void setUpdateView(FloatTouchListener listener) {
            mListener = new WeakReference<FloatTouchListener>(listener);
        }

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer value = (Integer) animation.getAnimatedValue();
            FloatTouchListener listener = null;
            if (mListener == null || (listener = mListener.get()) == null) {
                return;
            }
            listener.mFloatViewWindowParam.leftMargin = value;
            mFloatView.requestLayout();
        }
    }
}

注:文章参考Ruheng写的://www.greatytc.com/p/cc4d3c53d476,并加以修改,更易于使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容