在一些手机游戏中,玩家可以通过虚拟控制盘来控制游戏角色的行动。 无人机和玩具操控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");
}
});
复制代码
至此,我们实现了一个简单的控制盘控件。在一些控制类应用中可以使用这个控件。
若想要做出更优美,更吸引人的控件,需要我们有好的审美水平。