Android 自定义Layout 实现ChildView的动态拖拽位置

效果演示

Android 自定义Layout 实现ChildView的动态拖拽位置_第1张图片
演示

关键代码

  1. 创建一个DynamicLayout继承ViewGroup

  2. 实现onMeasure方法

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //测量自己
        int maxWidth = 0;
        int maxHeight = 0;
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            }
        }
        setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), resolveSize(maxHeight, heightMeasureSpec));

        //测量childView
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            final int childWidthMeasureSpec;
            if (lp.width == LayoutParams.MATCH_PARENT) {
                final int width = Math.max(0, getMeasuredWidth() - lp.leftMargin - lp.rightMargin);
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, lp.leftMargin + lp.rightMargin, lp.width);
            }

            final int childHeightMeasureSpec;
            if (lp.height == LayoutParams.MATCH_PARENT) {
                final int height = Math.max(0, getMeasuredHeight() - lp.topMargin - lp.bottomMargin);
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
            } else {
                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, lp.topMargin + lp.bottomMargin, lp.height);
            }
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
  1. 实现onLayout方法
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft = lp.leftMargin;
                int childTop = lp.topMargin;

                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
  1. 拦截TouchEvent事件
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return true;
    }
  1. 实现onTouchEvent方法
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent: " + event.getAction());

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //处理触摸按下事件
                handleTouchDown(event);
                break;
            case MotionEvent.ACTION_MOVE:
                //处理触摸移动事件
                handleTouchMove(event);
                break;
        }

        return true;
    }

    private void handleTouchMove(MotionEvent event) {
        if (mSelectedChild == null) {
            return;
        }

        float x = event.getX();
        float y = event.getY();

        MarginLayoutParams lp = (MarginLayoutParams) mSelectedChild.getLayoutParams();
        //计算view新的x轴位置并且防止超出左右边界
        int childX = (int) (x - mTouchOffsetX);
        if (childX < 0) {
            lp.leftMargin = 0;
        } else {
            int maxX = getMeasuredWidth() - mSelectedChild.getMeasuredWidth();
            lp.leftMargin = childX > maxX ? maxX : childX;
        }
        //计算view新的y轴位置并且防止超出上下边界
        int childY = (int) (y - mTouchOffsetY);
        if (childY < 0) {
            lp.topMargin = 0;
        } else {
            int maxY = getMeasuredHeight() - mSelectedChild.getMeasuredHeight();
            lp.topMargin = childY > maxY ? maxY : childY;
        }
        //重新布局 刷新位置
        requestLayout();
    }

    private void handleTouchDown(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        setViewTranslationZ(0);
        mSelectedChild = null;
        int count = getChildCount();
        for (int i = count - 1; i >= 0; i--) {
            View child = getChildAt(i);
            //获取View上下左右的坐标
            Rect rect = getChildRect(child);
            //通过判断按下位置是否在View之内进行选中View
            if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
                //将选中View存为成员变量供Move事件使用
                mSelectedChild = child;
                setViewTranslationZ(mTranslationZ);
                //计算出按下位置相对于选中View的坐标并存为成员变量供Move事件使用
                mTouchOffsetX = x - rect.left;
                mTouchOffsetY = y - rect.top;
                break;
            }
        }
    }

    //获取View的上下左右坐标
    private Rect getChildRect(View child) {
        MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        return new Rect(lp.leftMargin, lp.topMargin, lp.leftMargin + child.getMeasuredWidth(), lp.topMargin + child.getMeasuredHeight());
    }

Github

  • DynamicLayoutSample

你可能感兴趣的:(Android 自定义Layout 实现ChildView的动态拖拽位置)