先看下實(shí)現(xiàn)效果
對于自定義view不規(guī)則區(qū)域的觸摸事件點(diǎn)擊響應(yīng)涉及的知識點(diǎn)
1.Region
2.Matrix坐標(biāo)系變換
拓展知識點(diǎn):3.Path繪圖
Region
Region是繪制中的區(qū)域页滚,是一個(gè)閉合的區(qū)域。使用Region可以取區(qū)域的交集或者并集最后得到我們的不規(guī)則區(qū)域薯鼠。
Region的構(gòu)造方法有
public Region()
public Region(Region region)
public Region(Rect r)
public Region(int left, int top, int right, int bottom)
Region也可以后期添加區(qū)域
public void setEmpty() //清空
public boolean set(Region region)
public boolean set(Rect r)
public boolean set(int left, int top, int right, int bottom)
public boolean setPath(Path path, Region clip)//將path和clip的兩個(gè)區(qū)域取交集
對于上述的不規(guī)則區(qū)域,我們可以取一張屏幕畫布和該不規(guī)則區(qū)域的path取交集,這樣就能得到我們想要獲得的區(qū)域了。
circleRegion = new Region();
path = new Path();
//Path.Direction.CW---順時(shí)針 Path.Direction.CCW逆時(shí)針
path.addCircle(width / 2, height / 2, 250, Path.Direction.CW);
Region region = new Region(0, 0, width, height);
circleRegion.setPath(path, region);
可以使用
//繪制
RegionIterator iterator = new RegionIterator(region);
Rect rect = new Rect();
while (iterator.next(rect)) {
canvas.drawRect(rect, mPaint);
}
遍歷我們得到的region區(qū)域囚衔。
知道了region概念后锨亏,我們可以對一個(gè)圓形圖案的觸摸坐標(biāo)判斷是否在圓形區(qū)域內(nèi)。
public class AbsoluteMap extends View {
private Paint paint;
private int width, height;
private Path path;
private Region circleRegion;
public AbsoluteMap(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10);
circleRegion = new Region();
path = new Path();
//Path.Direction.CW---順時(shí)針 Path.Direction.CCW逆時(shí)針
path.addCircle(width / 2, height / 2, 250, Path.Direction.CW);
Region region = new Region(0, 0, width, height);
circleRegion.setPath(path, region);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
init();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(width / 2, height / 2, 250, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (circleRegion.contains((int) event.getX(), (int) event.getY())) {
Toast.makeText(getContext(), "觸摸區(qū)域內(nèi)", Toast.LENGTH_LONG).show();
}
break;
}
return true;
}
}
可以看到雏婶,我們?nèi)〕鰣A形region后绘闷,再對用戶的觸摸坐標(biāo)getx,gety判斷是否在區(qū)域內(nèi)即可橡庞。
2.Matrix
從第一個(gè)例子可以看到我們用的是相對坐標(biāo)系getx,gety來確定用戶的觸摸區(qū)域的,這樣就存在一個(gè)問題印蔗,有時(shí)候開發(fā)者為了方便或者繪制的圖形是對稱圖形時(shí)扒最,我們會將坐標(biāo)系平移,做出變換华嘹,這時(shí)候getx,gety就會發(fā)生變化吧趣。
所以一般我們會使用getRawX,getRawY來獲取用戶坐標(biāo),但是怎么把用戶的絕對坐標(biāo)轉(zhuǎn)換成我們的畫布坐標(biāo)呢耙厚?
這里就需要了解一下Matrix了强挫。
mapPoints mapRect mapVectors
這些API主要是根據(jù)當(dāng)前Matrix矩陣對點(diǎn)、矩形區(qū)域和向量進(jìn)行變換薛躬,以得到變換后的點(diǎn)俯渤、矩形區(qū)域和向量。經(jīng)常和下面的invert方法結(jié)合使用型宝。
invert
通過上面的mapXXX方法八匠,可以獲取變換后的坐標(biāo)或者矩形絮爷。但假設(shè)我們知道了變換后的坐標(biāo),如何計(jì)算Matrix變換前的坐標(biāo)那梨树?坑夯!
此時(shí)通過invert方法獲取的逆矩陣就派上用場了。所謂逆矩陣劝萤,就是Matrix旋轉(zhuǎn)了30度渊涝,逆Matrix就反向旋轉(zhuǎn)30度慎璧,Matrix放大n倍床嫌,逆Matrix就縮小n倍。
public class ChangeMap extends View {
private Paint paint;
private int width, height;
private Path path;
private Region circleRegion;
private Matrix matrix;
private float[] touchPoints = new float[2];
public ChangeMap(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10);
circleRegion = new Region();
path = new Path();
//Path.Direction.CW---順時(shí)針 Path.Direction.CCW逆時(shí)針
path.addCircle(0, 0, 250, Path.Direction.CW);
Region region = new Region(-width / 2, -height / 2, width / 2, height / 2);
circleRegion.setPath(path, region);
matrix = new Matrix();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
init();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(width / 2, height / 2);
matrix.reset();
if (matrix.isIdentity()) {
canvas.getMatrix().invert(matrix);
}
canvas.drawPath(path, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchPoints[0] = event.getRawX();
touchPoints[1] = event.getRawY();
matrix.mapPoints(touchPoints);
if (circleRegion.contains((int) touchPoints[0], (int) touchPoints[1])) {
Toast.makeText(getContext(), "觸摸區(qū)域內(nèi)", Toast.LENGTH_LONG).show();
}
break;
}
return true;
}
}
明白了基本原理以后胸私,我們就可以對復(fù)雜的不規(guī)則區(qū)域進(jìn)行點(diǎn)擊事件的判斷了厌处。
繪制第一張的圖時(shí),主要用到的就是path工具類岁疼。
public class SpecialMap extends View {
private Paint paint;
private int width, height;
private Region topRegion, leftRegion, rightRegion, bottomRegion, totalRegion, centerRegion;
private Matrix matrix;
private RectF rectFBig, rectFSmall;
private Path leftPath, topPath, rightPath, bottomPath, centerPath;
private float[] touchPoints = new float[2];
public SpecialMap(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.LTGRAY);
paint.setStrokeWidth(10);
topRegion = new Region();
leftRegion = new Region();
rightRegion = new Region();
bottomRegion = new Region();
totalRegion = new Region();
centerRegion = new Region();
rectFBig = new RectF(-400, -400, 400, 400);
rectFSmall = new RectF(-250, -250, 250, 250);
totalRegion = new Region(-width / 2, -height / 2, width / 2, height / 2);
leftPath = new Path();
topPath = new Path();
rightPath = new Path();
bottomPath = new Path();
centerPath = new Path();
int sweepAngle = 80;
/**
* startAngle是開始度數(shù)
* sweepAngle指的是旋轉(zhuǎn)的度數(shù)阔涉,也就是以startAngle開始,旋轉(zhuǎn)多少度捷绒,如果sweepAngle是正數(shù)瑰排,那么就是按順時(shí)針方向旋轉(zhuǎn),如果是負(fù)數(shù)就是按逆時(shí)針方向旋轉(zhuǎn)暖侨。
*/
rightPath.addArc(rectFBig, 5, sweepAngle);
/**
* arcTo是先畫一個(gè)橢圓椭住,然后再在這個(gè)橢圓上面截取一部分部形。這個(gè)圖形自然就是一個(gè)弧線了
*/
rightPath.arcTo(rectFSmall, 5 + sweepAngle, -sweepAngle);
rightPath.close();
bottomPath.addArc(rectFBig, 95, sweepAngle);
bottomPath.arcTo(rectFSmall, 95 + sweepAngle, -sweepAngle);
bottomPath.close();
leftPath.addArc(rectFBig, 185, sweepAngle);
leftPath.arcTo(rectFSmall, 185 + sweepAngle, -sweepAngle);
leftPath.close();
topPath.addArc(rectFBig, 275, sweepAngle);
topPath.arcTo(rectFSmall, 275 + sweepAngle, -sweepAngle);
topPath.close();
centerPath.addCircle(0, 0, 120, Path.Direction.CW);
matrix = new Matrix();
leftRegion.setPath(leftPath, totalRegion);
topRegion.setPath(topPath, totalRegion);
rightRegion.setPath(rightPath, totalRegion);
bottomRegion.setPath(bottomPath, totalRegion);
centerRegion.setPath(centerPath, totalRegion);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
init();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(width / 2, height / 2);
matrix.reset();
if (matrix.isIdentity()) {
canvas.getMatrix().invert(matrix);
}
canvas.drawPath(leftPath, paint);
canvas.drawPath(bottomPath, paint);
canvas.drawPath(rightPath, paint);
canvas.drawPath(topPath, paint);
canvas.drawPath(centerPath, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchPoints[0] = event.getRawX();
touchPoints[1] = event.getRawY();
matrix.mapPoints(touchPoints);
if (topRegion.contains((int) touchPoints[0], (int) touchPoints[1])) {
Toast.makeText(getContext(), "點(diǎn)擊了右上部", Toast.LENGTH_LONG).show();
} else if (leftRegion.contains((int) touchPoints[0], (int) touchPoints[1])) {
Toast.makeText(getContext(), "點(diǎn)擊了左上部", Toast.LENGTH_LONG).show();
} else if (bottomRegion.contains((int) touchPoints[0], (int) touchPoints[1])) {
Toast.makeText(getContext(), "點(diǎn)擊了左下部", Toast.LENGTH_LONG).show();
} else if (rightRegion.contains((int) touchPoints[0], (int) touchPoints[1])) {
Toast.makeText(getContext(), "點(diǎn)擊了右下部", Toast.LENGTH_LONG).show();
} else if (centerRegion.contains((int) touchPoints[0], (int) touchPoints[1])) {
Toast.makeText(getContext(), "點(diǎn)擊了中部", Toast.LENGTH_LONG).show();
}
break;
}
return true;
}
}