【Android 进阶之路】自定义View控件

  • 时间:2016年12月25日
  • 设备:Android 7.0 小米4
  • 需求:自定义一个齿轮
  • 地点:安师大秋实园

本博客内容一致同步到本人的博客站点:http://www.zhoutaotao.xyz 欢迎访问留言交流

前言

+尽管安卓SDK提供了非常丰富的控件供大家使用,但是在一些特殊的场合,仍然不能够满足需求,所以我们要学会自定义符合自己的需求,在此学习了一些简单的心得,代码比较简单,分享一下自己的心得,并记录学习过程。由于是新手,如果错误的理解,多谢指正,我会立即改正,以免误导他人。谢谢!

预期效果

简单的才静态齿轮效果,可调节颜色,齿轮半径。先做个静态的,后面学习扎实了,再做个动态的齿轮,效果图如下:

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>
  • 效果图
QQ拼音截图未命名.png

附录:动态设置颜色和半径

  • 思路:在自定义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.png

Radius=20

20.png

Radius=150

150.png

本博客内容一致同步到本人的博客站点:http://www.zhoutaotao.xyz 欢迎访问留言交流

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容