android 虚拟手柄
最近因为想写个Android小游戏,其中涉及到虚拟手柄,于是写了这个demo
效果图:
通过自定义两个view来实现,并且通过回调 将手柄的的偏移量也就是滑动的距离,传递给目标运动的view ,运动的view中放大这个偏移量后使需要运动的目标进行移动。
先写一个移动的view,这个比较简单,先处理传递进来的偏移量,然后计算偏移后点的坐标,然后根据坐标进行绘制
PoinetView.java
package com.example.liu.hfs;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
/**
* Created by liu on 9/5/17.
*/
public class PointView extends View {
private Bitmap mWhitePiece ;
private int mPanelWidth;
private float mLineHeight;
private float PieceSize = 3*1.0f/4;
private int MAX_LINE = 12;
private int width;
private float piontX ,piontY;
public PointView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mWhitePiece = BitmapFactory.decodeResource(getResources(),R.drawable.stone_w2);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取屏幕的宽带和高度
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
width = Math.min(widthSize,heightSize);
if (widthMode==MeasureSpec.UNSPECIFIED){
width=heightSize;
}else if (heightMode==MeasureSpec.UNSPECIFIED){
width=widthSize;
}
setMeasuredDimension(width, width);//设置我们View的高度和宽度
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mPanelWidth =w ;
mLineHeight = mPanelWidth*1.0f / MAX_LINE;
int PieceWidth = (int) (mLineHeight*PieceSize);
mWhitePiece = Bitmap.createScaledBitmap(mWhitePiece,PieceWidth,PieceWidth,false);
}
private float a,b;
public void setX(float a) {
this.a = a;
invalidate();//有偏移量传进来后就进行重绘
}
public void setY(float b ){
this.b = b;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
piontX = (float) (width / 2+a*2.5);//将偏移量乘以2.5倍后加上原点的X坐标值得到当前点的X坐标指
piontY = (float) (width / 2+b*2.5);//将偏移量乘以2.5倍后加上原点的Y坐标值得到当前点的Y坐标指
Log.e("PoinetView",piontX+"--"+piontY);
canvas.drawBitmap(mWhitePiece, piontX, piontY,null);//根据计算后的坐标进行绘制图形
check();
}
//检查坐标是否在范围内,再范围内就提示
private void check(){
if ((800 < piontY & piontY<= 900) && (700 < piontX & piontX< 800)){
Toast.makeText(getContext(), "失败", Toast.LENGTH_SHORT).show();
}
}
}
虚拟手柄 NavView.java
package com.example.liu.hfs;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by liu on 9/5/17.
*/
public class NavView extends View {
private int innerColor;
private int outerColor;
private final static int INNER_COLOR_DEFAULT = Color.parseColor("#d32f2f");
private final static int OUTER_COLOR_DEFAULT = Color.parseColor("#f44336");
private int OUTER_WIDTH_SIZE;
private int OUTER_HEIGHT_SIZE;
private int realWidth;//绘图使用的宽
private int realHeight;//绘图使用的高
private float innerCenterX;
private float innerCenterY;
private float outRadius;
private float innerRedius;
private Paint outerPaint;
private Paint innerPaint;
private OnNavAndSpeedListener mCallBack = null;
public interface OnNavAndSpeedListener{
public void onNavAndSpeed(float nav,float speed);
}
public NavView(Context context) {
this(context,null);
}
public NavView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = getResources().obtainAttributes(attrs,R.styleable.NavView);
innerColor = ta.getColor(R.styleable.NavView_InnerColor,INNER_COLOR_DEFAULT);
outerColor = ta.getColor(R.styleable.NavView_OuterColor,OUTER_COLOR_DEFAULT);
ta.recycle();
OUTER_WIDTH_SIZE = dip2px(context,125.0f);
OUTER_HEIGHT_SIZE = dip2px(context,125.0f);
outerPaint = new Paint();
innerPaint = new Paint();
outerPaint.setColor(outerColor);
outerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
innerPaint.setColor(innerColor);
innerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(width,height);
}
private int measureWidth(int widthMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthVal = MeasureSpec.getSize(widthMeasureSpec);
//处理三种模式
if(widthMode==MeasureSpec.EXACTLY){
return widthVal+getPaddingLeft()+getPaddingRight();
}else if(widthMode==MeasureSpec.UNSPECIFIED){
return OUTER_WIDTH_SIZE;
}else{
return Math.min(OUTER_WIDTH_SIZE,widthVal);
}
}
private int measureHeight(int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightVal = MeasureSpec.getSize(heightMeasureSpec);
//处理三种模式
if(heightMode==MeasureSpec.EXACTLY){
return heightVal+getPaddingTop()+getPaddingBottom();
}else if(heightMode==MeasureSpec.UNSPECIFIED){
return OUTER_HEIGHT_SIZE;
}else{
return Math.min(OUTER_HEIGHT_SIZE,heightVal);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
realWidth = w;
realHeight = h;
innerCenterX = realWidth/2;
innerCenterY = realHeight/2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
outRadius = Math.min(Math.min(realWidth/2-getPaddingLeft(),realWidth/2-getPaddingRight()),Math.min(realHeight/2-getPaddingTop(),realHeight/2-getPaddingBottom()));
//画外部圆
canvas.drawCircle(realWidth/2,realHeight/2,outRadius,outerPaint);
//内部圆
innerRedius = outRadius*0.5f;
canvas.drawCircle(innerCenterX,innerCenterY,innerRedius,innerPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN){
changeInnerCirclePosition(event);
}
if(event.getAction()==MotionEvent.ACTION_MOVE){
changeInnerCirclePosition(event);
Log.i("TAG","MOVED");
}
if(event.getAction()==MotionEvent.ACTION_UP){
innerCenterX = realWidth/2;
innerCenterY = realHeight/2;
if(mCallBack!=null){
mCallBack.onNavAndSpeed(0,0);
}
invalidate();
}
return true;
}
private void changeInnerCirclePosition(MotionEvent e) {
//圆的方程:(x-realWidth/2)^2 +(y - realHeight/2)^2 <= outRadius^2
//第一步,确定有效的触摸点集
float X = e.getX();
float Y = e.getY();
if(mCallBack!=null){
mCallBack.onNavAndSpeed(X-realWidth/2,Y-realHeight/2);
}
//boolean isPointInOutCircle = Math.pow(X-realWidth/2,2) +Math.pow(Y-realHeight/2,2)<=Math.pow(outRadius,2);
boolean isPointInOutCircle =true;
if(isPointInOutCircle){
Log.i("TAG","inCircle");
//两种情况:小圆半径
boolean isPointInFree = Math.pow(X-realWidth/2,2) +Math.pow(Y-realHeight/2,2)<=Math.pow(outRadius-innerRedius,2);
if(isPointInFree){
innerCenterX = X;
innerCenterY = Y;
}else{
//处理限制区域,这部分使用触摸点与中心点与外圆方程交点作为内圆的中心点
//使用近似三角形来确定这个点
//求出触摸点,触摸点垂足和中心点构成的直角三角形(pointTri)的直角边长
float pointTriX = Math.abs(realWidth/2-X);//横边
float pointTriY = Math.abs(realHeight/2-Y);//竖边
float pointTriZ = (float) Math.sqrt((Math.pow(pointTriX,2)+Math.pow(pointTriY,2)));
float TriSin = pointTriY/pointTriZ;
float TriCos = pointTriX/pointTriZ;
//求出在圆环上的三角形的两个直角边的长度
float limitCircleTriY = (outRadius-innerRedius)*TriSin;
float limitCircleTriX = (outRadius-innerRedius)*TriCos;
//确定内圆中心点的位置,分四种情况
if(X>=realWidth/2 && Y>=realHeight/2){
innerCenterX = realWidth/2+limitCircleTriX;
innerCenterY = realHeight/2+limitCircleTriY;
}else if(X<realWidth/2 && Y>=realHeight/2){
innerCenterX = realWidth/2-limitCircleTriX;
innerCenterY= realHeight/2+limitCircleTriY;
}else if(X>=realWidth/2 && Y<realHeight/2){
innerCenterX = realWidth/2+limitCircleTriX;
innerCenterY= realHeight/2-limitCircleTriY;
}else{
innerCenterX = realWidth/2-limitCircleTriX;
innerCenterY= realHeight/2-limitCircleTriY;
}
Log.i("TAG","inLimit");
}
invalidate();
}else{
Log.i("TAG","notInCircle");
}
}
public void setOnNavAndSpeedListener(OnNavAndSpeedListener listener){
mCallBack = listener;
}
public static int dip2px(Context context, float dpValue){
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue*scale +0.5f);
}
}
MainActivity.java
package com.example.liu.hfs;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private NavView navView;
private TextView textView;
private PointView PointView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置全屏
setContentView(R.layout.activity_main);
navView = (NavView) findViewById(R.id.nav);
PointView = (com.example.liu.hfs.PointView) findViewById(R.id.pointview);
textView = (TextView) findViewById(R.id.textView);
//回调函数
navView.setOnNavAndSpeedListener(new NavView.OnNavAndSpeedListener() {
@Override
public void onNavAndSpeed(float nav, float speed) {
textView.setText(String.valueOf(nav)+"g"+String.valueOf(speed));
PointView.setX(nav);//将偏移量传递给pointview
PointView.setY(speed);
Log.e("------",nav+"--"+speed+"---");
}
});
}
}
布局代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
<com.example.liu.hfs.NavView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:id="@+id/nav"
android:layout_alignParentBottom="true"
android:layout_marginLeft="50dp"
android:layout_marginBottom="50dp"/>
<com.example.liu.hfs.PointView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:background="@color/colorAccent"
android:id="@+id/pointview"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
/>
</RelativeLayout>
attrs.xim
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="InnerColor" format="color"/>
<attr name="OuterColor" format="color"/>
<declare-styleable name="NavView">
<attr name="InnerColor" />
<attr name="OuterColor"/>
</declare-styleable>
</resources>
最后我们需要强制横屏 别忘了 AndroidManifest.xml 中加 activity android:name=”.MainActivity” android:screenOrientation=”landscape”
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。
- 上一篇: android蓝牙手柄监听 BluetoothGamepad
- 下一篇: android游戏手柄问题