GameRocker: 360°游戏摇杆

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);
    }
}

到这里,整个项目就完成了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值