###说明:文章来自《Android群英传》学习笔记
###重写View来实现全新的控件
当Android系统原生的控件无法满足我们的需求时,我们就可以完全创建一个新的自定义View来实现需要的功能。创建一个自定义的View,难点在于绘制控件和实现交互。通常自定义View需要继承View类,并重写他的onDraw(),onMeasure()方法来实现绘制逻辑,同时通过重写onTouchEvent()等触控事件来实现交互逻辑。
下面通过一个案列,介绍一下自定义View:
先看一下效果图:
首先我们分析一下这个图的组成部分,这个图分三个部分,分为中间的圆形,中间显示的文字和外圈的弧形,所以我们只需要在onDraw()方法中去绘制这些图形就可以了,看过之前的文章,相应这些单独图形的绘制是非常容易的事,只是在这里进行一下组合。
代码如下:
public class CustomCircleView extends View {
float mCircleX,mCircleY;//定义圆心位置
float mSweepVlae; //旋转角度
float mRadius;//圆的半径
String mShowText;//显示的文字
float mShowTextSize;//显示文字的Size
Paint mCirclePaint;//圆心画笔
Paint mArcPaint;//弧形画笔
Paint mTextPaint;//文字画笔
RectF mArcRectF ;//弧形的外接矩形
Context context;
public CustomCircleView(Context context) {
super(context);
this.context = context;
}
public CustomCircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
//获取屏幕宽度
private float getWidthPixels(Context context){
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(outMetrics);
int width = outMetrics.widthPixels;
return width;
}
//获取屏幕高度
private float getHeightPixels(Context context){
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(outMetrics);
int height = outMetrics.heightPixels;
return height;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float heightPixels = getHeightPixels(context);
//mCircleX = getWidthPixels(context) /2;
mCircleY = heightPixels/2 - 100 ;
mRadius = (float) (mCircleY*0.6/2);
Log.i("zdd","mRadius=="+mRadius);
//定义弧形矩形框
mArcRectF = new RectF((float)(heightPixels*0.3) ,(float)(heightPixels*0.3) -100,
(float)(heightPixels*0.7),(float)(heightPixels*0.7) -100);
//定义圆形Paint
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(true);
mCirclePaint.setColor(Color.GREEN);
//定义弧形Paint
mArcPaint = new Paint();
mArcPaint.setAntiAlias(true);
mArcPaint.setColor(Color.BLUE);
mArcPaint.setStrokeWidth(35);//设置线宽
mArcPaint.setStyle(Paint.Style.STROKE);//设置空心
//定义文字Paint
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(Color.RED);
mTextPaint.setTextSize(mShowTextSize);
//绘制椭圆
canvas.drawArc(mArcRectF,270,mSweepVlae,false,mArcPaint);
canvas.drawCircle(mCircleY+100,mCircleY,mRadius,mCirclePaint);
canvas.drawText(mShowText,mCircleY-mShowText.length()*6+100,mCircleY+(mShowTextSize/4),mTextPaint);
}
//设置圆弧比例
public void setmSweepVlae(float sweepVlae){
if (sweepVlae < 0){
mSweepValue = 0 ;
}else{
//最大角度不能超过360,这里有个360的满角度和零角度的问题,大家可以根据自己的需求来单独处理
mSweepValue = sweepVlae % 360;
}
setShowText(numberFormat(mSweepValue));
this.invalidate();
}
//设置显示的文字
public void setShowText(String text){
mShowText = text +“%”;
}
//设置显示的文字的大小
public void setShowTextSize(float textSize){
mShowTextSize = textSize;
}
}
调用方法如下:
public class MainActivity extends AppCompatActivity {
CustomCircleView circleView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
circleView = (CustomCircleView) findViewById(R.id.circle);
circleView.setmSweepVlae(220);
circleView.setShowTextSize(52);
}
}
接下来我们给自定义的View 添加触摸事件,在外狐滑动时,实时刷新所占比,和动态显示圆中的文字,
这里我们重写View的onTouchEvent方法来实现我们滑动的动作:
@Override
public boolean onTouchEvent(MotionEvent event) {
float dx,dy;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
L.i("ACTION_DOWN");
//这里最好用 getX(),getY()方法,跟我们上边获取屏幕的方法对应上
dx = event.getX();
dy = event.getY();
L.i("dx==="+String.valueOf(dx));
L.i("dy==="+String.valueOf(dy));
//判断是否点击在外弧形内,在圆弧内,我们返回true,拦截触摸事件。
if (Math.abs(Math.hypot(Math.abs(dx-mCircleX),Math.abs(dy-mCircleY))-mRadius2) <= 60){
L.i("in Arc true");
return true;
}else {
L.i("in Arc false");
return false;
}
case MotionEvent.ACTION_MOVE:
L.i("ACTION_MOVE");
float currentX = event.getX();
float currentY = event.getY();
if (Math.abs(Math.hypot(Math.abs(currentX-mCircleX),Math.abs(currentY-mCircleY))-mRadius2) <= 60){
//如果在圆弧内才执行滑动动作
//分4个区域分别求角度,这里我用Math.atan方法求弧度,方法建议画坐标系,会更好理解
//如果大家有什么好的方便,快捷的方法,欢迎留言。
if(currentX > mCircleX && currentY < mCircleY){//第一象限
mSweepValue = (float) (Math.atan((currentX-mCircleX)/(mCircleY-currentY))/Math.PI*180);
}else if (currentX < mCircleX && currentY < mCircleY){//第二象限
mSweepValue = 360 - (float) (Math.atan((mCircleX-currentX)/(mCircleY-currentY))/Math.PI*180);
}else if (currentX < mCircleX && currentY < mCircleY){//第三象限
mSweepValue = 180 + (float) (Math.atan((mCircleX-currentX)/(currentY-mCircleY))/Math.PI*180);
}else{//第三象限
mSweepValue = 180 - (float) (Math.atan((currentX-mCircleX)/(currentY-mCircleY))/Math.PI*180);
}
setShowText(numberFormat(mSweepValue));//根据我们触摸的位置,刷新中间显示的百分比
this.invalidate();//刷新
L.i("mSweepValue==="+mSweepValue);
}
break;
case MotionEvent.ACTION_UP:
L.i("ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
显示中间百分比,我这里保留了一个小数,代码如下:
//转换数值,保留一位小数
public String numberFormat(float value){
DecimalFormat decimalFormat = new DecimalFormat(".#");
return String.valueOf(decimalFormat.format(value/360*100));
}
欢迎大家留言讨论。