Android拖动,缩放,自定义内容,控件制作(可拖动缩放RelativeLayout定制)

先上效果图:



一.     制作此控件的起源

项目需要一个可以拖动的控件,在网上可以找到很多例子,有图片拖动控件,有textview拖动控件。但是项目中需要控件同时可以动态通过手指调整尺寸,并且控件的内容不固定,需要自定义内容,即可以添加任意内容到拖动控件内。因此,编写此控件。

二.     根据需求做技术分析

1.     可拖动+调整尺寸:view的(scrollTo、scrollBy),设置LayoutParams,覆盖layout方法

2.     自定义内容:需要自定义的控件内存放其他控件,则需要自定义控件继承ViewGroup(LinearLayout、ReletiveLayout)

三.     Android自定义控件所需基础知识

a 位置坐标:

屏幕左上角是坐标原点(0,0),原点向右延伸是x正轴方向,原点向下延伸是y轴正方向

自定义控件的坐标位置是相对于父控件的:getTop()、getBottom(),getLeft(),getRight(),这几个函数用于获取自定义view在父布局坐标系的位置。

b  触摸感知

继承onTouchEvent,获取用户对自定义控件的触摸事件(down,move,up)

根据触摸的位置event.getX(),event.getY(),以及其他位置,判断要执行的的操作。包括根据位移移动,根据位移缩放。根据位移判断是否到达边界。

c 自定义控件父类选择

由于需求中控件里面的内容不定,即可以动态添加任意类型的Android控件到自定义的控件里面,因此这个自定义控件不能通过继承View实现,需要继承ViewGroup来实现,为了使用一些布局功能,最后项目选定继承ViewGroup的子类RelativeLayout,以实现动态添加任意多个任意类型的View控件。

c  移动控件

上文提到移动控件的三个方法:view的(scrollTo、scrollBy), 设置LayoutParams,覆盖layout方法。

Layout:我测试过layout移动控件,是可以达到移动控件的目的,但是破坏了控件尺寸计算路径,(本来是onmeasure之后,onlayout,由于我是自定义viewGroup现在是直接改动了layout,导致控件尺寸错乱,最后造成ViewGroup里面的内容显示不完整),layout介入了view/ViewGroup的地层绘制过程,造成混乱。

LayoutParams:这个参数一般是用于Android的xml布局文件里面,比如:layoutout_height=”” , layout_width=”” ,layout_marginLeft=””,layout_marginTop=””

假定在一个LinearLayout里面放一个imageView,通过修改ImageView的这几个参数就可以让ImageView在LinearLayout里面自由的移动位置:如图:

对自定义控件的位置设置转化为:(marginLeft,marginTop,width,height)

其中,marginLeft和marginTop负责确定控件的位置,width和height确定控件的大小,(可以看着图按自己的方式理解),总之是,通过这几参数的修改,可以使得控件在LinearLayout或RelativeLayout布局内自由的移动并且变换大小。

四.     可移动控件代码编写

原理都写清楚了,开始编写代码,定义一个类继承RelativeLayout,覆盖,onTouchEvent,然后编写逻辑代码:核心代码如下(都粘贴上看着心累):

public class MoveLayout extends RelativeLayout {

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                oriLeft = getLeft();
                oriRight = getRight();
                oriTop = getTop();
                oriBottom = getBottom();

                lastY = (int) event.getRawY();
                lastX = (int) event.getRawX();
                dragDirection = getDirection((int) event.getX(), (int) event.getY());
                break;
            case MotionEvent.ACTION_UP:
                             break;
            case MotionEvent.ACTION_MOVE:
                int tempRawX = (int)event.getRawX();
                int tempRawY = (int)event.getRawY();

                int dx = tempRawX - lastX;
                int dy = tempRawY - lastY;
                lastX = tempRawX;
                lastY = tempRawY;

                switch (dragDirection) {
                    case LEFT:
                        left( dx);
                        break;
                    case RIGHT:
                        right( dx);
                        break;
                    case BOTTOM:
                        bottom(dy);
                        break;
                    case TOP:
                        top( dy);
                        break;
                    case CENTER:
                        center( dx, dy);
                        break;
                }

                //把新的位置 oriLeft, oriTop, oriRight, oriBottom设置到控件,实现位置移动和大小变化。
                RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(oriRight - oriLeft, oriBottom - oriTop);
                lp.setMargins(oriLeft,oriTop,0,0);
                setLayoutParams(lp);
                           break;
        }
        return super.onTouchEvent(event);
    }
/**
 * 触摸点为中心->>移动
 */
private void center(int dx, int dy) {
    int left = getLeft() + dx;
    int top = getTop() + dy;
    int right = getRight() + dx;
    int bottom = getBottom() + dy;

    if (left < 0) {
        left = 0;
        right = left + getWidth();
    }
    if (right > screenWidth ) {
        right = screenWidth ;
        left = right - getWidth();
    }
    if (top < 0) {
        top = 0;
        bottom = top + getHeight();
    }
    if (bottom > screenHeight ) {
        bottom = screenHeight ;
        top = bottom - getHeight();
    }

    oriLeft = left;
    oriTop = top;
    oriRight = right;
    oriBottom = bottom;
}
}

五.     控件管理代码

以上实现了一个拖动和改变大小的控件,其实就是实现了一个定制的RelativeLayout,定制的RelativeLayout可以被拖动,和改变大小。因此可以在MoveLayout内添加任意view实现自己的显示效果。

 

现在为了能在一个布局上动态的增加很多个可移动的控件,并且对这些控件做管理功能,(动态增加、动态删除)

为了实现这个功能,又自定义了一个RelativeLayout来放置多个MoveLayout,动态增加,删除。新自定义的RelativeLayout叫做:DragView:简略代码如下:

public class DragView extends RelativeLayout implements MoveLayout.DeleteMoveLayout{

    public DragView(Context context) {
        super(context);
        init(context, this);
    }

    private void init(Context c, DragView thisp) {
        mContext = c;
        mMoveLayoutList = new ArrayList<>();
    }
/*
*通过ondraw获取DragView的实际大小,然后告诉所有被管理的MoveLayout
*/
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
      //  Log.e(TAG, "onDraw: height=" + getHeight());
        mSelfViewWidth = getWidth();
        mSelfViewHeight = getHeight();

        if (mMoveLayoutList != null) {
            int count = mMoveLayoutList.size();
            for (int a = 0; a < count; a ++) {
                mMoveLayoutList.get(a).setViewWidthHeight(mSelfViewWidth, mSelfViewHeight);
                mMoveLayoutList.get(a).setDeleteWidthHeight(DELETE_AREA_WIDTH, DELETE_AREA_HEIGHT);
            }
        }
    }

//添加新的MoveLayout,并在里面放置自己的控件selfView,同时制定位置和尺寸
    public void addDragView(View selfView, int left, int top ,int right, int bottom, boolean isFixedSize, boolean whitebg) {
        MoveLayout moveLayout = new MoveLayout(mContext);
        moveLayout.setClickable(true);
        moveLayout.setMinHeight(mMinHeight);
        moveLayout.setMinWidth(mMinWidth);
        int tempWidth = right - left;
        int tempHeight = bottom - top;
        if (tempWidth < mMinWidth) tempWidth = mMinWidth;
        if (tempHeight < mMinHeight) tempHeight = mMinHeight;

        //set postion
        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(tempWidth, tempHeight);
        lp.setMargins(left,top,0,0);
        moveLayout.setLayoutParams(lp);

        //add sub view (has click indicator)
        LayoutInflater inflater = LayoutInflater.from(mContext);
        View dragSubView = inflater.inflate(R.layout.drag_sub_view, null);
        LinearLayout addYourViewHere = (LinearLayout) dragSubView.findViewById(R.id.add_your_view_here);
        LinearLayout.LayoutParams lv = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        addYourViewHere.addView(selfView, lv);

        moveLayout.addView(dragSubView);
        //set fixed size
        moveLayout.setFixedSize(isFixedSize);

        addView(moveLayout);
        mMoveLayoutList.add(moveLayout);
    }
//添加新的MoveLayout,并在里面放置自己的控件selfView,同时制定位置和尺寸
    public void addDragView(int resId, int left, int top ,int right, int bottom, boolean isFixedSize, boolean whitebg) {
        LayoutInflater inflater2 = LayoutInflater.from(mContext);
        View selfView = inflater2.inflate(resId, null);
        addDragView(selfView, left, top , right, bottom, isFixedSize, whitebg);
    }

    @Override
    public void onDeleteMoveLayout(int identity) {
        int count = mMoveLayoutList.size();
        for (int a = 0; a < count; a ++) {
            if (mMoveLayoutList.get(a).getIdentity() == identity) {
                //delete
                removeView(mMoveLayoutList.get(a));
            }
        }
    }

}

六.     在自己项目里加入方法推荐

        本控件实现上非常简单,当然也稳定,并且显示控件定制化要求高,因此建议直接复制类到自己的工程里,并进行个性化的修改,因此这个控件没有做成库文件。整个控件的实现和调用工程。

自定义控件包括:MoveLayout.java,DragView,drag_sub_view.xml,corners_bg.xml,corners_bg2.xml,spot_corners_bg.xml

七.     下载

Github 下载链接
CSDN 下载链接


你可能感兴趣的:(Android)