Android自定义view之游戏摇杆

国际惯例,先贴效果:

这里是Github地址

实现步骤:

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();
    }

}

打完收工。

你可能感兴趣的:(Android自定义view之游戏摇杆)