Android自定义view之游戏摇杆
2020-08-01 本文已影响0人
奔跑吧李博
国际惯例,先贴效果:
实现步骤:
1.绘制外圆和内圆
2.在view中监听事件,按下和移动实时更新小圆的位置,抬起恢复小圆到中心。
用代码实现思路:
首先一览view中用到的属性。
private Paint outerCirclePaint;
private Paint innerCirclePaint;
/** 内圆中心x坐标 */
private double innerCenterX;
/** 内圆中心y坐标 */
private double innerCenterY;
/** view中心点x坐标 */
private float viewCenterX;
/** view中心点y左边 */
private float viewCenterY;
/** view宽高大小,设定宽高相等 */
private int size;
/** 外圆半径 */
private int outerCircleRadius;
/** 内圆半径 */
private int innerCircleRadius;
核心是处理手势滑动更新内圆圆心的坐标。我们将手势滑动区域分为自由域和非自由域,自由域为内圆不超出外圆,即在外圆半径减内圆半径的范围之内。自由域里面,内圆圆心随触摸点随意更改。而超出自由域了,内圆就不能再往外移动,只能在外圆内部随角度旋转。
监听手势处理滑动事件
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handleEvent(event);
break;
case MotionEvent.ACTION_MOVE:
handleEvent(event);
break;
case MotionEvent.ACTION_UP:
restorePosition();
break;
}
return true;
}
判断触摸点与中心点距离,小于自由域半径就随即更新内圆中心,否则额外处理。
private void handleEvent(MotionEvent event) {
double distance = Math.sqrt(Math.pow(event.getX()-viewCenterX, 2) + Math.pow(event.getY()-viewCenterY, 2)); //触摸点与view中心距离
if (distance < outerCircleRadius-innerCircleRadius) {
//在自由域之内,触摸点实时作为内圆圆心
innerCenterX = event.getX();
innerCenterY = event.getY();
invalidate();
} else {
//在自由域之外,内圆圆心在触摸点与外圆圆心的线段上
updateInnerCircelCenter(event);
}
}
判断手势超出自由域,需要用到一点点数字知识,这里是我做时画的一张手稿,难看请轻喷。
A点为当前手势点,在自由域之外,B点为内圆最远圆心,A、B与x轴垂线形成2个相似三角形。通过求OA,OB距离,并且知道A点坐标,通过等比求出B点坐标。
代码实现:
private void updateInnerCircelCenter(MotionEvent event) {
double distance = Math.sqrt(Math.pow(event.getX()-viewCenterX, 2) + Math.pow(event.getY()-viewCenterY, 2)); //当前触摸点到圆心的距离
int innerDistance = outerCircleRadius-innerCircleRadius; //内圆圆心到中心点距离
//相似三角形的性质,两个相似三角形各边比例相等得到等式
innerCenterX = (event.getX()-viewCenterX)*innerDistance/distance + viewCenterX;
innerCenterY = (event.getY()-viewCenterY)*innerDistance/distance + viewCenterY;
invalidate();
}
完整代码如下:
/**
* create by libo
* create on 2020/7/30
* description 手机方向键手柄view
*/
public class GameRockerView extends View {
private Paint outerCirclePaint;
private Paint innerCirclePaint;
/** 内圆中心x坐标 */
private double innerCenterX;
/** 内圆中心y坐标 */
private double innerCenterY;
/** view中心点x坐标 */
private float viewCenterX;
/** view中心点y左边 */
private float viewCenterY;
/** view宽高大小,设定宽高相等 */
private int size;
/** 外圆半径 */
private int outerCircleRadius;
/** 内圆半径 */
private int innerCircleRadius;
public GameRockerView(Context context) {
super(context);
init();
}
public GameRockerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
outerCirclePaint = new Paint();
outerCirclePaint.setColor(getResources().getColor(R.color.green));
outerCirclePaint.setAntiAlias(true);
outerCirclePaint.setMaskFilter(new BlurMaskFilter(50, BlurMaskFilter.Blur.INNER));
innerCirclePaint = new Paint();
innerCirclePaint.setAlpha(130);
innerCirclePaint.setColor(getResources().getColor(R.color.deep_green));
innerCirclePaint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
size = getMeasuredWidth();
setMeasuredDimension(size, size);
innerCenterX = size/2;
innerCenterY = size/2;
viewCenterX = size/2;
viewCenterY = size/2;
outerCircleRadius = size/2;
innerCircleRadius = size/5;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(viewCenterX, viewCenterY, outerCircleRadius, outerCirclePaint);
canvas.drawCircle((float) innerCenterX, (float) innerCenterY, innerCircleRadius, innerCirclePaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handleEvent(event);
break;
case MotionEvent.ACTION_MOVE:
handleEvent(event);
break;
case MotionEvent.ACTION_UP:
restorePosition();
break;
}
return true;
}
/**
* 处理手势事件
*/
private void handleEvent(MotionEvent event) {
double distance = Math.sqrt(Math.pow(event.getX()-viewCenterX, 2) + Math.pow(event.getY()-viewCenterY, 2)); //触摸点与view中心距离
if (distance < outerCircleRadius-innerCircleRadius) {
//在自由域之内,触摸点实时作为内圆圆心
innerCenterX = event.getX();
innerCenterY = event.getY();
invalidate();
} else {
//在自由域之外,内圆圆心在触摸点与外圆圆心的线段上
updateInnerCircelCenter(event);
}
}
/**
* 在自由域外更新内圆中心坐标
*/
private void updateInnerCircelCenter(MotionEvent event) {
double distance = Math.sqrt(Math.pow(event.getX()-viewCenterX, 2) + Math.pow(event.getY()-viewCenterY, 2)); //当前触摸点到圆心的距离
int innerDistance = outerCircleRadius-innerCircleRadius; //内圆圆心到中心点距离
//相似三角形的性质,两个相似三角形各边比例相等得到等式
innerCenterX = (event.getX()-viewCenterX)*innerDistance/distance + viewCenterX;
innerCenterY = (event.getY()-viewCenterY)*innerDistance/distance + viewCenterY;
invalidate();
}
/**
* 恢复内圆到view中心位置
*/
private void restorePosition() {
innerCenterX = viewCenterX;
innerCenterY = viewCenterY;
invalidate();
}
}
打完收工。