GamepadView、JostickView仿创客工场中的遥感的自定义View

一、游戏操纵杆的展示:

       小时候必定玩过小霸王类的手柄游戏,很怀念小时候。无意间看见创客工场里控制机器人的View中有一个类似操纵杆的控件。

所以很感兴趣的仿造做了一个玩玩,效果如下:

二、实现的思想步骤:

  1、首先自定义View的三大步骤 onMeasure()、onDraw()、onLayout(),在此控件中放置对应的图片即可实现。onLayout的放置似乎没有什么必要,最主要的是onDraw绘制出控件。

  2、绘制出控件接下来就是onTouchEvent()对手势的事件进行处理,包含中心遥感随着手势的点击、拖动实时位置的变化,手势释放时中心遥感回到原始位置。

 3、以上处理完之后就是逻辑的处理了,根据手势的事件在View所表现出来的坐标计算出对应的方向并与方向箭头挂上关系。

三、代码展示:

package com.wangyongyao1989.joystickview.views;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.wangyongyao1989.joystickview.R;

/**
 * @author wangyao
 * @package com.wangyongyao1989.joystickview.views
 * @describe TODO
 * @date 2018/8/3
 */

public class GameJoystickView extends View {

    public final static long DEFAULT_LOOP_INTERVAL = 200; // 200 ms
    private final double RAD = 57.2957795;      //1弧度=57.2957795°

    private final String TAG = GameJoystickView.class.getName();
    private Bitmap mJoystickBackground;
    private Bitmap mJoystickCenter;
    private Bitmap mJoystickCenterUP;

    private Paint mainCircle;
    private int mCenterX;
    private int mCenterY;
    private int mJoystickRadius;
    private Bitmap mJoystickCenterDown;
    private int mCenterPositionX = 0;
    private int mCenterPositionY = 0;
    private int mCenterRadius;
    private int mCenterNorH;
    private int mCenterNorW;

    private int mTouchPositionX;
    private int mTouchPositionY;

    private OnJoystickMoveListener onJoystickMoveListener; // Listener
    private long loopInterval = DEFAULT_LOOP_INTERVAL;


    public final static int VIEW_CENTER_CIRCLE = 0;
    public final static int UP_DIRECTION = -1;   //上
    public final static int DOWN_DIRECTION = -2;  //下
    public final static int LEFT_DIRECTION = -3;     //左
    public final static int RIGHT_DIRECTION = -4;   //右


    private int lastPower = 0;
    private int lastAngle = 0;
    private int[] angleArray = new int[] {0, 15, 35, 55, 75, 105, 125, 145, 165, 195,
            215, 235, 255, 285, 305, 325, 345, 360};

    private Bitmap mArrowLeftNor, mArrowLeftPre, mArrowRightNor, mArrowRightPre,
            mArrowUpNor, mArrowUpPre,mArrowDownNor, mArrowDownPre, mArrowLeft, mArrowRight, mArrowUp, mArrowDown;
    private int mArrowH;
    private int mArrowW;


    public GameJoystickView(Context context) {
        super(context);
        initGameJoystickView(context);
    }

    public GameJoystickView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initGameJoystickView(context);
    }

    public GameJoystickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initGameJoystickView(context);
    }

    /**
     * 遥感移动方向的监听
     */
    public interface OnJoystickMoveListener {
        void onValueChanged(int angle, int power, int direction);
    }

    public void setOnJoystickMoveListener(OnJoystickMoveListener listener,
                                          long repeatInterval) {
        this.onJoystickMoveListener = listener;
        this.loopInterval = repeatInterval;
    }


    private void initGameJoystickView(Context context) {

        //获取每种状态下的bitmap
        mJoystickBackground = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_bg_joystick);
        mJoystickCenter = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_center_nor);
        mJoystickCenterDown = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_center_pre);
        mJoystickCenterUP = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_center_nor);

        mArrowLeft = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_left_nor);
        mArrowRight = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_right_nor);
        mArrowUp= BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_up_nor);
        mArrowDown= BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_down_nor);


        mArrowLeftNor = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_left_nor);
        mArrowLeftPre = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_left_pre);
        mArrowRightNor = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_right_nor);
        mArrowRightPre = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_right_pre);

        mArrowUpNor= BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_up_nor);
        mArrowUpPre = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_up_pre);
        mArrowDownNor = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_down_nor);
        mArrowDownPre = BitmapFactory.decodeResource(context.getResources(), R.drawable.widget_image_joystick_down_pre);

        mArrowH = mArrowUpNor.getHeight();
        mArrowW = mArrowUpNor.getWidth();

        mCenterNorH = mJoystickCenter.getHeight();
        mCenterNorW = mJoystickCenter.getWidth();

        mCenterRadius = Math.max(mCenterNorH, mCenterNorW) / 5;

        mainCircle = new Paint(Paint.ANTI_ALIAS_FLAG);


    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        int d = Math.min(w, h);
        //控件中心位置坐标
        mCenterPositionX = (int) getWidth() / 2;
        mCenterPositionY = (int) getWidth() / 2;
        mTouchPositionX = mCenterPositionX;
        mTouchPositionY = mCenterPositionY;
        //获取整个控件矩形的边长的0.75倍
        mJoystickRadius = (int)( d/2 *0.75) ;

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int d = Math.min(measure(widthMeasureSpec), measure(heightMeasureSpec));
        setMeasuredDimension(d, d);
    }

    @Override
    protected void onDraw(Canvas canvas) {
//        super.onDraw(canvas);
        //获取view的中心点
        mCenterX = (getWidth()) / 2;
        mCenterY = (getHeight()) / 2;

        //canvas出背景图片
        canvas.drawBitmap(mJoystickBackground,null,new Rect(
                (mCenterX - mJoystickRadius),
                (mCenterY - mJoystickRadius),
                (mCenterX + mJoystickRadius),
                (mCenterY + mJoystickRadius)
        ),mainCircle);

        //朝上箭头
        canvas.drawBitmap(mArrowUp,null, new Rect(
                (mCenterX - mArrowW /4),
                (int) ((mCenterY - mJoystickRadius * 0.8) - mArrowH/4),
                (mCenterX + mArrowW/4),
                (int) ((mCenterY - mJoystickRadius * 0.8) + mArrowH/4)
        ),mainCircle);

        canvas.drawBitmap(mArrowDown,null, new Rect(
                (mCenterX - mArrowW /4),
                (int) ((mCenterY + mJoystickRadius * 0.8) - mArrowH/4),
                (mCenterX + mArrowW/4),
                (int) ((mCenterY + mJoystickRadius * 0.8) + mArrowH/4)
        ),mainCircle);

        canvas.drawBitmap(mArrowLeft,null, new Rect(
                (int) ((mCenterX - mJoystickRadius *0.8) - mArrowW /4),
                (mCenterY  - mArrowH/4),
                (int) ((mCenterX - mJoystickRadius *0.8) + mArrowW /4),
                (mCenterY + mArrowH/4)
        ),mainCircle);

        canvas.drawBitmap(mArrowRight,null, new Rect(
                (int) ((mCenterX + mJoystickRadius *0.8) - mArrowW /4),
                (mCenterY  - mArrowH/4),
                (int) ((mCenterX + mJoystickRadius *0.8) + mArrowW /4),
                (mCenterY + mArrowH/4)
        ),mainCircle);


        mCenterPositionX = mTouchPositionX;
        mCenterPositionY = mTouchPositionY;

        //canvas出中心遥感的图片
        canvas.drawBitmap(mJoystickCenter,null,new Rect(
                (mCenterPositionX - mCenterNorW/5),
                (mCenterPositionY - mCenterNorH/5),
                (mCenterPositionX +  mCenterNorW/5),
                (mCenterPositionY + mCenterNorH/5)
        ),mainCircle);


    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        mTouchPositionX = (int) event.getX();
        mTouchPositionY = (int) event.getY();

        double sqrt = Math.sqrt((mTouchPositionX - mCenterX) * (mTouchPositionX - mCenterX) +
                (mTouchPositionY - mCenterY) * (mTouchPositionY - mCenterY));
//        Log.e(TAG,"sqrt;"+sqrt);

        if (sqrt > (mJoystickRadius - mCenterRadius)  ) {
            mTouchPositionX = (int) ((mTouchPositionX - mCenterX )* (mJoystickRadius - mCenterRadius) / sqrt + mCenterX);
            mTouchPositionY = (int) ((mTouchPositionY - mCenterY) * (mJoystickRadius - mCenterRadius) / sqrt + mCenterY);
        }
        changeArrowState();
        invalidate();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN : {
                if (sqrt > mJoystickRadius) {
                    mTouchPositionX =  mCenterX;
                    mTouchPositionY =  mCenterY;
                    return false;
                }else {
                    mJoystickCenter = mJoystickCenterDown;
                    invalidate();
                }

            }
            if (onJoystickMoveListener != null)
                onJoystickMoveListener.onValueChanged(getAngle(), getPower(), getFourDirection());
            break;

            case MotionEvent.ACTION_MOVE : {

                if (onJoystickMoveListener != null)
                    onJoystickMoveListener.onValueChanged(getAngle(), getPower(), getFourDirection());

            }
            break;

            case MotionEvent.ACTION_UP : {

                mTouchPositionX = (int) mCenterX;
                mTouchPositionY = (int) mCenterY;
                mJoystickCenter = mJoystickCenterUP;
                mArrowUp = mArrowUpNor;
                mArrowDown = mArrowDownNor;
                mArrowLeft = mArrowLeftNor;
                mArrowRight = mArrowRightNor;
                invalidate();
                if (onJoystickMoveListener != null)
                    onJoystickMoveListener.onValueChanged(getAngle(), getPower(), getFourDirection());
            }
            break;
        }

        return true;
    }

    /**
     * 改变箭头显示状态
     */
    private void changeArrowState() {
        if (getFourDirection() == UP_DIRECTION ) {
            mArrowUp = mArrowUpPre;
            mArrowDown = mArrowDownNor;
            mArrowLeft = mArrowLeftNor;
            mArrowRight = mArrowRightNor;
        }else if (getFourDirection() == DOWN_DIRECTION) {
            mArrowDown = mArrowDownPre;
            mArrowUp = mArrowUpNor;
            mArrowLeft = mArrowLeftNor;
            mArrowRight = mArrowRightNor;
        }else if(getFourDirection() == LEFT_DIRECTION) {
            mArrowLeft = mArrowLeftPre;
            mArrowUp = mArrowUpNor;
            mArrowDown = mArrowDownNor;
            mArrowRight = mArrowRightNor;
        }else if (getFourDirection() == RIGHT_DIRECTION) {
            mArrowRight = mArrowRightPre;
            mArrowUp = mArrowUpNor;
            mArrowDown = mArrowDownNor;
            mArrowLeft = mArrowLeftNor;
        }
    }


    /**
     *  返回四个方向的值
     * @return
     */
    private int getFourDirection() {
        int direction = VIEW_CENTER_CIRCLE;
        lastPower = getPower();
        if (lastPower <= 33 ) {
            return direction;
        }
        int a = 0;
        if (lastAngle <= 0) {
            a = (lastAngle * -1) + 90;
        } else if (lastAngle > 0) {
            if (lastAngle <= 90) {
                a = 90 - lastAngle;
            } else {
                a = 360 - (lastAngle - 90);
            }
        }
        int arraylength = angleArray.length;
        for (int i = 1; i < arraylength; i++) {
            if (a >= angleArray[i-1] && a < angleArray[i]) {
                direction = i % (arraylength - 1);
                if (direction == 0) {
                    direction++;
                }
                break;
            }
        }
        Log.e(TAG,"direction:"+direction);
        if (direction > 3 && direction <= 5) {
            return UP_DIRECTION;
        }else if (direction > 5 && direction <=10) {
            return LEFT_DIRECTION;
        }else if (direction > 10 && direction <= 15) {
            return DOWN_DIRECTION;
        }else if ((direction > 0 && direction <= 3) || ((direction > 15 && direction <= 16))) {
            return RIGHT_DIRECTION;
        }else {
            return 0;
        }
    }

    /**
     * 手势移动的半径值(相对于View中心点)与背景图案半径的比值
     * @return
     */
    private int getPower() {
        return (int) (100 * Math.sqrt((mTouchPositionX - mCenterX) * (mTouchPositionX - mCenterX) +
                (mTouchPositionY - mCenterY) * (mTouchPositionY - mCenterY)) / mJoystickRadius);
    }

    /**
     * 通过坐标获取角度值(反正切值 * 弧度 = 角度)
     * @return
     */
    private int getAngle() {
        if (mTouchPositionX > mCenterX) {
            if (mTouchPositionY < mCenterY) {
                return lastAngle = (int) (Math.atan((mTouchPositionY - mCenterY) / (mTouchPositionX - mCenterX)) * RAD + 90);
            } else if (mTouchPositionY > mCenterY) {
                return lastAngle = (int) (Math.atan((mTouchPositionY - mCenterY) / (mTouchPositionX - mCenterX)) * RAD) + 90;
            } else {
                return lastAngle = 90;
            }
        } else if (mTouchPositionX < mCenterX) {
            if (mTouchPositionY < mCenterY) {
                return lastAngle = (int) (Math.atan((mTouchPositionY - mCenterY) / (mTouchPositionX - mCenterX)) * RAD - 90);
            } else if (mTouchPositionY > mCenterY) {
                return lastAngle = (int) (Math.atan((mTouchPositionY - mCenterY) / (mTouchPositionX - mCenterX)) * RAD) - 90;
            } else {
                return lastAngle = -90;
            }
        } else {
            if (mTouchPositionY <= mCenterY) {
                return lastAngle = 0;
            } else {
                if (lastAngle < 0) {
                    return lastAngle = -180;
                } else {
                    return lastAngle = 180;
                }
            }
        }
    }


    /**
     * 控件测量的设置
     * @param measureSpec
     * @return
     */
    private int measure(int measureSpec) {
        int result = 0;

        // Decode the measurement specifications.
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.UNSPECIFIED) {
            // Return a default size of 200 if no bounds are specified.
            result = 200;
        } else {
            // As you want to fill the available space
            // always return the full available bounds.
            result = specSize;
        }
        return result;
    }

}

四、代码源码下载地址:https://download.csdn.net/download/wangyongyao1989/10583034

五、github的地址:https://github.com/wangyongyao1989/JoystickView

 

在一些手机游戏,玩家可以通过虚拟控制盘来控制游戏角色的行动。 无人机和玩具操控App也有这一类控制盘的应用。用自定义View的方式来实现类似手柄的控件。相关代码请见 github.com/RustFisher/…JoystickView特性目前JoystickView特性如下2种风格固定控制盘;浮动跟随模式;控制盘会移动到手指第一次点击的地方可以在背景上添加“箭头”,即添加效果图片自定义的“触摸球”图片和背景图片手指移出了控制盘范围,仍然能够保持追随能获取到移动位置的百分比参数实现思路用自定义View的方式实现这个控制盘。创建TouchView。控制盘的基本要求是跟随手指做出反应。为了获取到手指触屏的坐标,会用到View的onTouchEvent方法。控件的“触摸球”和背景由图片得来。在自定义view先获取相应的bitmap,缩放成指定的尺寸。onTouchEvent获取到相应的坐标,计算出图片应该出现的位置;onDraw根据坐标进行绘制。 计算出手指位置与控制盘心的距离等信息,通过listener传递出去。代码示例样式设定有固定和浮动这两种风格,未来可能还会添加public enum PadStyle {     FLOATING /* 随用户手指重新定位 */,     FIXED    /* 固定位置 */ }控制盘配置我们可以不直接操作TouchView,创建TouchViewModel存放相关的配置。    private int bgResId;           // 背景图片资源ID     private int touchBmpResId;     // 触摸图资源ID - 例如一个圆球     private int directionPicResId; // 指示当前触摸点与圆心相对方向的图片ID     private float mWholeViewWid;    // 整个View的宽     private float mWholeViewHeight; // 整个View的高     private float mWholePadWid;    // 盘的宽度,包括箭头;并不是View的总宽度     private float mWholePadHeight; // 盘的高度,包括箭头;并不是View的总宽度     private int mRoundBgRadius;    // 背景圆的半径 背景圆位置可以变化     private int mTouchBallRadius = 100; // 触摸球的半径     private int mRoundBgPadding;   // 背景圆到Pad边界的px  一般是留给方向箭头的位置     private boolean showDirectionPic = false;    // 是否显示指示图片     private PadStyle mPadStyle = PadStyle.FIXED; // 默认为固定位置的     private PadLocationType mPadLocationType = PadLocationType.LEFT_BOT;     // .........控制盘管理器控制盘的配置项比较多,抽象出一个DefaultController来管理控制盘。这个控制器不是必要的。 管理器需要控制盘所在的父View,这里用的是RelativeLayout。创建一个“左控制盘”。将各个尺寸配置传入。最后添加到containerView。    private void createLeftControlTouchView() {         TouchViewModel model = new TouchViewModel(                 R.drawable.ui_pic_joystick_left_pad,                 R.drawable.ui_pic_joystick_control_ball);         model.setWholeViewSize(ctx.getResources().getDimensionPixelSize(R.dimen.ui_joystick_whole_field_wid),                 ctx.getResources().getDimensionPixelSize(R.dimen.ui_joystick_whole_field_height));         model.setPadSize(ctx.getResources().getDimensionPixelSize(R.dimen.ui_joystick_pad_size),                 ctx.getResources().getDimensionPixelSize(R.dimen.ui_joystick_pad_size));         int roundBgRadius = ctx.getResources().getDimensionPixelSize(R.dimen.ui_joystick_round_bg_radius);         model.setContentSize(roundBgRadius, (int) (roundBgRadius / 3.5));         model.setStyle(padStyle, PadLocationType.LEFT_BOT);         model.setRoundBgPadding(ctx.getResources().getDimensionPixelSize(R.dimen.ui_joystick_circle_bg_padding));         leftControlTouchView = new TouchView(ctx);         leftControlTouchView.init(model);         // View的总大小         RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(                 ctx.getResources().getDimensionPixelSize(R.dimen.ui_joystick_whole_field_wid),                 ctx.getResources().getDimensionPixelSize(R.dimen.ui_joystick_whole_field_height)         );         params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);         params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);         leftControlTouchView.setLayoutParams(params);     }     // ............     createLeftControlTouchView();     containerView.addView(leftControlTouchView);管理器初始化时需要一个ViewGroup来承载控制盘。    public DefaultController(Context context, RelativeLayout containerViewPadStyle padStyle) {         this.ctx = context;         this.containerView = containerView;         this.padStyle = padStyle;     }Fragment使用初始化管理器初始化管理器,创建控制盘    mDefaultController =             new DefaultController(getContext(),                     (RelativeLayout) root.findViewById(R.id.joystick_container));     mDefaultController.createViews();     mDefaultController.showViews(false);设置监听器,获取用户的操作信息通过控制器来设置监听器        mDefaultController.setLeftTouchViewListener(new JoystickTouchViewListener() {             @Override             public void onTouch(float horizontalPercent, float verticalPercent) {                 Log.d(TAG, "onTouch left: "   horizontalPercent   ", "   verticalPercent);             }             @Override             public void onReset() {                 Log.d(TAG, "onReset: left");             }             @Override             public void onActionDown() {                 Log.d(TAG, "onActionDown: left");             }             @Override             public void onActionUp() {                 Log.d(TAG, "onActionUp: left");             }         });至此,我们实现了一个简单的控制盘控件。在一些控制类应用可以使用这个控件。若想要做出更优美,更吸引人的控件,需要我们有好的审美水平。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值