最近在做一款交互性较为复杂的APP,需要开发一个方向操作控件。最终用自定义控件做了一个简单的版本。

这里我准备了两张素材图,作为方向盘被点击和没被点击的背景图。下面看看自定义的Wheel类

public class Wheel extends View implements View.OnTouchListener{
int xPosition;//点击按钮的x坐标
int yPosition;//点击按钮的y坐标
int centerX;//方向盘X轴中心
int centerY;//方向盘Y轴中心
int mainRadius;
int secondRadius;//点击的圆形按钮的半径
boolean isClicked;//用于判断方向盘是否被点击 public Wheel(Context context) {
super(context);
} public Wheel(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
} public Wheel(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
isClicked=false;//初始化为未点击状态
}
}

上面这是最初的代码,仅仅是声明了一些变量。

接下来我们来复写OnMeasure

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width =MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.UNSPECIFIED?:MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED?:MeasureSpec.getSize(heightMeasureSpec);
if(width>height){//将自定义控件的区域限制为正方形
width=height;
}else{
height=width;
}
this.mainRadius=(getWidth()-)/;//给主要半径赋值
this.secondRadius=mainRadius/*;//赋值可点击的圆形按钮的半径
setMeasuredDimension(width, height);
this.centerX=getWidth()/;//确定中心
this.centerY=getHeight()/;
this.xPosition=centerX;//最初可点击的圆形按钮在中心位置
this.yPosition=centerY;
}

接着是OnDraw函数

    @Override
protected void onDraw(Canvas canvas){
Bitmap bm;//背景图的bitmap
Paint circlePaint=new Paint();//可点击的圆形按钮的Paint对象
circlePaint.setColor(Color.parseColor("#52c1bd"));//设置颜色
circlePaint.setStyle(Paint.Style.FILL);//设置作图形式为填满
if(!isClicked){//如果为点击就将bm赋值为circle1这张图的bitmap对象,否则为circle
bm=((BitmapDrawable)getResources().getDrawable(R.mipmap.circle1)).getBitmap();
}
else {
bm = ((BitmapDrawable) getResources().getDrawable(R.mipmap.circle)).getBitmap();
}
Rect mSrcRect = new Rect(, , bm.getWidth(), bm.getHeight());//设置原始图像中要被画出来的区域
Rect mDestRect = new Rect(, ,getWidth()-, getHeight()-);//设置目标区域中会被画进去图像的区域
canvas.drawBitmap(bm,mSrcRect,mDestRect,BackgroundPaint);//画背景图
canvas.drawCircle(this.xPosition,this.yPosition,secondRadius,circlePaint);//画出可点击的中心按钮
}

上面我们创建Wheel类时还使用了OnTouchListener接口,所以要复写onTouch函数,但这里我们仅仅是写成下面的代码就行

不需要实现额外功能

    @Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}

我们实际上要用的是下面这个函数

   @Override
public boolean onTouchEvent(MotionEvent event){
isClicked=true;//设置为已经点击
this.xPosition = (int) event.getX();//得到点击的x坐标
this.yPosition = (int) event.getY();//得到点击的y坐标
        //如果点击的位置与圆心距离差距大于半径,就限制按钮的位置在边界处
        if(Math.sqrt((this.xPosition-this.centerX)*(this.xPosition-this.centerX)+(this.yPosition-this.centerY)*(this.yPosition-this.centerY))>mainRadius){
            double Yrate=(this.yPosition-this.centerY)/Math.sqrt((this.xPosition-this.centerX)*(this.xPosition-this.centerX)+(this.yPosition-this.centerY)*(this.yPosition-this.centerY));
double Xrate=(this.xPosition-this.centerX)/Math.sqrt((this.xPosition-this.centerX)*(this.xPosition-this.centerX)+(this.yPosition-this.centerY)*(this.yPosition-this.centerY));
this.yPosition=(int)(mainRadius*Yrate)+this.centerY;//设置可点击圆心按钮的位置在边界处
this.xPosition=(int)(mainRadius*Xrate)+this.centerX;
}
        
if(this.myWheelMoveListener!=null){//这里是之后我们要实现交互用的,限制先忽略
this.myWheelMoveListener.onValueChanged(this.xPosition,this.yPosition);
}
invalidate(); if(event.getAction()==){//如果点击释放后
isClicked=false;//设置为未点击状态
this.yPosition=this.centerY;//按钮归于圆心
this.xPosition=this.centerX;
if(this.myWheelMoveListener!=null){
this.myWheelMoveListener.onValueChanged(this.xPosition,this.yPosition);
}
invalidate();//重新绘图
}
return true;
}

到这里我们的按钮已经可以用了,但是我们还需要实现控件与外部的交互所以我们要定义接口,如下

public void setOnMyWheelMoveListener(OnMyWheelMoveListener listener){    //设置交互事件
this.myWheelMoveListener=listener;
}
public static abstract interface OnMyWheelMoveListener {
public abstract void onValueChanged(int xDistance, int yDistance);
}

下面看看完整的代码

public class Wheel extends View implements View.OnTouchListener{
int xPosition;
int yPosition;
int centerX;
int centerY;
int mainRadius;
int secondRadius;
boolean isClicked;
OnMyWheelMoveListener myWheelMoveListener; public Wheel(Context context) {
super(context);
} public Wheel(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
} public Wheel(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
isClicked=false;
} @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width =MeasureSpec.getMode(widthMeasureSpec)==MeasureSpec.UNSPECIFIED?:MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED?:MeasureSpec.getSize(heightMeasureSpec);
if(width>height){
width=height;
}else{
height=width;
}
this.mainRadius=(getWidth()-)/;
this.secondRadius=mainRadius/*;
setMeasuredDimension(width, height);
this.centerX=getWidth()/;
this.centerY=getHeight()/;
this.xPosition=centerX;
this.yPosition=centerY;
} @Override
protected void onDraw(Canvas canvas){
Bitmap bm;
Paint BackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
BackgroundPaint.setFilterBitmap(true);
BackgroundPaint.setDither(true); Paint circlePaint=new Paint();
circlePaint.setColor(Color.parseColor("#52c1bd"));
circlePaint.setStyle(Paint.Style.FILL);
if(!isClicked){
bm=((BitmapDrawable)getResources().getDrawable(R.mipmap.circle1)).getBitmap();
}
else {
bm = ((BitmapDrawable) getResources().getDrawable(R.mipmap.circle)).getBitmap();
}
Rect mSrcRect = new Rect(, , bm.getWidth(), bm.getHeight());
Rect mDestRect = new Rect(, ,getWidth()-, getHeight()-);
canvas.drawBitmap(bm,mSrcRect,mDestRect,BackgroundPaint);
canvas.drawCircle(this.xPosition,this.yPosition,secondRadius,circlePaint); } @Override
public boolean onTouch(View v, MotionEvent event) {
return false;
} @Override
public boolean onTouchEvent(MotionEvent event){
isClicked=true;
this.xPosition = (int) event.getX();
this.yPosition = (int) event.getY();
if(Math.sqrt((this.xPosition-this.centerX)*(this.xPosition-this.centerX)+(this.yPosition-this.centerY)*(this.yPosition-this.centerY))>mainRadius){
double Yrate=(this.yPosition-this.centerY)/Math.sqrt((this.xPosition-this.centerX)*(this.xPosition-this.centerX)+(this.yPosition-this.centerY)*(this.yPosition-this.centerY));
double Xrate=(this.xPosition-this.centerX)/Math.sqrt((this.xPosition-this.centerX)*(this.xPosition-this.centerX)+(this.yPosition-this.centerY)*(this.yPosition-this.centerY));
this.yPosition=(int)(mainRadius*Yrate)+this.centerY;
this.xPosition=(int)(mainRadius*Xrate)+this.centerX;
}
if(this.myWheelMoveListener!=null){
this.myWheelMoveListener.onValueChanged(this.xPosition,this.yPosition);
}
invalidate(); if(event.getAction()==){
isClicked=false;
this.yPosition=this.centerY;
this.xPosition=this.centerX;
if(this.myWheelMoveListener!=null){
this.myWheelMoveListener.onValueChanged(this.xPosition,this.yPosition);
}
invalidate();
}
return true;
} public void setOnMyWheelMoveListener(OnMyWheelMoveListener listener){
this.myWheelMoveListener=listener;
}
public static abstract interface OnMyWheelMoveListener {
public abstract void onValueChanged(int xDistance, int yDistance);
}
}

到这里自定义简单方向盘控件就基本实现了,下面看看具体使用

布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.drw.myapplication.MainActivity"
android:background="#fff"> <com.drw.myapplication.Wheel
android:id="@+id/myWheel"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/> </RelativeLayout>

主类

public class MainActivity extends AppCompatActivity {
Wheel wheel;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
wheel=(Wheel)findViewById(R.id.myWheel);
tv=(TextView)findViewById(R.id.tv);
wheel.setOnMyWheelMoveListener(new Wheel.OnMyWheelMoveListener() {//设置交互事件
@Override
public void onValueChanged(int xDistance, int yDistance) {
tv.setText(""+xDistance+","+yDistance);
}
});
}
}

执行的效果图如下

     

Android 自定义简易的方向盘操作控件的更多相关文章

  1. Android 自定义View之自绘控件

    首先要提前声明一下,我对于自定义View的理解并不是很深,最近啃了几天guolin博主写的关于自定义View的博客,讲的非常棒,只不过涉及到源码和底层的一些东西,我自己就懵逼了,目前只是会了关于自定义 ...

  2. Android判断Touch为滑动事件还是操作控件

    Android判断Touch为滑动事件还是操作控件 因为在项目中要判断WebView是否处于滚动状态,但它不像ListView有onScrollStateChanged方法来监听,要实现就得手动监听它 ...

  3. Android support library支持包常用控件介绍(二)

    谷歌官方推出Material Design 设计理念已经有段时间了,为支持更方便的实现 Material Design设计效果,官方给出了Android support design library ...

  4. Android开发技巧——自定义控件之组合控件

    Android开发技巧--自定义控件之组合控件 我准备在接下来一段时间,写一系列有关Android自定义控件的博客,包括如何进行各种自定义,并分享一下我所知道的其中的技巧,注意点等. 还是那句老话,尽 ...

  5. (转载)Android UI设计之AlertDialog弹窗控件

    Android UI设计之AlertDialog弹窗控件 作者:qq_27630169 字体:[增加 减小] 类型:转载 时间:2016-08-18我要评论 这篇文章主要为大家详细介绍了Android ...

  6. (转载) Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框

    Android 带清除功能的输入框控件ClearEditText,仿IOS的输入框 标签: Android清除功能EditText仿IOS的输入框 2013-09-04 17:33 70865人阅读  ...

  7. winform 跨线程操作控件

    当进行winform的开发时,经常遇到用时比较久的操作,在传统的单线程程序中,用户必须等待这个耗时操作完成以后才能进行下一步的操作,这个时候,多线程编程就派上用场了,将这个耗时的操作放到一个新的子线程 ...

  8. WinForm中新开一个线程操作 窗体上的控件(跨线程操作控件)

    最近在做一个winform的小软件(抢票的...).登录窗体要从远程web页面获取一些数据,为了不阻塞登录窗体的显示,开了一个线程去加载数据远程的数据,会报一个错误"线程间操作无效: 从不是 ...

  9. WinForm中跨线程操作控件

    在WinForm编程时会遇到通过后台线程操作界面的情况,直接在后台线程执行的方法中直接操作控件会报错,这时候就要使用跨线程方式间接操作控件.下面是两种实现方式.   1.采用定义delegate的方式 ...

随机推荐

  1. git 远程库和url

    我们使用 git remote add origin <url> 来关联远程主机,这个origin就是关联的远程主机名,如果我们想同时关联两个远程主机,我们可以用 git remote a ...

  2. 在windows上部署使用Redis--资料整理

    声明:一下只是针对windows系统,其他系统资料需自己补全. 很简单:下载.安装.安装桌面管理工具.测试.细不具表,下面几个网址应该足以解决你的所有问题. 网址访问专用Host: http://pa ...

  3. java容器类1:Collection,List,ArrayList,LinkedList深入解读

    1. Iterable 与 Iterator Iterable 是个接口,实现此接口使集合对象可以通过迭代器遍历自身元素. public interface Iterable<T> 修饰符 ...

  4. C# 委托进阶

    本文参考自:https://wenku.baidu.com/view/41ab91d3c1c708a1284a44d7.html?qq-pf-to=pcqq.c2c 1.为什么委托定义的返回值通常为v ...

  5. MySQL 连接注意事项

    外连接 A LEFT JOIN B join_condition 数据表B的结果集依赖数据表A 数据表A的结果集根据左连接条件依赖所有数据表 B表除外). 左外连接条件决定如何检索数据表B(在没有指定 ...

  6. Javascript显示和隐式类型转换

    1.转换成字符串 多数的JavaScript宿主环境(比如Node.js和Chrome)都提供了全局函数toString: 与此同时Object.prototype也定义了toString方法,使得所 ...

  7. Struts2 ongl内存结构

    valuestack是OgnlValueStack的实现,而OgnlValueStack是基于ValueStack的实现 valuestack的内存结构为: 里面主要的为:context和root r ...

  8. Executors多线程

    介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异步任务你还只是如下new T ...

  9. tomcat+nginx+redis集群搭建并解决session共享问题。

    1 集群搭建 https://www.cnblogs.com/yjq520/p/7685941.html 2 session共享 https://blog.csdn.net/tuesdayma/art ...

  10. redis数据类型(四)list类型

    一.list类型 list是一个链表结构,可以理解为一个每个子元素都是 string 类型的双向链表. 主要功能是push.pop.获取一个范围的所有值等. 操作中key理解为链表的名字. 二.Lis ...