In some mobile games, the player can control the actions of the game character through a virtual console. There are also applications for this type of dial in drone and toy control apps.

Use custom View way to achieve similar handle control.

The code can be found at github.com/RustFisher/…

JoystickView features

JoystickView currently has the following features

  • Two kinds of style
    • Fixed control panel;
    • Floating follow mode; The dial moves to where your finger first clicks
  • You can add an “arrow” on the background to add an effect image
  • Custom “touch ball” picture and background picture
  • The finger moves out of the range of the control panel and remains following
  • Can get the percentage parameter of the moving position

Implementation approach

Use custom View to implement the control disk. Create the TouchView.

The basic requirement of the control panel is to follow the finger to react. To get the coordinates of the finger touch screen, we use the View’s onTouchEvent method.

The “touch ball” and background in the control are derived from the image. In the custom view first get the corresponding bitmap, scale to the specified size.

The corresponding coordinates are obtained in onTouchEvent to calculate the position of the image; OnDraw draws according to coordinates. Calculate the distance between the finger position and the center of the control disk, and transmit the information through the listener.

Code sample

Set the style

There are fixed and floating styles, which may be added in the future

public enum PadStyle {
    FLOATING /* Reposition with the user finger */,
    FIXED    /* Fixed position */
}
Copy the code

Control panel Configuration

Instead of directly manipulating the TouchView, we can create the TouchViewModel to store the configuration.

    private int bgResId;           // Background image resource ID
    private int touchBmpResId;     // Touch graph resource ID - for example, a sphere
    private int directionPicResId; // Indicates the image ID of the current touch point relative to the center of the circle

    private float mWholeViewWid;    // The width of the entire View
    private float mWholeViewHeight; // The height of the View
    private float mWholePadWid;    // The width of the disk, including the arrow; It's not the total width of the View
    private float mWholePadHeight; // The height of the disk, including the arrow; It's not the total width of the View

    private int mRoundBgRadius;    // Background circle radius background circle position can vary
    private int mTouchBallRadius = 100; // Touch the radius of the ball
    private int mRoundBgPadding;   // The px from the background circle to the Pad boundary is usually reserved for the direction arrow

    private boolean showDirectionPic = false;    // Whether to display the indicator picture
    private PadStyle mPadStyle = PadStyle.FIXED; // The default is fixed position
    private PadLocationType mPadLocationType = PadLocationType.LEFT_BOT;
    / /...
Copy the code

Control disk manager

The controller disk has many configuration items. A DefaultController is abstracted to manage the controller disk. This controller is not necessary. The manager needs to control the parent View of the disk, using a RelativeLayout.

Create a Left drive. Pass in the size configuration. And then we add it to containerView.

    private void createLeftControlTouchView(a) {
        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 total size
        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);
Copy the code

The manager initialization requires a ViewGroup to hold the control disk.

    public DefaultController(Context context, RelativeLayout containerView, PadStyle padStyle) {
        this.ctx = context;
        this.containerView = containerView;
        this.padStyle = padStyle;
    }
Copy the code

Fragments used in

Initialize the manager

Initialize the manager to create a control disk

    mDefaultController =
            new DefaultController(getContext(),
                    (RelativeLayout) root.findViewById(R.id.joystick_container));
    mDefaultController.createViews();
    mDefaultController.showViews(false);
Copy the code

Set the listener to obtain the operation information of the user

The listener is set through the controller

        mDefaultController.setLeftTouchViewListener(new JoystickTouchViewListener() {
            @Override
            public void onTouch(float horizontalPercent, float verticalPercent) {
                Log.d(TAG, "onTouch left: " + horizontalPercent + "," + verticalPercent);
            }

            @Override
            public void onReset(a) {
                Log.d(TAG, "onReset: left");
            }

            @Override
            public void onActionDown(a) {
                Log.d(TAG, "onActionDown: left");
            }

            @Override
            public void onActionUp(a) {
                Log.d(TAG, "onActionUp: left"); }});Copy the code

So far, we have achieved a simple control disk control. You can use this control in some control class applications.

If you want to make more beautiful, more attractive controls, we need to have a good aesthetic level.