自定义控件入门 类似手游控制移动的触摸球(自定义view基础,简单数学应用,事件回调)...

自定义触摸球View
本文通过实例演示如何自定义一个触摸球View,包括绘制两个圆、实现跟随手指移动的效果及限制小圆在大圆内移动等功能。介绍了Paint、Canvas等核心组件的使用。

这篇文章能帮你学到如何自定义view,很多人都觉得自定义view是一个很难的东西,其实很简单,只是要记住的东西比较多而已,然而我重来不会刻意去记住什么东西,理论懂了,实践下,不懂就百度。我就以这个触摸球为例子,不知道是不是叫触摸球,暂且这么叫吧(本文也只是根据这个view来写一些知识,如果哪里不理解或者不好,欢迎批评,如果想直接看代码在最后面。。。)


先上效果图

如果看完这个图你觉得自己可以实现或者说觉得很简单,我觉得有必要先自己实现一个吧,起码把原理步骤,需要的数学计算先在脑海里面自己过一遍,然后思考一下,再来看看我这篇文章,如果你有更好的思路也要分享出来。

关于自定义view可以参考这个,写了一系列的文章,很完整 http://blog.youkuaiyun.com/aigestudio/article/details/41212583

步骤:

针对这个触摸球实现步骤分为4步(如果熟悉自定义view的可以到第3步了)

  • 画出2个圆
  • 让圆可以动起来
  • 在小圆只能在大圆的范围内移动
  • 把小圆的数据回调
画出2个圆

这一步的实现就会涉及自定义view的最最最基本的知识。 首先要画画,也是要步骤的吧

  1. 准备画笔paint 这个android已经有为我们提供了一直很强大的笔,你可以用这支笔为所欲为了,笔是不缺,现在就看用笔的人了,先介绍这支笔吧。
Paint mPaint = new Panit();
复制代码

这样就有一支笔了,然后就要给笔加各种属性了,嘿嘿嘿,要加什么?我在这个项目就很简单啦。 就加了4个属性

mPaint.setColor(Color.LTGRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
复制代码

setColor

这个很好理解 就是要一支什么颜色的笔

setStyle

这个有3个选择

  • Paint.Style.STROKE 这个是空心
  • Paint.Style.FALL 这个是实心
  • Paint.Style.FILL_AND_STROKE
  • 如果不太理解就先要一个印象,后面会明白的

setStrokeWidth

这个是设置一支笔的宽度,就是笔画的粗细

setAntiAlias

是否抗抗锯齿这个效果很明显,可以自己些下代码感觉感觉

  1. 现在有笔了,那要怎么画?在哪里画?

都说自定义view啦,肯定是画在view上面啦。

  • 首先我们先自己建一个类继承view

    public class FloatballView extends View  {
    
    private Context mContext;
    private Paint mPaint;
    private int mWidth;
    private int mHeight;
    private float posX;
    private float posY;
    private boolean isMove = false;
    
    private final int minRadius = 50;
    private final int maxRadius = 200;
    
    private FloatballListener floatballListener;
    
    public FloatballView(Context context) {
        super(context);
    }
    
    public FloatballView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mPaint = new Paint();
        mPaint.setColor(Color.LTGRAY);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setAntiAlias(true);
    
    }
    
    public void setFloatballListener(FloatballListener floatballListener) {
        this.floatballListener = floatballListener;
    }
    
    public FloatballView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        posX = mWidth / 2;
        posY = mHeight / 2;
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mWidth / 2, mHeight / 2, maxRadius, mPaint);
        canvas.drawCircle(posX, posY, minRadius, mPaint);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }
    }
    
    复制代码

    本例子只需要关注onDraw 构造方法做了一些初始化的工作 比如我们刚才的笔,一般都在构造方法初始化。

    初始代码

    public FloatballView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mPaint = new Paint();
        mPaint.setColor(Color.LTGRAY);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setAntiAlias(true);
        posX = mWidth / 2;
        posY = mHeight / 2;
    
    }
    复制代码

    onDraw 就是我们用笔的地方啦

    画圈圈的代码

    canvas.drawCircle(mWidth / 2, mHeight / 2, maxRadius, mPaint);
    canvas.drawCircle(posX, posY, minRadius, mPaint);
    复制代码

    canvas 就是画布的意思 我们这里用到它的一个drawCircle的方法,这个方法就是画一个圆,第一个参数就是圆心的x,第二个就是y了第三个就是半径,第4个就放下我们的点,这些参数大家可以改改,看看变化,加强理解。 这样我们2个圆就已经画出来了。本文只是大概讲下大体的流程,如果你想成为一个自定义高手,建议你去按下上面我推荐的那篇文章,作者很用心写的,很详细,当然你要花的时间也非常多,我这篇文章只能带你看看什么是自定义,并不能让你成为高手。

让圆动起来

如果想要圆跟着我们手指动起来,首先就需要知道我们的手指在哪里吧

只要实现 onTouchEvent 这个方法就可以了,这个方法会告诉我们,我们在这个view触摸了那些地方,是落下的状态还是离开的状态,还是滑动的状态

if (event.getAction() == MotionEvent.ACTION_DOWN
                && mWidth / 2 - minRadius/2 <= event.getX() && event.getX() <= mWidth / 2 + minRadius/2
                && mHeight / 2 - minRadius/2 <= event.getY() && event.getY() <= mHeight / 2 + minRadius/2) {
            isMove = true;

        } else if (event.getAction() == MotionEvent.ACTION_MOVE && isMove) {

                posX = event.getX();
                posY = event.getY();


        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            posX = mWidth / 2;
            posY = mHeight / 2;
            isMove = false;
        }

        invalidate();
        return true;
复制代码

这时候就可以用你的手指来带飞那个小圈圈了

这里3个判断分别指手指在屏幕的3个状态,dowm move up. 所以第一个判断就是在手指必须是down的状态和在小圈圈的状态下才可以让他move,这里加了一个ismove来标志,第一个就是move啦,当手指在屏幕上面移动的时候,改变posx和posy的值并且invalidate来重绘,达到移动的目的。第三个判断就是手指离开屏幕了,将小球默认回到原来的位置,并且让标志为false。然后小球就动起来了,美滋滋,可是小圆离开大圆了 真尴尬,要是打游戏打到圈圈没了,那不给队友干死,那如何将小圈圈固定在大圆圈里面? 请看下一步

在小圆只能在大圆的范围内移动

如果看到这里,你先想想自己的思路如何,最好自己实现下看是否可行,这里要运用到高中数学的一些简单知识

主要有2点

  • 2点间的距离
  • 相似三角形

我们先想想我们要做什么,有什么,需要什么,我们目的很强烈,就是要小圈圈在大圈圈里边,那么2圆心肯定是不会超过大圆圈的半径的是吧。那么这个问题就转换成为2点间的问题了,如果你刚才没有思路,看到这里我还是建议你自己又可以开始尝试了写这个效果了。

2点的距离公式很简单 sqrt($(x1 - x2)^2+(y1 - y2)^2$)

但我又遇到个问题了,当手指离开大圈圈的时候,这时候手指只能控制着小球的方向,因为小球的距离已经是极限了嘛,这个我用到了相似三角形的原理,什么是相似三角形? 如果大家忘记了可以去看下,后面会上图,描述一下这个关系。这里也利用了相似三角形的边比例一定相同来控制小圈圈2边的变化,从而达到控制角度。 说这么多我觉得还是上张手绘图吧,良心手绘呀。(下图有点错误,mHeight是mHeight/2 mWidth也是一样)

其中红色就是我们的2个相似三角形,posX和posY就是我们要求的2个数据。 先上代码吧:

 posX = (float) (mWidth/2 +((event.getX()-(mWidth/2))/(l/maxRadius)));
 posY = (float) (mHeight/2 +((event.getY()-(mHeight/2))/(l/maxRadius)));
复制代码

不知道有没有小朋友不懂 其实转成原始公式就是这样子滴:

l/maxRadius=(posX-mWidth)/(event.getx()-(mWidth/2))
然后和上图一起理解应该没压力吧。。。。

这样就基本是完成了我们这个小球球了

数据的回调

这个也很重要,相当于把这个view的状态暴露出来,让我们用他的人反馈到信息,这个我能想到的就2个数据,所以写了一个接口来暴露数据。 代码如下:

public  interface  FloatballListener{
        /**
         * 回调小球的方向和偏移的程度
         * @param direction  小球的方向 1-12  分别代表12个方向  就是我们平时说的几点钟方向。
         * @param level 0-10 表示小球的偏移程度 越靠近圆心就越小。
         */
        public void onTouch(double direction,double level);
    }
复制代码

这里小球的方向还在考虑怎么做,如果有看官可以指点1 2,将感激不尽呀。 如果大家觉得能学习到一丁点东西,就点个赞吧,小弟才有欲望多为各位看官排忧解难呀。。。。。

下面是完整代码:

package com.example.floatballdemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by Abner on 2017/7/19.
 */

public class FloatballView extends View  {

    private Context mContext;
    private Paint mPaint;
    private int mWidth;
    private int mHeight;
    private float posX;
    private float posY;
    private boolean isMove = false;

    private final int minRadius = 50;
    private final int maxRadius = 200;

    private FloatballListener floatballListener;

    public FloatballView(Context context) {
        super(context);
    }

    public FloatballView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mPaint = new Paint();
        mPaint.setColor(Color.LTGRAY);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setAntiAlias(true);
    }

    public void setFloatballListener(FloatballListener floatballListener) {
        this.floatballListener = floatballListener;
    }

    public FloatballView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }

    /**
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        Log.e("abner", "widthMeasureSpec:" + widthMeasureSpec);
        Log.e("abner", "heightMeasureSpec" + heightMeasureSpec);
        Log.e("abner", "getMeasuredWidth:" + getMeasuredWidth());
        Log.e("abner", "getMeasuredHeight" + getMeasuredHeight());
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        posX = mWidth / 2;
        posY = mHeight / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mWidth / 2, mHeight / 2, maxRadius, mPaint);
        canvas.drawCircle(posX, posY, minRadius, mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN
                && mWidth / 2 - minRadius <= event.getX() && event.getX() <= mWidth / 2 + minRadius
                && mHeight / 2 - minRadius <= event.getY() && event.getY() <= mHeight / 2 + minRadius) {
            isMove = true;

        } else if (event.getAction() == MotionEvent.ACTION_MOVE && isMove) {


            double l = Math.sqrt(Math.pow((event.getX()-mWidth/2),2)+Math.pow((event.getY()-mHeight/2),2));
            if (l>=maxRadius)
            {
                posX = (float) (mWidth/2 +((event.getX()-(mWidth/2))/(l/maxRadius)));
                posY = (float) (mHeight/2 +((event.getY()-(mHeight/2))/(l/maxRadius)));
//                Log.e("abner","x:"+(posX-mWidth/2));
//                Log.e("abner","y:"+(posY-mHeight/2));
                Log.e("abner",""+(posX-mWidth/2)/(posY-mHeight/2));
            }
            else
            {
                posX = event.getX();
                posY = event.getY();
            }

//            floatballListener.onTouch(,(minRadius/l)*10);

        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            posX = mWidth / 2;
            posY = mHeight / 2;
            isMove = false;
        }

        invalidate();
        return true;

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    public  interface  FloatballListener{
        /**
         * 回调小球的方向和偏移的程度
         * @param direction  小球的方向 1-12  分别代表12个方向  就是我们平时说的几点钟方向。
         * @param level 0-10 表示小球的偏移程度 越靠近圆心就越小。
         */
        public void onTouch(double direction,double level);
    }
}
复制代码

转载于:https://juejin.im/post/5a31d9fdf265da432c23dc38

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值