1.自定义控件的步骤
2.构造函数有什么用
3.自定义属性有什么用
4.初始化一些画笔放在哪里
5.onmesure()如何写
6.invalide源码分析
1.写一个类继承view或者其他控件
public class CustomView extends View {
}
2.在xml中定义自定义的view
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_root_ll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.sigestudio.customviewdemo.views.CustomView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
3.写构造方法:
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
为什么要重写构造函数?
大致意思是无法解析我们的CustomView类找不到方法,为什么呢?我们在xml文件引用我们的CustomView类时为其指定了两个android自带的两个属性:layout_width和layout_height,当我们需要使用类似的属性(比如更多的什么id啊、padding啊、margin啊之类)时必须在自定义View的构造方法中添加一个AttributeSet类型的签名来解析这些属性:
网上有很多关于三个构造函数使用时机的说法,但是说法正确的却没有几家,这里正式的给大家科普一下:
- 在代码中直接new一个Custom View实例的时候,会调用第一个构造函数.这个没有任何争议.
- 在xml布局文件中调用Custom View的时候,会调用第二个构造函数.这个也没有争议.
- 在xml布局文件中调用Custom View,并且Custom View标签中还有自定义属性时,这里调用的还是第二个构造函数.
也就是说,系统默认只会调用Custom View的前两个构造函数,至于第三个构造函数的调用,通常是我们自己在构造函数中主动调用的(例如,在第二个构造函数中调用第三个构造函数).
我们自己怎么写?
第一个调用滴第二个,第二个调用第三个
public SportHeadView(Context context) {
super(context);
Log.d("SportHeadView","SportHeadView one");
init();
}
public SportHeadView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
Log.d("SportHeadView","SportHeadView two");
init();
}
public SportHeadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.d("SportHeadView","SportHeadView three");
init();
}
没有使用自定义属性:
SportHeadView: SportHeadView two
不知道传啥,用这个!
SportHeadView sportHeadViewTest=new SportHeadView(MainActivity.this);
4.重写ondraw()方法
Android也给我们提供了这两样东西:Paint和Canvas,一个是画笔而另一个呢当然是画布啦~~,我们可以看到在onDraw方法中,画布Canvas作为签名被传递进来,也就是说这个画布是Android为我们准备好的
---------------------
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setAntiAlias(true);
}
设置了抗锯齿(一种让图像边缘显得更圆滑光泽动感的碉堡算法):setAntiAlias(true)
5.CustomView的代码让其实现Runnable接口
实现Runnable接口还是在子线程,所以更新ui用:postInvalidate()
public class CustomView extends View implements Runnable {
private Paint mPaint;// 画笔
private Context mContext;// 上下文环境引用
private int radiu;// 圆环半径
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
// 初始化画笔
initPaint();
}
/**
* 初始化画笔
*/
private void initPaint() {
// 实例化画笔并打开抗锯齿
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/*
* 设置画笔样式为描边,圆环嘛……当然不能填充不然就么意思了
*
* 画笔样式分三种:
* 1.Paint.Style.STROKE:描边
* 2.Paint.Style.FILL_AND_STROKE:描边并填充
* 3.Paint.Style.FILL:填充
*/
mPaint.setStyle(Paint.Style.STROKE);
// 设置画笔颜色为浅灰色
mPaint.setColor(Color.LTGRAY);
/*
* 设置描边的粗细,单位:像素px
* 注意:当setStrokeWidth(0)的时候描边宽度并不为0而是只占一个像素
*/
mPaint.setStrokeWidth(10);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制圆环
canvas.drawCircle(MeasureUtil.getScreenSize((Activity) mContext)[0] / 2, MeasureUtil.getScreenSize((Activity) mContext)[1] / 2, radiu, mPaint);
}
@Override
public void run() {
/*
* 确保线程不断执行不断刷新界面
*/
while (true) {
try {
/*
* 如果半径小于200则自加否则大于200后重置半径值以实现往复
*/
if (radiu <= 200) {
radiu += 10;
// 刷新View
invalidate();
} else {
radiu = 0;
}
// 每执行一次暂停40毫秒
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
总结:.动画的原理:
大家如果了解动画的原理就会知道,一个动画是由无数张连贯的图片构成的,这些图片之间快速地切换再加上我们眼睛的视觉暂留给我们造成了在“动”的假象。
再提供一个setter方法对外设置半径值,并在设置了该值后调用invalidate()方法重绘View:
注意的点:
3个构造方法:前面2个最终调用到第3个构造函数
里面的单位是多少?px,所以自定义是如何适配的
onMeasure:宽度和高度不一致
测量模式:int mode=MeasureSpec.getMode(widthMeasureSpec);//通过宽可以获取测量模式,通过高也可以获取测量模式,因为它们不一样。
描边的宽度,要注意
动态产生差值的办法:handler,动画
自定义属性,希望不要乱传入值,有什么办法?
注解的办法
自定义属性要注意什么问题?
有重复的属性,和别人的冲突,编译的时候报错
一些注意的东西:
测量:一般是自己的东西+padding()
测量文字的宽和高。和基线
bottom:正值,top,负值,相对于基线来说,FontMetrics的这几个变量的值都是以baseline为基准的
公式:(bottom-top)/2-bottom+centerY
// 测量文字的宽高
Rect textBounds = new Rect();
mPaint.getTextBounds(mStep, 0, mStep.length(), textBounds);
int dx = (getWidth() - textBounds.width()) / 2;
// 获取画笔的FontMetrics
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
// 计算文字的基线
int baseLine = (int) (getHeight() / 2 + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom);
// 绘制步数文字
canvas.drawText(mStep, dx, baseLine, mPaint);
圆弧效果:
// 设置为 ROUND
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Jo
自定义控件的步骤!extent view
1.三个构造方法
2.自定义属性
3.xml设置自定义属性
4.onMeaseure
5.onDraw
6.设置一些外包调用方法
每个方法写完,绘制一步一步。就要先调试下,然后下一步。
自定义viewGroup:
1.构造函数
2.不需要属性
3.onMeasure
4.onLayout
分析:
2.1:画-背景圆弧
2.2:画-当前进度圆弧
2.3:画-步数文字
2.4:提供一些方法
2.5:动画效果
2.1. 画-背景圆弧
@Override
protected void onDraw(Canvas canvas) {
// 2.画背景大圆弧
int centerX = mViewWidth / 2;
int centerY = mViewHeight / 2;
// 设置圆弧画笔的宽度
mPaint.setStrokeWidth(mRoundWidth);
// 设置为 ROUND
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
// 设置画笔颜色
mPaint.setColor(mRoundColor);
mPaint.setStyle(Paint.Style.STROKE);
// 半径
int radius = (int) (centerX - mRoundWidth);
RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
// 画背景圆弧
canvas.drawArc(oval, mStartAngle, mSweepAngle, false, mPaint);
}
2.2:画-当前进度圆弧
// 画进度圆弧
mPaint.setColor(mProgressColor);
// 计算当前百分比
float percent = (float) mProgressStep/mMaxStep;
// 根据当前百分比计算圆弧扫描的角度
canvas.drawArc(oval, mStartAngle, percent*mSweepAngle, false, mPaint);
2.3:画-步数文字
// 重置画笔
mPaint.reset();
mPaint.setAntiAlias(true);
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
String mStep = ((int)(percent*mMaxStep)) + "";
// 测量文字的宽高
Rect textBounds = new Rect();
mPaint.getTextBounds(mStep, 0, mStep.length(), textBounds);
int dx = (getWidth() - textBounds.width()) / 2;
// 获取画笔的FontMetrics
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
// 计算文字的基线
int baseLine = (int) (getHeight() / 2 + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom);
// 绘制步数文字
canvas.drawText(mStep, dx, baseLine, mPaint);
来源: https://www.jianshu.com/p/4e0eb9bb09ab
2.4. 提供一些方法
// 设置当前最大步数
public synchronized void setMaxStep(int maxStep) {
if (max < 0) {
throw new IllegalArgumentException("max 不能小于0!");
}
this.mMaxStep = maxStep;
}
public synchronized int getMaxStep() {
return mMaxStep;
}
// 设置进度
public synchronized void setProgress(int progress) {
if (progress < 0) {
throw new IllegalArgumentException("progress 不能小于0!");
}
this.progress = progress;
// 重新刷新绘制 -> onDraw()
invalidate();
}
public synchronized int getProgress() {
return progress;
}
完整的代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final SportHeadView sportHeadView=findViewById(R.id.tv_my);
handler=new Handler(getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
progress+=100;
sportHeadView.setProgress(progress);
handler.sendEmptyMessageDelayed(0,200);
}
};
handler.sendEmptyMessage(0);
}
int progress=100;
Handler handler;
}
public class SportHeadView extends View {
int mViewWidth;
int mViewHeight;
int mRoundWidth=20;
int mRoundColor=R.color.colorAccent;
int mStartAngle=130;
int mSweepAngle=270;
public SportHeadView(Context context) {
super(context);
init();
}
public SportHeadView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public SportHeadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/***
* 初始化,在3个参数里面初始化。感觉还是会奔溃
*/
private void init() {
mPaint=new Paint();
mPaint.setAntiAlias(true);
}
/***
* 我们要求是画一个正方形
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int mode=MeasureSpec.getMode(widthMeasureSpec);//
int width= MeasureSpec.getSize(widthMeasureSpec);
int height=MeasureSpec.getSize(heightMeasureSpec);
if(width>height){
width=height;
}else {
height=width;
}
mViewWidth=width;
mViewHeight=mViewWidth;
setMeasuredDimension(width,height);
}
Paint mPaint;
@Override
protected void onDraw(Canvas canvas) {
// 2.画背景大圆弧
int centerX = mViewWidth / 2;
int centerY = mViewHeight / 2;
// 设置圆弧画笔的宽度
mPaint.setStrokeWidth(mRoundWidth);
// 设置为 ROUND
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
// 设置画笔颜色
mPaint.setColor(mRoundColor);
mPaint.setStyle(Paint.Style.STROKE);
// 半径
int radius = (int) (centerX - mRoundWidth);
RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
// 画背景圆弧
canvas.drawArc(oval, mStartAngle, mSweepAngle, false, mPaint);
// 画进度圆弧
mPaint.setColor(mProgressColor);
// 计算当前百分比
float percent = (float) mProgressStep/mMaxStep;
// 根据当前百分比计算圆弧扫描的角度
canvas.drawArc(oval, mStartAngle, percent*mSweepAngle, false, mPaint);
// 重置画笔
mPaint.reset();
mPaint.setAntiAlias(true);
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
String mStep = ((int)(percent*mMaxStep)) + "";
// 测量文字的宽高
Rect textBounds = new Rect();
mPaint.getTextBounds(mStep, 0, mStep.length(), textBounds);
int dx = (getWidth() - textBounds.width()) / 2;
// 获取画笔的FontMetrics
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
// 计算文字的基线
int baseLine = (int) (getHeight() / 2 + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom);
// 绘制步数文字
canvas.drawText(mStep, dx, baseLine, mPaint);
}
// 设置当前最大步数
public synchronized void setMaxStep(int maxStep) {
if (max < 0) {
throw new IllegalArgumentException("max 不能小于0!");
}
this.mMaxStep = maxStep;
}
public synchronized int getMaxStep() {
return mMaxStep;
}
// 设置进度
public synchronized void setProgress(int progress) {
if (progress < 0) {
throw new IllegalArgumentException("progress 不能小于0!");
}
this.progress = progress;
this.mProgressStep=this.progress;
// 重新刷新绘制 -> onDraw()
invalidate();
}
public synchronized int getProgress() {
return progress;
}
int max=3000;
int progress;
int mTextSize=40;
int mTextColor=R.color.colorPrimary;
int mMaxStep=3000;
int mProgressStep=0;
int mProgressColor=R.color.colorPrimaryDark;
}