Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

preface

There has always been a withdrawal function in our project. In practice, it is a red envelope View that can be dragged. After clicking, it is the logic of response

Recently, an international version of the customer asked to use, I was wondering, Google Store and foreign customers to eat this set? Whatever the client is, turn it on.

Look at the effect

The red one is an effect in our project, it seems to be wrapped by our current Android big brother, the code is a bit messy, I don’t understand. You can see at the end there’s a little bit of a drag jam, don’t worry about that, it’s a projection software problem.

There are some problems in using this View. There is a banner on our home page. Whenever you drag your finger, as long as the banner above is switched, makka will be moved to the left or right. Very freak. we programmers can not tolerate this kind of thing happening, immediately start to change!!

First, post the old code and let’s think about it

/**
 * 红包悬浮窗
 */
public class FloatDragView {

    // 屏幕的宽度 屏幕的高度
    private static int mScreenWidth = -1, mScreenHeight = -1;
    // 用于记录上一次的位置(坐标0对应x,坐标1对应y)
    private static int[] lastPosition;
    // 上下文
    private final Activity context;
    // 可拖动按钮(外层布局)
    private RelativeLayout mImageView;
    // 是否截断touch事件
    private boolean isIntercept = false;
    // 控件宽高
    private int mImageViewWidth, mImageViewHeight;
    // 控件相对屏幕左上角移动的位置
    private int relativeMoveX, relativeMoveY;

    /**
     * 初始化实例
     *
     * @param context
     */
    public FloatDragView(Activity context) {
        this.context = context;
        mScreenWidth = ScreenSizeUtils.getInstance(context).getScreenWidth();
        mScreenHeight = ScreenSizeUtils.getScreenHeight(context);
        lastPosition = new int[]{0, 0};
    }

    /**
     * @param context        上下文
     * @param mViewContainer 可拖动按钮要存放的对应的Layout
     * @param clickListener  可拖动按钮的点击事件
     */
    public void addFloatDragView(Activity context, RelativeLayout mViewContainer, View.OnClickListener clickListener) {
        // 设置宽高
        mImageViewWidth = ImageUtil.dp2px(context, 55);
        mImageViewHeight = mImageViewWidth * 136 / 107;
        // 获取拖动按钮
        mImageView = getFloatDragView(clickListener);
        if (!getChildA(mViewContainer)) {
            mViewContainer.addView(mImageView);
            // 设置拖动按钮,并添加在父布局里
            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mImageView.getLayoutParams();
            layoutParams.width = mImageViewWidth;
            layoutParams.height = mImageViewHeight;
            mImageView.setLayoutParams(layoutParams);
        }
    }

    /**
     * @param clickListener
     * @return 获取可拖动按钮的实例
     */
    private RelativeLayout getFloatDragView(View.OnClickListener clickListener) {
        if (mImageView != null && mImageView.getChildCount() != 0) {
            if (mImageView.getVisibility() != View.VISIBLE) {
                mImageView.setVisibility(View.VISIBLE);
            }
            return mImageView;
        } else {
            if (mImageView == null) {
                mImageView = new RelativeLayout(context);
                mImageView.setTag("123456789");
            }
            mImageView.setVisibility(View.VISIBLE);
            mImageView.setClickable(true);
            mImageView.setFocusable(true);
            if (clickListener != null) {
                mImageView.setOnClickListener(clickListener);
            }
            // 添加图片控件
            ImageView imageView = new ImageView(context);
            imageView.setClickable(false);
            imageView.setFocusable(false);
            imageView.setEnabled(false);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            imageView.setImageResource(R.mipmap.float_red_package);
            RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            mImageView.addView(imageView, imageParams);
            // 初始位置
            RelativeLayout.LayoutParams lpFeedback = new RelativeLayout.LayoutParams(
                    RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
            lpFeedback.setMargins(0, 0, 15, 250);
            lpFeedback.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
            lpFeedback.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            lpFeedback.width = mImageViewWidth;
            lpFeedback.height = mImageViewHeight;
            mImageView.setLayoutParams(lpFeedback);
            // 设置拖动
            setFloatDragViewTouch(mImageView);
            return mImageView;
        }
    }

    /**
     * 可拖动按钮的touch事件
     *
     * @param floatDragView
     */
    @SuppressLint("ClickableViewAccessibility")
    private void setFloatDragViewTouch(final RelativeLayout floatDragView) {
        floatDragView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(final View v, MotionEvent event) {
                int action = event.getAction();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        isIntercept = false;
                        relativeMoveX = (int) event.getRawX();
                        relativeMoveY = (int) event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        int dx = (int) event.getRawX() - relativeMoveX;
                        int dy = (int) event.getRawY() - relativeMoveY;
                        // 这里修复一些华为手机无法触发点击事件
                        int distance = (int) Math.sqrt(dx * dx + dy * dy);
                        // 此处稍微增加一些移动的偏移量,防止手指抖动,误判为移动无法触发点击时间
                        if (distance == 0) {
                            isIntercept = false;
                            Log.e("TAG", "isIntercept: ");
                            break;
                        }
                        isIntercept = true;
                        int left = v.getLeft() + dx;
                        int top = v.getTop() + dy;
                        int right = v.getRight() + dx;
                        int bottom = v.getBottom() + dy;
                        // 范围判断
                        if (left < 15) {
                            left = 15;
                            right = left + v.getWidth();
                        }
                        if (right > mScreenWidth - 15) {
                            right = mScreenWidth - 15;
                            left = right - v.getWidth();
                        }
                        if (top < 250) {
                            top = 250;
                            bottom = top + v.getHeight();
                        }
                        if (bottom > mScreenHeight - 250) {
                            bottom = mScreenHeight - 250;
                            top = bottom - v.getHeight();
                        }
                        Log.e("TAG", "onTouch: "+left+"   "+top+"   "+right+"   "+bottom);
                        v.layout(left, top, right, bottom);
                        relativeMoveX = (int) event.getRawX();
                        relativeMoveY = (int) event.getRawY();
                        break;
                    case MotionEvent.ACTION_UP:
                        if (isIntercept) {
                            // 每次移动都要设置其layout,不然由于父布局可能嵌套listview,
                            // 当父布局发生改变冲毁(如下拉刷新时)则移动的view会回到原来的位置
                            RelativeLayout.LayoutParams lpFeedback = new RelativeLayout.LayoutParams(
                                    RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
                            lpFeedback.setMargins(v.getLeft(), v.getTop(), 0, 0);
                            lpFeedback.width = mImageViewWidth;
                            lpFeedback.height = mImageViewHeight;
                            v.setLayoutParams(lpFeedback);
                            // 设置靠近边沿的
                            setImageViewNearEdge(v);
                        }
                        break;
                }
                return isIntercept;
            }
        });
    }

    /**
     * 将拖动按钮移动到边沿
     *
     * @param v
     */
    private void setImageViewNearEdge(final View v) {
        Log.e("TAG", "setImageViewNearEdge: "+v.getLeft());
        if (v.getLeft() < (mScreenWidth / 2)) {
            // 设置位移动画 向左移动控件位置
            final TranslateAnimation animation = new TranslateAnimation(0, -v.getLeft() + 15, 0, 0);
            animation.setDuration(400);// 设置动画持续时间
            animation.setRepeatCount(0);// 设置重复次数
            animation.setFillAfter(true);
            animation.setRepeatMode(Animation.ABSOLUTE);
            animation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation arg0) {
                    // TODO: 2017/3/1
                }

                @Override
                public void onAnimationRepeat(Animation arg0) {
                    // TODO: 2017/3/1
                }

                @Override
                public void onAnimationEnd(Animation arg0) {
                    v.clearAnimation();
                    RelativeLayout.LayoutParams lpFeedback = new RelativeLayout.LayoutParams(
                            RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
                    lpFeedback.setMargins(15, v.getTop(), 0, 0);
                    lpFeedback.width = mImageViewWidth;
                    lpFeedback.height = mImageViewHeight;
                    v.setLayoutParams(lpFeedback);
                    v.postInvalidateOnAnimation();
                    lastPosition[0] = 0;
                    lastPosition[1] = v.getTop();
                }
            });
            v.startAnimation(animation);
        } else {
            final TranslateAnimation animation = new TranslateAnimation(0,
                    mScreenWidth - v.getLeft() - v.getWidth() - 15, 0, 0);
            animation.setDuration(400);// 设置动画持续时间
            animation.setRepeatCount(0);// 设置重复次数
            animation.setRepeatMode(Animation.ABSOLUTE);
            animation.setFillAfter(true);
            animation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation arg0) {
                    // TODO: 2017/3/1
                }

                @Override
                public void onAnimationRepeat(Animation arg0) {
                    // TODO Auto-generated method stub
                }

                @Override
                public void onAnimationEnd(Animation arg0) {
                    v.clearAnimation();
                    RelativeLayout.LayoutParams lpFeedback = new RelativeLayout.LayoutParams(
                            RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
                    lpFeedback.setMargins(mScreenWidth - v.getWidth() - 15, v.getTop(), 0, 0);
                    lpFeedback.width = mImageViewWidth;
                    lpFeedback.height = mImageViewHeight;
                    v.setLayoutParams(lpFeedback);
                    v.postInvalidateOnAnimation();
                    lastPosition[0] = mScreenWidth - v.getWidth();
                    lastPosition[1] = v.getTop();
                }
            });
            v.startAnimation(animation);
        }
    }

    public void remove() {
        if (mImageView != null) {
            mImageView.removeAllViews();
            mImageView.setVisibility(View.GONE);
        }
    }

    private Boolean getChildA(View view) {
        Boolean a = false;
        if (view instanceof ViewGroup) {
            ViewGroup vp = (ViewGroup) view;
            for (int i = 0; i < vp.getChildCount(); i++) {
                View viewchild = vp.getChildAt(i);
                if (viewchild.getTag() != null && String.valueOf(viewchild.getTag()).equals("123456789")) {
                    return true;
                }
                a = a || getChildA(viewchild);
            }
        }
        return a;
    }
}
Copy the code

usage

Just new one and call addFloatDragView with three parameters, the first is the context, the second is a RelativeLayout, and the third is click listener.

This is very restrictive, and your root layout must be RelativeLayout, otherwise it won’t work. You can also go inside and replace all your RelativeLayout with ConstraintLayout, and you’ll notice that the View is destroyed, the motion animation doesn’t work, and it’s stuck in the top left corner. Do not change this situation on the basis of direct custom than modify save time.

So here’s a View THAT I just arbitrarily wrote

/** * public class TestImageView extends AppCompatImageView {private int mScreenWidth; private int mScreenHeight; private int mLastX; private int mLastY; private boolean isMove = false; private OnClickListener onClickListener; private Activity activity; public TestImageView(@NonNull @NotNull Context context) { super(context); init(context); } public TestImageView(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) { super(context, attrs); init(context); } public TestImageView(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } public void setActivity(Activity activity) { this.activity = activity; } private void init(Context context) { mScreenWidth = ScreenSizeUtils.getInstance(context).getScreenWidth(); mScreenHeight = ScreenSizeUtils.getScreenHeight(context); } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); Int x = (int) event.getrawx (); int y = (int) event.getRawY(); switch (action) { case MotionEvent.ACTION_DOWN: isMove = false; break; case MotionEvent.ACTION_MOVE: isMove = true; int deltaX = x - mLastX; Int deltaY = y - mLastY; / / amount of y direction int translationX = (int) (ViewHelper. GetTranslationX + deltaX (this)); / / x direction translation deltaX int translationY = (int) (ViewHelper. GetTranslationY + deltaY (this)); / / y direction translation deltaY ViewHelper. SetTranslationX (this, translationX); ViewHelper.setTranslationY(this, translationY); break; Case motionEvent.action_UP: /** * If (! isMove) { if(onClickListener! =null){ onClickListener.onClick(this); } } setImageViewNearEdge(this); break; } // Update position mLastX = x; mLastY = y; return true; } private void setImageViewNearEdge(final View v) { ViewPropertyAnimator viewPropertyAnimator = ViewPropertyAnimator.animate(this); / / set the animation time viewPropertyAnimator. SetDuration (400); If (mLastX < (mScreenWidth / 2)) {// Set the displacement animation to move the control position to the left viewPropertyAnimator.translationX(-v.getLeft() + 15); } else {/ / set displacement viewPropertyAnimator animation mobile controls to the right position. The translationX (mScreenWidth - v.g etLeft () - v.g etWidth () - 15); } viewPropertyAnimator.start(); } @Override public void setOnClickListener(OnClickListener onClickListener) { this.onClickListener = onClickListener; }}Copy the code

By the way, if you want to use this View you need to import a three-party library NineOldAndroids

Android Library for using the Honeycomb (Android 3.0) Animation API on all versions of the platform back to 1.0! Animation prior to Honeycomb was very limited in what it could accomplish so in Android 3.x a new API was written. With only a change in imports, we are able to use a large subset of the new-style animation with exactly the same API.

I can use some animations in the lower version. I'm using the API wrapped around it.

The effect

I'm numb, and I have to deal with sliding up. Alas! We programmers can't say no!! Continue to transform

private void setImageViewNearEdge(final View v) { Log.e("TAG", "setImageViewNearEdge: " + mLastX + " " + mLastY); ViewPropertyAnimator viewPropertyAnimator = ViewPropertyAnimator.animate(this); / / set the animation time viewPropertyAnimator. SetDuration (400); If (mLastX < (mScreenWidth / 2)) {// Set the displacement animation to move the control position to the left viewPropertyAnimator.translationX(-v.getLeft() + 15); } else {/ / set displacement viewPropertyAnimator animation mobile controls to the right position. The translationX (mScreenWidth - v.g etLeft () - v.g etWidth () - 15); } /** * if the last position is more than one of the defined positions, we will move the Y axis */ log. e("TAG", "setImageViewNearEdge: "+getPaddingTop()); if (mLastY < getMeasuredHeight() + ImageUtil.dp2px(activity, 25)) {/ / the above plus the status bar. A highly viewPropertyAnimator translationY (- getTop () + getMeasuredHeight () / 2); If (mLastY > mscreenheight-getMeasuredheight ()) {if (mLastY > mscreenheight-getMeasuredheight ()) { viewPropertyAnimator.translationY(mScreenHeight - v.getTop() - v.getHeight() * 2); } viewPropertyAnimator.start(); }Copy the code

In fact, there are some minor flaws, because my judgment is to judge the position of your finger press, in fact, I think it is better to judge the center point of the view, but I am too bad, do not know how to calculate, if you know, please tell me in the comments section