美文网首页Android安卓自定义View
【Android 进阶之路】自定义View控件

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

作者: 雁归来兮 | 来源:发表于2016-12-25 13:27 被阅读353次
    • 时间: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 欢迎访问留言交流

    相关文章

      网友评论

      本文标题:【Android 进阶之路】自定义View控件

      本文链接:https://www.haomeiwen.com/subject/yovhvttx.html