Android自定义view之游戏摇杆

作者: 奔跑吧李博 | 来源:发表于2020-08-01 17:28 被阅读0次

    国际惯例,先贴效果:

    这里是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之游戏摇杆

        本文链接:https://www.haomeiwen.com/subject/ycmnkktx.html