GameRocker: 360°游戏摇杆
GameRocker是一个自定义View,类似于游戏操作中的摇杆,可以实现360°方向切换。
最近因为项目需求,需要在Android平板上使用类似游戏手柄上的360°摇杆来实现机器人运动的控制。
首先,看看实现的效果图:
其实现原理比较简单,设置小圆的触摸事件,并跟随手指的滑动而移动,但不能超过大圆的范围。但是手指移动的范围是可以大于大圆的范围的,所以需要先判断手指移动的范围是否超过大圆范围,超过了就需要进行坐标折算。因为是同心圆,所以,判断的是否超限的依据可以简化为,手指移动的x,y坐标的平方和是否大于最初的圆环的宽度。其原理示意图如下:
对于超限的移动,需要折算坐标,其公式如下:
double distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
distanceX = (float) (originLeft * distanceX / distance);
distanceY = (float) (originTop * distanceY / distance);
其中distanceX 、distanceY 为手指移动的距离;originLeft 、originTop 为最开始小圆的marginLfet和marginTop,也就是圆环的宽度。
其实现步骤可以分为以下几步:
1、使用shape.xml文件画出大、小两个圆,并同心放置在布局文件里
circle_background.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size android:height="150dp"
android:width="150dp"/>
<solid android:color="@color/background" />
</shape>
circle_button.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size android:height="80dp"
android:width="80dp"/>
<solid android:color="@color/light_blue" />
</shape>
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="200dp">
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/circle_background"
android:layout_centerInParent="true"
android:id="@+id/background"/>
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/circle_button"
android:layout_marginTop="60dp"
android:layout_marginLeft="60dp"
android:id="@+id/rocker"
android:clickable="true"/>
</RelativeLayout>
2、新建一个GameRocker继承RelativeLayout,代码如下:
package com.james_jiang.gamerocker.game_rocker;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.james_jiang.gamerocker.R;
public class GameRocker extends RelativeLayout
implements View.OnTouchListener{
private final static String TAG = "GameRocker";
private ImageView rocker;
private ImageView background;
private boolean loadOnce;
private GameRockerListener listener; //rocker移动监听,传递坐标数据
private float xDown; //按下时x坐标
private float yDown; //按下时y坐标
private int originLeft; //也是最大移动距离
private int originTop;
private float xDistance; //上一次移动的x距离
private float yDistance;
private RelativeLayout.LayoutParams params;
private final static int DIVIDE = 15; //松开时,反弹传递的距离等分数
public GameRocker(Context context) {
super(context);
initFiled();
initView(context);
}
public GameRocker(Context context, AttributeSet attrs) {
super(context, attrs);
initFiled();
initView(context);
}
public GameRocker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initFiled();
initView(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public GameRocker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initFiled();
initView(context);
}
private void initFiled(){
loadOnce = false;
listener = null;
xDown = 0.0f;
yDown = 0.0f;
xDistance = 0.0f;
yDistance = 0.0f;
}
private void initView(Context context){
View gameRocker = LayoutInflater.from(context).inflate(R.layout.game_rocker, null, true);
rocker = (ImageView) gameRocker.findViewById(R.id.rocker);
background = (ImageView) gameRocker.findViewById(R.id.background);
rocker.setOnTouchListener(this);
addView(gameRocker, 0); //需要添加视图
}
@Override
protected void onLayout(boolean change, int l, int t, int r, int b){
super.onLayout(change, l, t, r, b);
if(change && !loadOnce){
rocker.setImageResource(R.drawable.circle_button);
background.setImageResource(R.drawable.circle_background);
params = (RelativeLayout.LayoutParams) rocker.getLayoutParams();
loadOnce = true;
originTop = rocker.getTop();
originLeft = rocker.getLeft();
}
}
//注册监听器
public void setOnGameRockerListener(GameRockerListener listener){
this.listener = listener;
}
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
xDown = event.getRawX();
yDown = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float distanceX = event.getRawX() - xDown;
float distanceY = event.getRawY() - yDown;
//先判断是否越界
if(!isInnerCircle(distanceX, distanceY)){
//坐标折算
double distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
distanceX = (float) (originLeft * distanceX / distance);
distanceY = (float) (originTop * distanceY / distance);
}
//再执行响应
if(distanceX != xDistance || distanceY != yDistance) {
xDistance = distanceX;
yDistance = distanceY;
params.setMargins((int) (distanceX + originLeft),
(int) (distanceY + originTop),
0, 0);
rocker.setLayoutParams(params);
if(listener != null){
listener.onDirection(distanceX, distanceY);
}
}
break;
case MotionEvent.ACTION_UP:
float divideX = xDistance / DIVIDE;
float divideY = yDistance / DIVIDE;
for (int i = 0; i < DIVIDE; i++) {
xDistance = xDistance - divideX;
yDistance = yDistance - divideY;
if(i == DIVIDE - 1) {
xDistance = 0.0f;
yDistance = 0.0f;
}
params.setMargins((int) xDistance + originLeft,
(int) yDistance + originTop, 0, 0);
rocker.setLayoutParams(params);
if (listener != null) {
listener.onDirection(xDistance, yDistance);
}
}
break;
}
return false;
}
//判断是否在外圈内,因为是同心圆,所以任何方向上都只能平移圆环的宽度
private boolean isInnerCircle(float xMove, float yMove){
if(Math.sqrt(xMove * xMove + yMove *yMove) > originLeft)
return false;
return true;
}
}
3、摇杆移动监听器,在使用摇杆的地方注册监听器,获取移动的x、y坐标。
package com.james_jiang.gamerocker.game_rocker;
public interface GameRockerListener {
//入参是移动的x和y的距离
void onDirection(float x, float y);
}
4、在主Activity布局内加入该控件,并注册监听器。
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.james_jiang.gamerocker.MainActivity">
<com.james_jiang.gamerocker.game_rocker.GameRocker
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/game_rocker"
android:layout_centerInParent="true"/>
</RelativeLayout>
MainActivity.java:
package com.james_jiang.gamerocker;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import com.james_jiang.gamerocker.game_rocker.GameRocker;
import com.james_jiang.gamerocker.game_rocker.GameRockerListener;
public class MainActivity extends Activity implements GameRockerListener{
private final static String TAG = "MainActivity";
private GameRocker gameRocker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView(){
gameRocker = (GameRocker) findViewById(R.id.game_rocker);
setOnListener();
}
private void setOnListener(){
gameRocker.setOnGameRockerListener(this);
}
@Override
public void onDirection(float x, float y) {
Log.i(TAG, "x = " + x + ", y = " + y);
}
}
到这里,整个项目就完成了。