【Android 进阶之路】自定义View控件
2016-12-25 本文已影响353人
雁归来兮
- 时间:2016年12月25日
- 设备:Android 7.0 小米4
- 需求:自定义一个齿轮
- 地点:安师大秋实园
本博客内容一致同步到本人的博客站点:http://www.zhoutaotao.xyz 欢迎访问留言交流
前言
+尽管安卓SDK提供了非常丰富的控件供大家使用,但是在一些特殊的场合,仍然不能够满足需求,所以我们要学会自定义符合自己的需求,在此学习了一些简单的心得,代码比较简单,分享一下自己的心得,并记录学习过程。由于是新手,如果错误的理解,多谢指正,我会立即改正,以免误导他人。谢谢!
- 这个需求是看了 http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0606/3001.html
这篇文章之后觉得不错,自己整理思路,不过好像方法不同,以后再做慢慢研究吧,有兴趣的可以看看。
预期效果
简单的才静态齿轮效果,可调节颜色,齿轮半径。先做个静态的,后面学习扎实了,再做个动态的齿轮,效果图如下:
QQ拼音截图未命名.png由于我是初次接触自定义View组件,在记录的过程中,难免有误解出现,敬请告知和谅解。
步骤
一、 设置属性内容
二、定义类并继承View
三、编写相应的方法
四、在布局中使用
正文
- 废话不多说,开始主题
设置属性内容
1、在资源文件目录value中新建资源文件attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--齿轮颜色颜色-->
<attr name="grarColor" format="color"></attr>
<!--齿轮半径 单位:dp-->
<attr name="gearRadius" format="dimension"></attr>
<!--这是声明属性 name的值要和自定义View的java类名一致,后面就会看到-->
<!--把上面的属性给复制下来,移除format属性-->
<declare-styleable name="Gear">
<!--齿轮颜色颜色-->
<attr name="grarColor"></attr>
<!--齿轮半径 单位:dp-->
<attr name="gearRadius"></attr>
</declare-styleable>
</resources>
这里的name比较重要,并非随意的命名,需要和其java文件相匹配,后面会用到,里面有三个属性,很简单的声明。
定义类并继承View
- 新建Java文件Gear.java,文件名要和第一步的name一致,否则不能使用的
- 其构造函数有四个,由于刚接触,没有深入研究,这里就不说区别了,一般我们使用两个参数的那一个
public class Gear extends View {
public Gear(Context context) {
super(context);
}
public Gear(Context context, AttributeSet attrs) {
super(context, attrs);
//我们一般使用这一个,当然也可以使用第三个构造函数
}
public Gear(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public Gear(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}
- 构造完成之后,需要获取在xml布局文件传入的值
//声明全局变量,保存属性参数
private int gearColor;
private float gearRadius;
private float gearLength;
//画笔
private Paint paint;
public Gear(Context context, AttributeSet attrs) {
super(context, attrs);
//获取属性集合
TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.Gear);
//为全局变量赋值
//获取颜色,第一位参数属性,第二个是默认值,注意是8位的16进制数,前两位为不透明度,要写上,不可省略
gearColor=typedArray.getColor(R.styleable.Gear_grarColor,0xFF000000);
//获取半径,后面的为默认值,由于传入的数据单位是dp,需要转化为px
gearRadius=typedArray.getDimension(R.styleable.Gear_gearRadius,dp2px(0));
//从属性集中获取属性完成之后一定要回收
typedArray.recycle();
//初始化画笔
paint=new Paint();
//设置画笔的宽度
paint.setStrokeWidth(dp2px(4));
}
public float dp2px(int dpValue){
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpValue,getResources().getDisplayMetrics());
}
编写相应的方法
-
测量的方法onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width=getRealValue(widthMeasureSpec);
//由于布局中的宽度存在三种类型,精确的数据,包含,和占用父布局,所以我们需要处理数据
int height=getRealValue(heightMeasureSpec);
//计算完成后,调用此方法,传入实际的需要宽度和高度
setMeasuredDimension(width,height);
}
public int getRealValue(int value){
int mode=MeasureSpec.getMode(value);//得到数据的模式,模式占用32位数据的前两位
int size=MeasureSpec.getSize(value);//得到系统给的数值
int result=0;
if (mode==MeasureSpec.EXACTLY) {//如果是精准的数值的话,就使用系统的值
result=size;
}else {
result= (int) paint.descent();
//如果比包含的话,使用画笔的数据,也就是包含了,这之前一定要调用paint.setStrokeWidth(float width)
if (mode==MeasureSpec.AT_MOST)//占用父布局的话
{
result=size;
}
}
return result;
}
-
测量的方法onMeasure()
当然onDraw()之前还有一个方法
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
//onLayout方法是ViewGroup中子View的布局方法,用于放置子View的位置。
//放置子View很简单,只需在重写onLayout方法,然后获取子View的实例
//调用子View的layout方法实现布局。
//在实际开发中,一般要配合onMeasure测量方法一起使用。
//由于这个Demo很简单,Layout就不做处理了,有兴趣的自行研究学习。
+下面看重点onDraw()方法,我们进行绘制,首先使用数学工具分析我们的需求:
QQ拼音截图未命名.png- 先画大圆,半径为R,圆心位置为中心
- 在画小圆,半径为R/3,圆心位置为中心
- 在画圆心,并且连接齿轮上的某一点
- 其次画齿轮,圆周分60份,要认识到在特殊的点,比如0,5,10,15,20,25,30的长度略高于其他长度
- 齿轮本质为线,只要确定起点和终点即可,我们尝试着来计算。
- 起点:(x+RSin a,y+Rcos a)
- 终点 : (x+ (R+l)sin a,y+(R+l)cos a) 其中l为齿轮的长度,在特殊的点略大点
下面看代码
@Overrideprotected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//圆心位置
int circle_x = getWidth() / 2;
int circle_y = getHeight() / 2;
//设置画笔颜色
paint.setColor(gearColor);
//设置画笔为绘制边框
paint.setStyle(Paint.Style.STROKE);
//设置线宽
paint.setStrokeWidth(dp2px(4));
//绘制大圆形
canvas.drawCircle(circle_x, circle_y, dp2px((int) gearRadius), paint);
//绘制小圆,半径为大圆形的1/3
canvas.drawCircle(circle_x, circle_y, dp2px((int) gearRadius) / 3, paint);
//绘制圆心点
canvas.drawPoint(circle_x, circle_y, paint);
//绘制齿轮
//1/60刻度的弧度值
float kedu = (float) (2 * Math.PI / 60);
paint.setStrokeWidth(dp2px(3));
for (int i = 0; i < 60; i++) {
int len = 18;
if (i % 5 == 0)
len = 30;
float start_X = (float) (circle_x + dp2px((int) gearRadius) * Math.sin(kedu * i));
float start_Y = (float) (circle_y + dp2px((int) gearRadius) * Math.cos(kedu * i));
float end_X = (float) (circle_x + (dp2px((int) gearRadius) + len) * Math.sin(kedu * i));
float end_Y = (float) (circle_y + (dp2px((int) gearRadius) + len) * Math.cos(kedu * i));
canvas.drawLine(start_X, start_Y, end_X, end_Y, paint);
if (i==6)
canvas.drawLine(circle_x,circle_y,start_X,start_Y,paint);
}
}
在布局中使用
- 很简单的使用方法,需要在根布局中设置一个属性,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<!--在布局中加入 xmlns:tools="http://schemas.android.com/apk/res-auto"-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<cn.app.hybord.tao.myapplication.Gear
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:gearRadius="50dp"
tools:grarColor="#FFFF00FF"
/>
</RelativeLayout>
- 效果图
附录:动态设置颜色和半径
- 思路:在自定义View中写入公有的方法,在代码中绑定后调用,然后调用相应的设置方法进行重绘
- 布局文件:
<?xml version="1.0" encoding="utf-8"?>
<!--在布局中加入 xmlns:tools="http://schemas.android.com/apk/res-auto"-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:orientation="vertical"
android:padding="15dp"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit_raduis"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="请输入半径长度"
/>
<Button
android:id="@+id/btn_draw"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="重新绘制"
/>
<cn.app.hybord.tao.myapplication.Gear
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:gearRadius="50dp"
tools:grarColor="#FFFF00FF"
/>
</LinearLayout>
- Gear.java中的方法
public void setGearColor(int gearColor) {
//重新赋值
this.gearColor = gearColor;
//重绘
invalidate();
//备注:子线程重绘
// postInvalidate();
}
public void setGearRadius(float gearRadius) {
//如上注释
this.gearRadius = gearRadius;
invalidate();
}
- Activity中的代码文件
public class MainActivity extends AppCompatActivity {
private EditText radius;
private Button reDraw;
private Gear gear;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
radius= (EditText) findViewById(R.id.edit_raduis);
reDraw= (Button) findViewById(R.id.btn_draw);
gear= (Gear) findViewById(R.id.gear);
reDraw.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//仅仅写了修改半径,如果修改其他的类似的方法
gear.setGearRadius(Float.parseFloat(radius.getText().toString().trim()));
}
});
}
}
效果
默认半径【也就是xml布局中的文件设置的半径】
morenbanjiang.pngRadius=20
20.pngRadius=150
150.png本博客内容一致同步到本人的博客站点:http://www.zhoutaotao.xyz 欢迎访问留言交流