牛骨文教育服务平台(让学习变的简单)
博文笔记

android 虚拟手柄

创建时间:2017-05-12 投稿人: 浏览次数:1114

最近因为想写个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”

声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。