先上效果图:
一. 制作此控件的起源
项目需要一个可以拖动的控件,在网上可以找到很多例子,有图片拖动控件,有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
七. 下载