这篇文章能帮你学到如何自定义view,很多人都觉得自定义view是一个很难的东西,其实很简单,只是要记住的东西比较多而已,然而我重来不会刻意去记住什么东西,理论懂了,实践下,不懂就百度。我就以这个触摸球为例子,不知道是不是叫触摸球,暂且这么叫吧(本文也只是根据这个view来写一些知识,如果哪里不理解或者不好,欢迎批评,如果想直接看代码在最后面。。。)
先上效果图
如果看完这个图你觉得自己可以实现或者说觉得很简单,我觉得有必要先自己实现一个吧,起码把原理步骤,需要的数学计算先在脑海里面自己过一遍,然后思考一下,再来看看我这篇文章,如果你有更好的思路也要分享出来。
关于自定义view可以参考这个,写了一系列的文章,很完整 http://blog.youkuaiyun.com/aigestudio/article/details/41212583
步骤:
针对这个触摸球实现步骤分为4步(如果熟悉自定义view的可以到第3步了)
- 画出2个圆
- 让圆可以动起来
- 在小圆只能在大圆的范围内移动
- 把小圆的数据回调
画出2个圆
这一步的实现就会涉及自定义view的最最最基本的知识。 首先要画画,也是要步骤的吧
- 准备画笔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
是否抗抗锯齿这个效果很明显,可以自己些下代码感觉感觉
- 现在有笔了,那要怎么画?在哪里画?
都说自定义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);
}
}
复制代码