前言
Andriod自定义view已经是现在非常广泛应用到技术了,能够实现很多实用功能,以及动画特效等等。今天主题就是通过继承view画一个矩形框,可以左右上下拉伸,也可以四个角拉伸,按着中间位置可随便移动位置。本文也是给自己做一个技术的保留,书写的也比较潦草,如果有什么不足之处,也希望能够指出,有什么疑问也欢迎在评论区评论。
目录
1.效果展示
说明:
看了上面效果展示有发现里面加了一些别的元素在里面,比如四个角的图标,内容填充,四个角的坐标,这些东西只是以我个人兴趣加上去的,会面去讲解,不需要可以去掉。
2.图解
- 由图可见我将整个view分为9个区域:1,2,3,4分别为左上角区域,右上角区域,左下角区域和右下角区域;5,6,7,8分别为上边框区域,左边框区域,右边框区域和下边框区域,还有个9中间填充区域。
- 1,2,3,4,5,6,7,8这个几块区域都属于空白区域,在view里面是有边距的,这几块区域主要作用是方便你手按在上面好判断你是按在哪一块区域要做某个操作,如果这几块区域范围太小,手就不容易按到你想要按的那个地方,假如你想按4执行右下角拉伸,结果因为4那个点太小了没按住按到8下边框上面了,那就只能上下拉伸了。9就是中间那一大片区域,主要作用按在9区域里面可以执行拖动,移动。
画前说明:
在画view前一定要有清楚地思路然后再去写,就拿这矩形来说,你要画它第一你要先确定它的开始位置是在哪,结束位置是在哪,还有他的边框是不是还要与其他相关联,手按在上面滑动时又是怎么改变矩形的边框的等等。
3.详解
- 实现自定义view先创建一个类 DragScaleView 继承view重写三个构造方法,这些地方就不细说了。
- 先做一些初始化的工作,准备一些需要的参数:
screenHeight和screenWidth初始化获取屏幕宽高,后面主要用作与点击移动view是确定布局位置 的,里面随便加进来了两个小图标(可以忽略)
offset定义一个偏移值;还有几个位运算TOP,LEFT,BOTTOM,RIGHT,LEFT_TOP,RIGHT_TOP, LEFT_BOTTOM,RIGHT_BOTTOM,CENTER分别代表上图几个区域5,6,8,7,1,2,3,4,9;还有两个 画笔borderPaint,fillPaint
3. 然后开始进入正题,画出矩形边框,使用准备好的画笔 borderPaint,在onDraw进行绘制
borderPaint.setColor(Color.parseColor("#00BCD4")); //设置画笔颜色
borderPaint.setStrokeWidth(4.0f); //设置画笔宽度
borderPaint.setStyle(Paint.Style.STROKE); // 设置画笔样式(描边)
//画矩形边框,前两个参数是起始位置,后两个位置是最终位置,getWidth和getHeight
是获取view宽和高
canvas.drawRect(offset, offset, getWidth() - offset, getHeight()
- offset, borderPaint);
drawRect是画矩形需要用到的,这个就不用详说了,主要说明一下里面需要参数,前两个是开始的位置,都知道view的最开始位置是在左上角坐标(0,0),也就是X轴坐标0,Y轴坐标0, 为什么要设置我一开始定义的偏移值offset,offset就是给每一个边框留25的距离空隙,假如offset定义25,就设置X轴和Y轴开始位置为(25,25)的地方,其实就是方便手按在上面进行判断,正好也确定了左上角的位置区域,左,上两个位置确定,然后是右边拿到view的宽度减去25就得到了右边的位置了,底部位置拿到view高度减去25,最后设置画笔,其实这样就已经可以确定四个角的位置,还有四个边的位置了,最后设置上画笔就能看到效果了。
然后下面就是画里面填充部分然后里面参数不用变,主要设置的是setStyle
//矩形填充
fillPaint.setColor(Color.parseColor("#7300BCD4"));
fillPaint.setStrokeWidth(4.0f);
fillPaint.setStyle(Paint.Style.FILL); // 设置样式(填充)
canvas.drawRect(offset, offset, getWidth() - offset, getHeight()
- offset, fillPaint);
画完后效果:旁边黑色边框不用在意,截图没截好
设置四个角图标就不解说了,看代码
4. 处理onTouch里面的事件,初始化位置参数。
/**
* 处理view事件,初始化位置
* @param v
* @param event
* @return
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
view = v;
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
oriLeft = v.getLeft(); // 初始化获取view左边框位置
oriRight = v.getRight(); // 初始化获取view右边框位置
oriTop = v.getTop(); // 初始化获取view上边框位置
oriBottom = v.getBottom(); // 初始化获取view下边框位置
//获取相对屏幕的X轴位置,和getX()区别在于getX()是获取相对view的X轴位置
lastX = (int) event.getRawX(); // 初始化点击在屏幕X位置
lastY = (int) event.getRawY(); // 初始化点击在屏幕Y位置
//获取点击view上某个位置,例如左边框,中间填充处,或四个角等
dragDirection = getDirection(v, (int) event.getX(),
(int) event.getY());
}
// 处理拖动事件
delDrag(v, event, action);
invalidate();
return false;
}
5. 当按下是判断按下的位置是在view的9个区域的某个位置,并返回。
这里说明一下 event.getX() 和 event.getRawX()区别:event.getX()是获取相对view的X轴位置,event.getRawX()是获取相对屏幕的X轴位置
//获取点击view上某个位置,例如边框,中间填充处,或四个角等
dragDirection = getDirection(v, (int) event.getX(),
(int) event.getY());
/**
* 获取触摸点flag
*
* @param v
* @param x 点击x轴位置,是在view上x轴位置
* @param y 点击y轴位置,是在view上y轴位置
* @return
*/
protected int getDirection(View v, int x, int y) {
int left = v.getLeft();
int right = v.getRight();
int bottom = v.getBottom();
int top = v.getTop();
Log.e("getDirection", "getDirection: right:"+right+"\nleft:"+left+"\nX:"+x );
//判断当前点击的位置,并返回对应位置
if (x < 50 && y < 50) {
//如果当前触摸在view身上的位置X,Y轴都小于50那此时点击在view位置就是在左上角的范围
return LEFT_TOP;
}
if (y < 50 && right - left - x < 50) {
return RIGHT_TOP;
}
if (x < 50 && bottom - top - y < 50) {
return LEFT_BOTTOM;
}
if (right - left - x < 50 && bottom - top - y < 50) {
return RIGHT_BOTTOM;
}
if (x < 50) {
return LEFT;
}
if (y < 50) {
return TOP;
}
if (right - left - x < 50) {
return RIGHT;
}
if (bottom - top - y < 50) {
return BOTTOM;
}
return CENTER;
}
详解:v.getLeft获取view左边相对于在父view的位置,getRight获取view右边框到父view左边缘的位置…,可以看下面图解。然后说一下逻辑,上图代码逻辑是通过判断点击的位置来确定当前点击在9个区哪个位置然后返回指定位置,获取view各边的位置,right - left就是计算出view的宽,bottom - top就是计算出view的高,其实宽和高直接就可以用getWidth和getHeight获取,获取宽或高以后减去手指点在view上的x或y轴位置,判断是不是在指定区域的范围内,返回指定位置,假如我定义左上角范围是25*25的范围,而我点击的位置是在view的(10,10)的坐标,也就是在view的X和Y为10位置就可以判断出当前点击的范围是在左上角然后就return LEFT_TOP,50是我确定那四个边框和四个角每一个都是50的范围,50我其实完全可以替换成offset*2,如左边框,手指点击在左边框的左边25范围和左边框的右边25的范围内,加起来就是50的范围。
关于位置的概念图:
注: getRight = getLeft + getWidth;
getBottom = getTop + getHeight;
6. 获取到触摸点以后进行判断做相应事件处理。
- 处理拖动事件,dragDirection是return的点击的位置
/**
* 处理拖动事件
*
* @param v
* @param event
* @param action
*/
protected void delDrag(View v, MotionEvent event, int action) {
switch (action) {
case MotionEvent.ACTION_MOVE:
// 计算出在屏幕移动距离,(移动的位置 - 按下时的位置)
int dx = (int) event.getRawX() - lastX;
int dy = (int) event.getRawY() - lastY;
switch (dragDirection) {
case LEFT: // 左边缘
left(v, dx);
break;
case RIGHT: // 右边缘
right(v, dx);
break;
case BOTTOM: // 下边缘
bottom(v, dy);
break;
case TOP: // 上边缘
top(v, dy);
break;
case CENTER: // 点击中心-->>移动
center(v, dx, dy);
break;
case LEFT_BOTTOM: // 左下
left(v, dx);
bottom(v, dy);
break;
case LEFT_TOP: // 左上
left(v, dx);
top(v, dy);
break;
case RIGHT_BOTTOM: // 右下
right(v, dx);
bottom(v, dy);
break;
case RIGHT_TOP: // 右上
right(v, dx);
top(v, dy);
break;
}
if (dragDirection != CENTER) {
v.layout(oriLeft, oriTop, oriRight, oriBottom);
}
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
dragDirection = 0;
break;
}
}
- 直接上全代码吧!代码里注解
4.全代码
/**
* 拖动缩放矩形框
*/
public class DragScaleView extends View implements View.OnTouchListener {
protected int screenWidth;
protected int screenHeight;
protected int lastX;
protected int lastY;
private int oriLeft;
private int oriRight;
private int oriTop;
private int oriBottom;
private int dragDirection;
private static final int TOP = 0x15;
private static final int LEFT = 0x16;
private static final int BOTTOM = 0x17;
private static final int RIGHT = 0x18;
private static final int LEFT_TOP = 0x11;
private static final int RIGHT_TOP = 0x12;
private static final int LEFT_BOTTOM = 0x13;
private static final int RIGHT_BOTTOM = 0x14;
private static final int CENTER = 0x19;
private int offset = 25;
protected Paint borderPaint = new Paint(); //画边框用
protected Paint fillPaint = new Paint(); //填充用
//画四角图标
private Rect leftTopRect;
private Rect rightTopRect;
private Drawable icLeftTop;
private Drawable icRightTop;
private Rect leftBottomRect;
private Rect rightBottomRect;
private View view;
/**
* 初始化获取屏幕宽高
*/
protected void initScreenW_H() {
screenHeight = getResources().getDisplayMetrics().heightPixels - 50;
screenWidth = getResources().getDisplayMetrics().widthPixels;
//获取图标drawable
icLeftTop = ContextCompat.getDrawable(getContext(), R.drawable.ic_left_top_rect);
icRightTop = ContextCompat.getDrawable(getContext(), R.drawable.ic_right_top_rect);
}
public DragScaleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setOnTouchListener(this);
initScreenW_H();
}
public DragScaleView(Context context, AttributeSet attrs) {
super(context, attrs);
setOnTouchListener(this);
initScreenW_H();
}
public DragScaleView(Context context) {
super(context);
setOnTouchListener(this);
initScreenW_H();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
borderPaint.setColor(Color.RED);
borderPaint.setStrokeWidth(4.0f);
borderPaint.setStyle(Paint.Style.STROKE);
//画矩形边框
canvas.drawRect(offset, offset, getWidth() - offset, getHeight()
- offset, borderPaint);
//矩形填充
fillPaint.setColor(getResources().getColor(R.color.drag_scale_fill));
fillPaint.setStrokeWidth(4.0f);
fillPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(offset, offset, getWidth() - offset, getHeight()
- offset, fillPaint);
//绘制图标
//左上角
icLeftTop.setBounds(leftTopRect);
icLeftTop.draw(canvas);
//右上角
icRightTop.setBounds(rightTopRect);
icRightTop.draw(canvas);
//左下角
icRightTop.setBounds(leftBottomRect);
icRightTop.draw(canvas);
//右下角
icLeftTop.setBounds(rightBottomRect);
icLeftTop.draw(canvas);
//主要防止第一次进入没有触碰view就获取坐标位置报null问题
if (view == null) {
view = this;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//左上角图标
leftTopRect = new Rect(0, 0, 2 * offset, 2 * offset);
//右上角图标
rightTopRect = new Rect(getWidth() - offset * 2, 0, getWidth(), 2 * offset);
//左下角
leftBottomRect = new Rect(0, getHeight() - offset * 2, offset * 2, getHeight());
//右下角
rightBottomRect = new Rect(getWidth() - offset * 2, getHeight() - offset * 2, getWidth(), getHeight());
}
/**
* 处理view事件,初始化位置
* @param v
* @param event
* @return
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
view = v;
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
oriLeft = v.getLeft(); // 初始化获取view左边框位置
oriRight = v.getRight(); // 初始化获取view右边框位置
oriTop = v.getTop(); // 初始化获取view上边框位置
oriBottom = v.getBottom(); // 初始化获取view下边框位置
//获取相对屏幕的X轴位置,和getX()区别在于getX()是获取相对view的X轴位置
lastX = (int) event.getRawX(); // 初始化点击在屏幕X位置
lastY = (int) event.getRawY(); // 初始化点击在屏幕Y位置
//获取点击view上某个位置,例如左边框,中间填充处,或四个角等
dragDirection = getDirection(v, (int) event.getX(),
(int) event.getY());
}
// 处理拖动事件
delDrag(v, event, action);
invalidate();
return false;
}
/**
* 处理拖动事件
*
* @param v
* @param event
* @param action
*/
protected void delDrag(View v, MotionEvent event, int action) {
switch (action) {
case MotionEvent.ACTION_MOVE:
// 计算出在屏幕移动距离,(移动的位置 - 按下时的位置)
int dx = (int) event.getRawX() - lastX;
int dy = (int) event.getRawY() - lastY;
switch (dragDirection) {
case LEFT: // 左边缘
left(v, dx);
break;
case RIGHT: // 右边缘
right(v, dx);
break;
case BOTTOM: // 下边缘
bottom(v, dy);
break;
case TOP: // 上边缘
top(v, dy);
break;
case CENTER: // 点击中心-->>移动
center(v, dx, dy);
break;
case LEFT_BOTTOM: // 左下
left(v, dx);
bottom(v, dy);
break;
case LEFT_TOP: // 左上
left(v, dx);
top(v, dy);
break;
case RIGHT_BOTTOM: // 右下
right(v, dx);
bottom(v, dy);
break;
case RIGHT_TOP: // 右上
right(v, dx);
top(v, dy);
break;
}
if (dragDirection != CENTER) {
v.layout(oriLeft, oriTop, oriRight, oriBottom);
}
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
dragDirection = 0;
break;
}
}
/**
* 触摸点为中心->>移动
*
* @param v
* @param dx 计算出移动后距离(x轴)
* @param dy 计算出移动后距离(y轴)
*/
private void center(View v, int dx, int dy) {
// 算出各个边框最终位置
int left = v.getLeft() + dx;
int top = v.getTop() + dy;
int right = v.getRight() + dx;
int bottom = v.getBottom() + dy;
// 判断移动到最左边时做响应处理
if (left < -offset) {
left = -offset;
right = left + v.getWidth();
}
// 判断移动到最右边做响应处理
if (right > screenWidth + offset) {
right = screenWidth + offset;
left = right - v.getWidth();
}
// 判断移动到最上边做响应处理
if (top < -offset) {
top = -offset;
bottom = top + v.getHeight();
}
// 判断移动到最下边做响应处理
if (bottom > screenHeight + offset) {
bottom = screenHeight + offset;
top = bottom - v.getHeight();
}
// 通过计算最终位置设置布局
v.layout(left, top, right, bottom);
}
/**
* 触摸点为上边缘
*
* @param v
* @param dy
*/
private void top(View v, int dy) {
oriTop += dy;
if (oriTop < -offset) {
oriTop = -offset;
}
if (oriBottom - oriTop - 2 * offset < 100) {
oriTop = oriBottom - 2 * offset - 100;
}
}
/**
* 触摸点为下边缘
*
* @param v
* @param dy
*/
private void bottom(View v, int dy) {
oriBottom += dy;
if (oriBottom > screenHeight + offset) {
oriBottom = screenHeight + offset;
}
if (oriBottom - oriTop - 2 * offset < 100) {
oriBottom = 100 + oriTop + 2 * offset;
}
}
/**
* 触摸点为右边缘
*
* @param v
* @param dx
*/
private void right(View v, int dx) {
oriRight += dx;
if (oriRight > screenWidth + offset) {
oriRight = screenWidth + offset;
}
if (oriRight - oriLeft - 2 * offset < 100) {
oriRight = oriLeft + 2 * offset + 100;
}
}
/**
* 触摸点为左边缘
*
* @param v
* @param dx 计算出移动后距离(x轴)
*/
private void left(View v, int dx) {
// 算出最终位置(left边框最终位置)
oriLeft += dx;
if (oriLeft < -offset) {
oriLeft = -offset;
}
if (oriRight - oriLeft - 2 * offset < 100) {
oriLeft = oriRight - 2 * offset - 100;
}
}
/**
* 获取触摸点flag
*
* @param v
* @param x 点击x轴位置,是在view上x轴位置
* @param y 点击y轴位置,是在view上y轴位置
* @return
*/
protected int getDirection(View v, int x, int y) {
int left = v.getLeft();
int right = v.getRight();
int bottom = v.getBottom();
int top = v.getTop();
//判断当前点击的位置,并返回对应位置
if (x < 50 && y < 50) {
//如果当前触摸在view身上的位置X,Y轴都小于50那此时点击在view位置就是在左上角的范围
return LEFT_TOP;
}
if (y < 50 && right - left - x < 50) {
return RIGHT_TOP;
}
if (x < 50 && bottom - top - y < 50) {
return LEFT_BOTTOM;
}
if (right - left - x < 50 && bottom - top - y < 50) {
return RIGHT_BOTTOM;
}
if (x < 50) {
return LEFT;
}
if (y < 50) {
return TOP;
}
if (right - left - x < 50) {
return RIGHT;
}
if (bottom - top - y < 50) {
return BOTTOM;
}
return CENTER;
}
/**
* 获取截取宽度
*
* @return
*/
public int getCutWidth() {
return getWidth() - 2 * offset;
}
/**
* 获取截取高度
*
* @return
*/
public int getCutHeight() {
return getHeight() - 2 * offset;
}
/**
* 获取左上角坐标位置
*/
public int[] getLeftTopLocation() {
if (view != null) {
int x = view.getLeft() + offset;
int y = view.getTop() + offset;
Log.e("DragScaleView", "getLeftTopLocation: " + x + "\n" + y);
return new int[]{x, y};
} else {
return null;
}
}
/**
* 获取右上角坐标位置
*/
public int[] getRightTopLocation() {
if (view != null) {
int x = view.getLeft() + getWidth() - offset;
int y = view.getTop() + offset;
Log.e("DragScaleView", "getLeftTopLocation: " + x + "\n" + y);
return new int[]{x, y};
} else {
return null;
}
}
/**
* 获取左下角坐标位置
*/
public int[] getLeftBottomLocation() {
if (view != null) {
int x = view.getLeft() + offset;
int y = view.getTop() + getHeight() - offset;
return new int[]{x, y};
} else {
return null;
}
}
/**
* 获取右下角坐标位置
*/
public int[] getRightBottomLocation() {
if (view != null) {
int x = view.getLeft() + getWidth() - offset;
int y = view.getTop() + getHeight() - offset;
return new int[]{x, y};
} else {
return null;
}
}
/**
* 返回view是否为空,确认当前view是否初始化
* @return
*/
public boolean isNotNullView() {
if (view != null) {
return true;
} else {
return false;
}
}
}
使用直接在布局里面定义就行,你们也可以在里面自己添加自定义属性,或其他想添加的东西。