Android - JoystickView 虛擬手柄,控制盤,自定義UI

rf_dev發表於2019-05-07

在一些手機遊戲中,玩家可以通過虛擬控制盤來控制遊戲角色的行動。 無人機和玩具操控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 containerView, PadStyle 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");
            }
        });
複製程式碼

至此,我們實現了一個簡單的控制盤控制元件。在一些控制類應用中可以使用這個控制元件。

若想要做出更優美,更吸引人的控制元件,需要我們有好的審美水平。

相關文章