[Android]使用自定义SurfaceView实现幸运转盘效果

文章介绍了如何使用Android的SurfaceView来实现一个抽奖转盘的应用。开发者首先比较了自定义View和SurfaceView,选择了后者因其高性能适合频繁图形绘制。内容包括转盘的各个图层设计,如背景、外层内容、内层数字和按钮,并通过状态机控制不同状态下的颜色变化。代码中实现了状态接口和不同状态(初始态、运行态、等待态)的逻辑,以及基于SurfaceView的转盘绘制代码,详细展示了如何绘制和更新转盘的各个元素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 背景

最近想写个小应用,有个类似抽奖转盘的控件需要实现,因此记录和分享这个实现过程。一开始打算使用自定义view来写的,毕竟之前写过,后来写了一半,发现SurfaceView是一个专门为频繁绘制图形而提供的高性能类,因此决定改为SurfaceView来实现。

使用硬件:Nexus7 2013版,十年前的平板了,性能表现还行。

  1. 效果预览

按住中间的按钮开始转动灯盘,停止后加权两个灯盘的数字,获得加权和进行显示,效果如下:

GIF有时加载不出来,csdn的问题, 我贴张图片大家自行想象下,数字区域黄色的灯块会在用户按住中心按钮时进行滚动,松手后取和显示在中央。

不知道为什么,这个GIF在优快云有时候就是显示不出来

  1. 实现想法和思路

由于图层的属性约束,因此绘制图形需要先从底层开始绘制,保障绘制顺序,那么按照所设计的转盘,分别从下到上的图层为:

  • 背景圆盘

  • 外层内容 与 内层 数字栅格

  • 按钮图层

  • 数字总和

其中,内层的数字栅格具有演示效果,考虑在灯盘转动过程中,将整个内层栅格全部置灰,以增强视觉对比效果,同时通过控制色块的位置,制造数字灯盘转动的效果,而灯盘转灯的间隔,则根据SurfaceView中的刷新线程间隔来进行控制。

考虑到转盘的整体效果,存在三种状态:初始态 - > 运行态 - > 等待态

初始态表示未发生过启动事件,无选中表现在界面,运行态表示灯盘转动过程,界面需要有对比和动画效果,等待态表示运行结束,选中结果保留在屏幕上。

因此,基于以上考虑,单独实现一个状态机来配合控制状态切换。

  1. 代码实现

考虑到代码的灵活统一和可阅读性,我们除了状态机,还将外层内容,内层数字栅格,按钮及数字图层抽象为三个类,方便随时调整内部参数。

代码目录结构如下:

Component,各图层要素抽象类内容包;

fsm,状态机;

LuckyWheel, GUI绘制及逻辑控制。

4.1图层内容抽象

4.1.1外层内容

外圈内容抽象,包含画笔,背景颜色,以及要定义显示的内容元素。

因为格子并非标准的矩形,因此需要使用path类进行绘制,利用弧度绘制显示,因此需要存储内外相切圆的半径。

将UI和文字的path存储至此类中,方便重复利用。

public class PunishmentAndReward {
    public int itemsNum;
    public float bg_out_radius;
    public float bg_in_radius;
    public float txt_radius;
    public final String[] txt_array= {"文字A","文字A","文字A","文字A",
            "文字A","文字A","文字A","文字A"};
    public List<Path> layer_list = new ArrayList<>();
    public List<Path> text_list = new ArrayList<>();
    public Paint mPaintPRLayer,mPaintText;

    public final int PRsColorOne = 0xff60c5ba;
    public final int PRsColorTwo = 0xffffc952;
    public final int PRsColorThree = 0xffa5dff9;
    public final int PRsColorFour = 0xffef5285;

    public PunishmentAndReward(){
        itemsNum = txt_array.length;
        mPaintPRLayer = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintPRLayer.setStyle(Paint.Style.FILL);
        mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintText.setStrokeWidth(2);
        mPaintText.setColor(Color.WHITE);
        mPaintText.setTextAlign(Paint.Align.CENTER);
        mPaintText.setTextSize(45);
        mPaintText.setStyle(Paint.Style.FILL);
    }
}

4.2.2 内层及数字内容

public class Stride {
    public int itemsNum;
    public float bg_out_radius;
    public float bg_in_radius;
    public float txt_radius;
    public final String[] txt_array= {"0", "8", "3", "7" ,"4", "9", "2", "1", "5", "10",
            "6", "3", "1", "12","11","6", "8", "1", "10", "5",
            "12","4","9","1","8","2","7","9","10","6",
            "5","7","4","12","11","3","2","6","9","1"};
    public List<Path> layer_list = new ArrayList<>();
    public List<Path> text_list = new ArrayList<>();

    public final int defaultZeroColor = 0xff41D3BD;
    public final int defaultColorOne = 0xff090707;
    public final int defaultColorTwo = 0xffE53A40;
    public final int unselectedColor = 0xff9baec8;
    public final int selectedColor = 0xffffc952;

    public Paint mPaintStrideLayer, mPaintText;

    public Stride(){
        itemsNum = txt_array.length;
        mPaintStrideLayer = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintStrideLayer.setStyle(Paint.Style.FILL);
        mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintText.setStrokeWidth(2);
        mPaintText.setColor(Color.WHITE);
        mPaintText.setTextAlign(Paint.Align.CENTER);
        mPaintText.setTextSize(55);
        mPaintText.setStyle(Paint.Style.STROKE);
    }

}

4.2.3按钮内容

由于按钮有触摸事件,为了限制触发事件只在按钮圆圈内生效,需要使用Region类进行判断。

public class StartBtn {
    public float mRadius=0;
    public Paint mPaintStartBtn;
    public Paint mPaintTextBg;
    public Paint mPaintText;
    public float offsetText;

    public Path mPath = new Path();
    public Region mRegion = new Region();


    public StartBtn(){
        mPaintStartBtn = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintStartBtn.setStyle(Paint.Style.FILL);
        mPaintStartBtn.setColor(0xff1ec0ff);
        mPaintTextBg = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintTextBg.setStyle(Paint.Style.FILL);
        mPaintTextBg.setColor(0xff1ec0ff);
        mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintText.setStyle(Paint.Style.FILL);
        mPaintText.setColor(Color.WHITE);
        mPaintText.setTextAlign(Paint.Align.CENTER);
    }

    public void calc_path(){
        mPath.addCircle(0,0,mRadius, Path.Direction.CW);
//        Log.i("TAG", "calc_path: "+mRadius);
        mRegion.setPath(mPath,new Region(-(int)mRadius,-(int)mRadius,(int)mRadius,(int)mRadius));
        mPaintText.setTextSize(mRadius);
        offsetText = mRadius/4;
    }

4.2状态机

4.2.1接口定义

动画控制原理只需要控制不同时刻的颜色变化即可,因此状态接口只需要定义颜色接口。

public interface State {
    int[] pickStridesColors();    //内圈栅格颜色

    int[] pickPRsColors();        //外圈栅格颜色
}

4.2.2状态机控制器

使用单例模式做控制器,实现基本的状态切换能力。

public class FSMM implements State{
    private final String TAG = "FSMM";
    private static FSMM instalce = null;
    private final Initial initialState;
    private final Running runningState;
    private final Waiting waitingState;
    private State state;
    public final String str_initial = "state_initial";
    public final String str_running = "state_running";
    public final String str_waiting = "state_waiting";

    public final int[] selected_flags = {0,1};
    public Stride strideObj = new Stride();
    public PunishmentAndReward punRewObj = new PunishmentAndReward();
    public StartBtn startBtn = new StartBtn();

    public static FSMM getInstance(){
        if (null == instalce){
            synchronized (FSMM.class){
                if (null == instalce){
                    instalce = new FSMM();
                }
            }
        }
        return instalce;
    }

    private void init(){
        Log.i(TAG, "init: FSMM 初始化完成 ...");
    }

    private FSMM(){
        initialState = new Initial(this);
        runningState = new Running(this);
        waitingState = new Waiting(this);
        this.state = initialState;
    }

    public void setState(State state){
        Log.i(TAG, "setState: before --> " + getStateString());
        this.state = state;
        Log.i(TAG, "setState: after --> " + getStateString());
    }

    private State getState(){
        return this.state;
    }

    public State getStateByString(String str){
        switch (str){
            case str_initial:
                return initialState;
            case str_running:
                return runningState;
            case str_waiting:
                return waitingState;
            default:
                Log.i(TAG, "getStateByString: err here");
                return null;
        }
    }

    public String getStateString(){
        if (getState() instanceof Initial){
            return str_initial;
        }else if (getState() instanceof Running){
            return str_running;
        }else if (getState() instanceof Waiting){
            return str_waiting;
        }else {
            return "unknown_state";
        }
    }


    @Override
    public int[] pickStridesColors() {
        return state.pickStridesColors();
    }

    @Override
    public int[] pickPRsColors() {
        return state.pickPRsColors();
    }
}

4.2.3初始状态定义

初始咋红台下,只要提供固定颜色即可,特殊颜色如内圈的0栅格,额外设置即可。

public class Initial implements State{
    private final String TAG = "Initial";
    FSMM fsmm ;
    public Initial(FSMM fsmm) {
        this.fsmm = fsmm;
    }

    @Override
    public int[] pickStridesColors() {
        int[] colors = new int[fsmm.strideObj.itemsNum];
        colors[0] = fsmm.strideObj.defaultZeroColor;
        for (int i = 1; i < fsmm.strideObj.itemsNum; i++){
            if (i % 2 == 0){
                colors[i] = fsmm.strideObj.defaultColorTwo;
            }else {
                colors[i] = fsmm.strideObj.defaultColorOne;
            }
        }
        return colors;
    }

    @Override
    public int[] pickPRsColors() {
        int[] colors = new int[fsmm.punRewObj.itemsNum];
        colors[0] = fsmm.punRewObj.PRsColorOne;
        colors[1] = fsmm.punRewObj.PRsColorTwo;
        colors[2] = fsmm.punRewObj.PRsColorOne;
        colors[3] = fsmm.punRewObj.PRsColorThree;
        colors[4] = fsmm.punRewObj.PRsColorFour;
        colors[5] = fsmm.punRewObj.PRsColorTwo;
        colors[6] = fsmm.punRewObj.PRsColorThree;
        colors[7] = fsmm.punRewObj.PRsColorFour;
        return colors;
    }
}

4.2.4运行状态定义

运行状态中,需要灰化每一个数字灯格,表现为未选中状态,每一次运行,都会对选中数字栅格的颜色进行改变,由于我们设计的是双跑马灯,因此,需要FSMM控制器中定义int[] 来存储位置数字,每调运一次接口,选中位置标记分别++和--,并对栅格位置的颜色进行相应的改变。

public class Running implements State {
    private final String TAG = "Running";
    FSMM fsmm ;
    public Running(FSMM fsmm) {
        this.fsmm = fsmm;
    }
    
    @Override
    public int[] pickStridesColors() {
        int[] selected_flags = fsmm.selected_flags;
        int[] colors = new int[fsmm.strideObj.itemsNum];
        selected_flags[0]++;
        selected_flags[1]--;
        if (selected_flags[1] == selected_flags[0]){
            selected_flags[0]++;
        }
        if (selected_flags[0] > 39){
            selected_flags[0] = 0;
        }
        if (selected_flags[0] < 0){
            selected_flags[0] = 39;
        }
        if (selected_flags[1] > 39){
            selected_flags[1] = 0;
        }
        if (selected_flags[1] < 0){
            selected_flags[1] = 39;
        }

//        Log.i(TAG, "pickStridesColors: "+ selected_flags[0]+"--"+selected_flags[1]);
        for (int i=0;i<fsmm.strideObj.itemsNum;i++){
            if (i == selected_flags[0] || i ==selected_flags[1]){
                colors[i] = fsmm.strideObj.selectedColor;
            }else {
                colors[i] = fsmm.strideObj.unselectedColor;
            }
        }
        return colors;
    }

    @Override
    public int[] pickPRsColors() {
        int[] colors = new int[fsmm.punRewObj.itemsNum];
        colors[0] = fsmm.punRewObj.PRsColorOne;
        colors[1] = fsmm.punRewObj.PRsColorTwo;
        colors[2] = fsmm.punRewObj.PRsColorOne;
        colors[3] = fsmm.punRewObj.PRsColorThree;
        colors[4] = fsmm.punRewObj.PRsColorFour;
        colors[5] = fsmm.punRewObj.PRsColorTwo;
        colors[6] = fsmm.punRewObj.PRsColorThree;
        colors[7] = fsmm.punRewObj.PRsColorFour;
        return colors;
    }
}

4.2.5等待状态定义

等待装态需要做的事情为恢复灯盘的原本颜色,同时保留被选中栅格位置的颜色。

public class Waiting implements State{
    private final String TAG = "Waiting";
    FSMM fsmm;
    public Waiting(FSMM fsmm) {
        this.fsmm = fsmm;
    }

    @Override
    public int[] pickStridesColors() {
        int[] colors = new int[fsmm.strideObj.itemsNum];

        for (int i = 0; i < fsmm.strideObj.itemsNum; i++){
            if (i % 2 == 0){
                colors[i] = fsmm.strideObj.defaultColorTwo;
            }else {
                colors[i] = fsmm.strideObj.defaultColorOne;
            }
            if (i == fsmm.selected_flags[0] || i == fsmm.selected_flags[1]){
                colors[i] = fsmm.strideObj.selectedColor;
            }
        }
        if (0 != fsmm.selected_flags[0] && 0 != fsmm.selected_flags[1]){
            colors[0] = fsmm.strideObj.defaultZeroColor;
        }
        return colors;
    }

    @Override
    public int[] pickPRsColors() {
        int[] colors = new int[fsmm.punRewObj.itemsNum];
        colors[0] = fsmm.punRewObj.PRsColorOne;
        colors[1] = fsmm.punRewObj.PRsColorTwo;
        colors[2] = fsmm.punRewObj.PRsColorOne;
        colors[3] = fsmm.punRewObj.PRsColorThree;
        colors[4] = fsmm.punRewObj.PRsColorFour;
        colors[5] = fsmm.punRewObj.PRsColorTwo;
        colors[6] = fsmm.punRewObj.PRsColorThree;
        colors[7] = fsmm.punRewObj.PRsColorFour;
        return colors;
    }
}

4.3基于SurfaceView的转盘代码实现

代码中使用了initBGCounter等需要注意下,本意是为了避免重复绘制造成性能损耗,因为SurfaceView具有多重缓冲的特性,因此如果想固定某个背景不再重新绘制,需要初始化三次画面,因此代码中使用了个计数器保证缓冲全部被填充。

public class LuckyWheel extends SurfaceView implements SurfaceHolder.Callback ,Runnable{
    private final String TAG = "LuckyWheel";
    private final Context mContext;
    private Paint mPaintBackground;
    private SurfaceHolder mSurfaceHolder;
    Canvas mCanvas;
    private float mWidth, mHeight;
    boolean isDrawing= false, isStarting= false;;
    float mBackLayerRadius;
    private int initBGCounter = 0;  //针对三缓冲而设置的计数器
    private int initPRLayerCounter =0;

    public LuckyWheel(Context context) {
        super(context);
        this.mContext = context;

    }

    public LuckyWheel(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;

    }

    public LuckyWheel(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = getWidth();
        mHeight = getHeight();
        calc_paras();   //计算标准参数
        init();
        calc_punishment_and_reward_paras(FSMM.getInstance().punRewObj.bg_out_radius,
                FSMM.getInstance().punRewObj.bg_in_radius,
                FSMM.getInstance().punRewObj.txt_radius);
        calc_stride_layer_paras(FSMM.getInstance().strideObj.bg_out_radius
                ,FSMM.getInstance().strideObj.bg_in_radius
                ,FSMM.getInstance().strideObj.txt_radius);
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        Log.i(TAG, "surfaceCreated: ");
        isDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        Log.i(TAG, "surfaceDestroyed: ");
        isDrawing = false;
    }

    @Override
    public void run() {
        Log.i(TAG, "run: 运行开始");
        long t = 0;
        while (isDrawing){
            t = System.currentTimeMillis();
            try {
                mCanvas = mSurfaceHolder.lockCanvas();
                axis_init();

                draw_background();
                draw_punishment_and_reward_layer();
                draw_strides_layer();
                draw_start_button_layer();
            }finally {
                if (mCanvas!=null){
                    mSurfaceHolder.unlockCanvasAndPost(mCanvas);
                }
            }
//            Log.i(TAG, "run: 运行中");
            try {
                Thread.sleep(Math.max(0, 5-(System.currentTimeMillis()-t)));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void init(){
        Log.d(TAG, "init: hehhehe");
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
        this.setZOrderOnTop(true);  //画布透明处理
        this.mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true); //屏幕常亮
        mPaintBackground = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintBackground.setColor(0xff52616a);
        mPaintBackground.setStyle(Paint.Style.FILL);
        FSMM.getInstance().startBtn.mPaintStartBtn.setShadowLayer(FSMM.getInstance().startBtn.mRadius,0,0,0xff0080ff);
        FSMM.getInstance().startBtn.calc_path();
    }

    //坐标原点移动到中间,y轴翻转
    private void axis_init(){
        mCanvas.translate(mWidth/2, mHeight/2);
//        mCanvas.scale(1,-1);
    }

    private void draw_background(){
        if (initBGCounter < 4){
            mCanvas.drawCircle(0,0,mBackLayerRadius,mPaintBackground);
            mCanvas.drawPath(FSMM.getInstance().startBtn.mPath,FSMM.getInstance().startBtn.mPaintStartBtn);
            initBGCounter++;
        }
    }

    private void calc_paras(){
        mBackLayerRadius = mWidth/2;
        FSMM.getInstance().punRewObj.bg_out_radius = mWidth/30 * 14;
        FSMM.getInstance().punRewObj.bg_in_radius = mWidth/30 * 11;
        FSMM.getInstance().punRewObj.txt_radius = (float) (mWidth/30 * 12) ;
        FSMM.getInstance().strideObj.bg_out_radius = mWidth/30 * 11;
        FSMM.getInstance().strideObj.bg_in_radius = mWidth/30 * 9;
        FSMM.getInstance().strideObj.txt_radius = (float)(mWidth/30 * 9.5) ;

        FSMM.getInstance().startBtn.mRadius = mWidth/30 * 7;
    }

    private void draw_strides_layer(){
        int[] colors = FSMM.getInstance().pickStridesColors();
        for (int i = 0; i <FSMM.getInstance().strideObj.itemsNum ;i++){
            FSMM.getInstance().strideObj.mPaintStrideLayer.setColor(colors[i]);
            mCanvas.drawPath(FSMM.getInstance().strideObj.layer_list.get(i),FSMM.getInstance().strideObj.mPaintStrideLayer);
        }
        for (int i = 0 ; i< FSMM.getInstance().strideObj.itemsNum;i++){
            mCanvas.drawTextOnPath(FSMM.getInstance().strideObj.txt_array[i],FSMM.getInstance().strideObj.text_list.get(i),0,0, FSMM.getInstance().strideObj.mPaintText);
        }
    }

    /*数字层参数计算,包含文字*/
    private void calc_stride_layer_paras(float out_rads, float in_rads, float text_rads){
        int start_angle = 0;
        int offset_angle = 360 / FSMM.getInstance().strideObj.itemsNum; //计算每个item需要旋转的角度
        RectF text_recfF = new RectF(-text_rads,-text_rads,text_rads,text_rads);
        RectF out_rectF = new RectF(-out_rads, -out_rads, out_rads, out_rads);
        RectF in_recfF = new RectF(-in_rads, -in_rads, in_rads, in_rads);
        for (int i =0; i <FSMM.getInstance().strideObj.itemsNum; i++){
            int out_start_angle = start_angle + i * offset_angle;   //每次偏移
            int in_start_angle = out_start_angle + offset_angle;    //内偏多一个便宜角度反向开始扫弧度

            Path path = new Path();
            path.arcTo(out_rectF, out_start_angle, offset_angle,false);
            path.arcTo(in_recfF,in_start_angle,-offset_angle,false);
            path.close();
            FSMM.getInstance().strideObj.layer_list.add(path);
            Path mStrideTextPath = new Path();
            mStrideTextPath.arcTo(text_recfF,out_start_angle,offset_angle);
            FSMM.getInstance().strideObj.text_list.add(mStrideTextPath);

        }
    }

    private void draw_start_button_layer(){
        mCanvas.drawPath(FSMM.getInstance().startBtn.mPath,FSMM.getInstance().startBtn.mPaintTextBg);
        int num1 = Integer.parseInt(FSMM.getInstance().strideObj.txt_array[FSMM.getInstance().selected_flags[0]]);
        int num2 = Integer.parseInt(FSMM.getInstance().strideObj.txt_array[FSMM.getInstance().selected_flags[1]]);
        String numStr = String.valueOf(num1+num2);
        float offset = FSMM.getInstance().startBtn.offsetText;
        mCanvas.drawText(numStr,0,offset,FSMM.getInstance().startBtn.mPaintText);
    }

    private void draw_punishment_and_reward_layer(){
        if (initPRLayerCounter < 4){
            initPRLayerCounter ++ ;
            int[] colors = FSMM.getInstance().pickPRsColors();
            for (int i = 0; i <FSMM.getInstance().punRewObj.itemsNum ;i++){
                FSMM.getInstance().punRewObj.mPaintPRLayer.setColor(colors[i]);
                mCanvas.drawPath(FSMM.getInstance().punRewObj.layer_list.get(i),FSMM.getInstance().punRewObj.mPaintPRLayer);
            }
            for (int i = 0 ; i< FSMM.getInstance().punRewObj.itemsNum;i++){
                mCanvas.drawTextOnPath(FSMM.getInstance().punRewObj.txt_array[i],FSMM.getInstance().punRewObj.text_list.get(i),0,0, FSMM.getInstance().punRewObj.mPaintText);
            }
        }
    }

    private void calc_punishment_and_reward_paras(float out_rads, float in_rads, float text_rads){
        int start_angle = 0;
        int offset_angle = 360 / FSMM.getInstance().punRewObj.itemsNum;
        RectF text_recfF = new RectF(-text_rads,-text_rads,text_rads,text_rads);
        RectF out_rectF = new RectF(-out_rads, -out_rads, out_rads, out_rads);
        RectF in_recfF = new RectF(-in_rads, -in_rads, in_rads, in_rads);
        for (int i = 0; i < FSMM.getInstance().punRewObj.itemsNum;i++){
            int out_start_angle = start_angle + i * offset_angle;   //每次偏移
            int in_start_angle = out_start_angle + offset_angle;    //内偏多一个便宜角度反向开始扫弧度
            Path path = new Path();
            path.arcTo(out_rectF, out_start_angle, offset_angle,false);
            path.arcTo(in_recfF,in_start_angle,-offset_angle,false);
            path.close();
            FSMM.getInstance().punRewObj.layer_list.add(path);
            Path mTextPath = new Path();
            mTextPath.arcTo(text_recfF,out_start_angle,offset_angle);
            FSMM.getInstance().punRewObj.text_list.add(mTextPath);
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_DOWN:
                //判断是否落在按钮区域内,event.getXY获得的是屏幕坐标系,由于画布坐标系做过平移,因此需要做转换
                if (FSMM.getInstance().startBtn.mRegion.contains((int)event.getX()-(int)mWidth/2,(int)event.getY()-((int) mHeight/2))){
                    FSMM.getInstance().selected_flags[0] = new Random().nextInt(FSMM.getInstance().strideObj.itemsNum);
                    FSMM.getInstance().selected_flags[1] = new Random().nextInt(FSMM.getInstance().strideObj.itemsNum);
                    FSMM.getInstance().setState(FSMM.getInstance().getStateByString("state_running"));

                }else {
                    Log.i(TAG, "onTouchEvent: 未落在");
                }
                Log.i(TAG, "ACTION_DOWN: "+x+"---"+y);
                break;
            case MotionEvent.ACTION_UP:
                FSMM.getInstance().setState(FSMM.getInstance().getStateByString("state_waiting"));
                break;
        }
        return true;
    }
}
  1. 引用参考

5.1Nexus7平板

刷机包:https://forum.xda-developers.com/t/rom-flo-deb-unofficial-lineageos-19-1-2023-02-18.3569067/page-204#post-88244585

刷机参考:https://blog.lzc.app/index.php/2021/09/27/nexus-7-%E4%BA%8C%E4%BB%A3-%E5%88%B7%E6%9C%BA%E5%AE%89%E8%A3%85android-7-1-2%E7%B3%BB%E7%BB%9F/

5.2参考

Android 自定义View —— Path_android path 描边_胡小牧的博客-优快云博客

安卓canvas path addArc()与arcTo()方法的区别_path.addarc_Java_noob1的博客-优快云博客

Android_自定义遥控器按钮_CodeCopyer的博客-优快云博客

android 自定义view 画板改变画笔颜色_android 不断变化paint颜色修改_小鲁班one的博客-优快云博客

LOL Colors - Curated color palette inspiration (webdesignrankings.com)

Android 自定义View-文字绘制_android 自定义view绘制文字_xiangxiongfly915的博客-优快云博客

5.3资源

https://pictogrammers.com/library/mdi/

https://fonts.google.com/icons?selected=Material+Icons&icon.platform=android

https://www.aigei.com/s?tab=file&type=2d&dim=interface_ui-is_vip_false

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值