最近在做一款交互性較為復雜的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?100:MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED?100:MeasureSpec.getSize(heightMeasureSpec); if(width>height){//將自定義控件的區域限制為正方形 width=height; }else{ height=width; } this.mainRadius=(getWidth()-100)/2;//給主要半徑賦值 this.secondRadius=mainRadius/5*2;//賦值可點擊的圓形按鈕的半徑 setMeasuredDimension(width, height); this.centerX=getWidth()/2;//確定中心 this.centerY=getHeight()/2; 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(0, 0, bm.getWidth(), bm.getHeight());//設置原始圖像中要被畫出來的區域 Rect mDestRect = new Rect(30, 30,getWidth()-30, getHeight()-30);//設置目標區域中會被畫進去圖像的區域 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()==1){//如果點擊釋放后 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?100:MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getMode(heightMeasureSpec)==MeasureSpec.UNSPECIFIED?100:MeasureSpec.getSize(heightMeasureSpec); if(width>height){ width=height; }else{ height=width; } this.mainRadius=(getWidth()-100)/2; this.secondRadius=mainRadius/5*2; setMeasuredDimension(width, height); this.centerX=getWidth()/2; this.centerY=getHeight()/2; 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(0, 0, bm.getWidth(), bm.getHeight()); Rect mDestRect = new Rect(30, 30,getWidth()-30, getHeight()-30); 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()==1){ 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); } }); } }
執行的效果圖如下