如何自定义圆弧按钮?

作者: GuaKin_Huang | 来源:发表于2016-05-16 22:55 被阅读1561次

    Android


    番茄时钟工作法,有没有人认识这小不点的,哈哈,我来给大家解释下什么叫番茄时钟工作法。不过一看就知道是和番茄有关的,什么,你不信,哈哈,我也不信,告诉大家吧!简单来说嘛就是一套时间管理方法,选择一个一个你待完成的任务,例如打飞机。。。(嘘嘘嘘。。。),将番茄时钟,也就是一个计时器而已,设为25分钟,专注打飞机,专注打飞机。。。(打飞机这活中途不能停哦)直到番茄时钟响起就ok了。就这么简单。

    喂喂喂,跑题啦!这次我们说的是圆弧按钮哦,也就图1-1这叼样。好,那咋们就进入正题,再说多一句,这番茄时钟工作法挺有用的,对于小编我这种定力不足的人来说,哈哈,犹如看着打码的片子突然变无码了,哈哈哈哈。。。

    图1-1

    咋们先来分析下。。。

    咋一看去这东西是不是一个圆环呢,啊啊啊,五环,你比四环多一环,哈哈,小岳岳的歌终于被我用上了。既然是一个圆环,那肯定是由一个外圆和一个内圆组成的吧,中间呢,就是一个类似textView的东西吗?好,有这思路,那我们可以这样弄,先画两个半径不一的圆,再重叠在一起,(可以通过ImageView来弄),让后添加一个textView就大功告成了,这就是自定义控件中通过组合已有的控件来达到自定义控件的效果,这种方法比较简单,哈哈,都说程序员追求简单,但这也太没技术含量了,咱也是想装B的人,好下面就介绍点有技术含量的东西。

    这东西所包含的元素,什么外圆,内圆,文字,咱们统统通过android的canvas画出来,哈哈,够逼格没,不过喜欢装逼的人,得先承受得起挫折,鄙视。。。

    好,开始了!

    咱们把上面一整块东西就看成个变了模样的Button,本来是眉清目秀的小姑娘,要变得如此惊艳,那得彻头彻尾地变啊,哈哈,那咱就自定义一个Button嘛,想她变成哪个美女据变成哪个美女,嘻嘻嘻。。。什么,自定义控件,搞啥子勒,别慌别慌,这东西入门是相当容易的,好,咱就开始入门自定义控件。

    什么是自定义控件呢?

    根据我的经验,虽然我没啥经验哈,这东西还是挺有用的,你要在手机上弄一些酷炫叼炸天的控件时,那大部分得用到自定义控件的,这东西呢,说复杂,那可以复杂到你头痛,复杂到你要去乖乖地拿起大学的高数,线性代数好好学;说简单呢,就简单的继承、重写,照着模板来就好,还是能弄个不错的东西出来的,拿来骗骗学弟学妹也是可以的哦!

    控件的简单继承关系

    控件继承

    由上图可知,所有的控件都是继承自View 的,也就是说,View 是鼻祖,对于自定义控件来说,继承的祖先越大,灵活性就越好,越能创造出酷炫叼炸天的控件,但同时,难度也会有所增加,这次咱们就继承View ,然后开始酷炫叼炸天之旅行。

    自定义控件的三种形式

    1. 继承已有的控件来实现自定义控件;
    2. 通过继承一个布局文件来实现自定义控件;
    3. 通过继承View类来实现自定义控件。

    • 新建CircleButton并继承View,并添加三个构造方法,重写OnDraw() 方法:
     /*构造方法*/
        public CircleButton(Context context) {
            this(context, null);
        }
    
        public CircleButton(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public CircleButton(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView(context, attrs);//初始化界面,后面有具体实现
        }
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
        }
    
    • 既然是自定义控件,那归根到底,它还是一个控件嘛,和ButtonTextView 可都是亲兄弟嘛,兄弟间必然是有相似的特征的,例如属性,点击事件。。。那下面咱们就来自定义属性先。咱们最终做出来的结果就是下图这逼:
    效果图

    那这里包括哪些自定义属性呢?一个是:中间文字的颜色(textColor)、文字的大小(textSize)、文字的内容(text);另一个是:圆环的颜色(backgroundColor)。

    好,那咱们就自定义这些属性,该如何定义呢?别急,首先在res/values 下新建atts.xml 文件(前提是该目录下不存在该文件),然后就开始自定义上面的属性了

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <!--name:属性的名字  format:该属性可以拥有的数据类型-->
        <declare-styleable name="CircleButton">
            <attr name="backgroundColor" format="color"/>
            <attr name="textSize" format="dimension"/>
            <attr name="text" format="string"/>
            <attr name="textColor" format="color"/>
        </declare-styleable>
    </resources>
    

    既然定义好了属性,那就得用他们,接下来就是获取自定义属性并赋值,在这里可以通过上下文的obtainStyledAttributes() 方法获取 :

    private int backgroundColor;
    private float textSize;
    private String text;
    private int textColor;
    ......
    ......
    ......
    
     /*获取自定义属性并赋值*/
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleButton);
    
            backgroundColor = typedArray.getColor(R.styleable.CircleButton_backgroundColor, Color.RED);
            textColor = typedArray.getColor(R.styleable.CircleButton_textColor, Color.WHITE);
            textSize = typedArray.getDimension(R.styleable.CircleButton_textSize, 80);
            text = typedArray.getString(R.styleable.CircleButton_text);
    

    这里需要注意的是通过R.styleable.xxx 来找到属性的,因为在定义属性的时候就声明了
    <declare-styleable name="CircleButton">

    在获取完后,可以通过typedArray.recycle();进行回收,优化性能。

    • 定义完属性又获取到属性了,那下面就是去应用这些属性了。好,那咱们就先画圆,这得在OnDraw() 内画了,开始展示泡妞的功力了。

    既然要画画,那是不是得需要画笔和画纸啊,总不能在哪淫想嘛,那样妹子早就跟别人跑了,这画笔和画纸就是传说中的PaintCanvas 了。好,咱们就拿来一根画笔Paint mPaint = new Paint(),先挑个颜色先mPaint.setColor(backgroundColor),为了画出细节,咱们需要挑一个笔尖比较细的画笔mPaint.setStrokeWidth(4),这画笔还分风格的,mPaint.setStyle(Paint.Style.STROKE),好了,画笔搞好了,接下来就是在画纸上画画了canvas.drawArc(10, 10, getWidth()-10, getHeight()-10, -90, -360+mAngle, true, mPaint), OK,打工搞成,详细代码在后面会给出。

    这样就把圆给画出来了,下面就是画中间的文字了,同理,需要先配置好画笔,然后再在画纸上画画,不过这里需要注意的是,我们的先确认文字的起始点和长度,这样才能画出好看的文字可通过以下代码获取:

    mPaint.getTextBounds(text, 0, text.length(), mRect);
    int textWidth = mRect.width();
    int textHeight = mRect.height();
    

    接下来就是在画纸上画画
    canvas.drawText(text, (getWidth()-textWidth) / 2, (getHeight()+textHeight) / 2, mPaint);


    好,到这里为止,已经把一个静态的控件构建好了,接下来就可以在布局中调用了,咱们在acytivity_main.xml 调用吧:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:circleBtn="http://schemas.android.com/apk/res-auto"
        android:background="@color/colorPrimary"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.nlte.test_diyview.MainActivity">
    
        <com.nlte.test_diyview.CircleButton
            android:id="@+id/btn_circle_gray"
            android:layout_gravity="center"
            android:layout_width="200dp"
            android:layout_height="200dp"
            circleBtn:backgroundColor="#b0adad"
            circleBtn:textSize="30dp"
            circleBtn:textColor="@android:color/holo_red_light" />
    
        <com.nlte.test_diyview.CircleButton
            android:id="@+id/btn_circle_red"
            android:layout_gravity="center"
            android:layout_width="200dp"
            android:layout_height="200dp"
            circleBtn:backgroundColor="#ff0202"
            circleBtn:text="20"
            circleBtn:textSize="30dp"
            circleBtn:textColor="@android:color/holo_red_light" />
    
    </FrameLayout>
    
    

    为了后面的方便,在这里我们用FrameLayout, 并在里面添加的颜色不同的两个CircleButton控件,在这里需要注意的是,引用自定义控件是需要添加下面代码:xmlns:circleBtn="http://schemas.android.com/apk/res-auto"circleBtn 可以自由定义。

    到这里可以说是大功告成了,但这只是一个静态的就控件,没有交互能力,既然如此,那我们还不如用张图片代替就ok了,还高搞这么麻烦干嘛,别急别急,咱们接下来开始让他活起来,简单粗暴点,给他添加一个点击事件,当点击按钮一次,就让里面的数字减一。

    好的,接下来开始了!。。。

    简单够粗暴,咱们可以直接在CircleButton添加点击事件,并将里面的数字减一,然后再重新刷新视图(修改视图时,一般需要刷新视图),代码如下(在initView()方法内添加):

    this.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                   number--;
                    //刷新视图
                   invalidate();
                }
            });
    

    但咱们不能这样子做,咱们是懒人,为了下次在其他地方也能够用这样一个控件,并根据需要来进行不同的时间处理,咱们可以通过接口回调的方法来实现调用者对控件事件的出处理,什么,接口回调,不懂啊,哈哈,悄悄告诉你,我也是半懂半懂,也是套着模板来的:

        private CirclebuttonClickListener mListener;
        public interface CirclebuttonClickListener{
            void circleButtonClick();
        }
        public void setOnCirclebuttonClickListener(CirclebuttonClickListener listener){
            mListener = listener;
        }
    
    1. 首先定义一个接口 interface CirclebuttonClickListener
    2. 创建接口变量 CirclebuttonClickListener mListener
    3. 暴露一个方法给调用者来注册接口回调,通过接口来获得回调者对接口方法的实现

    然后刚才的代码就可以改成:

            this.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mListener.circleButtonClick();
                }
            });
    

    是不是没那么复杂呢?

    接下来就就交给别人处理了,自己的任务完成了,(听着咋那么像卖身呢)

    好,别人该如何用呢,其实和普通控件添加事件一样,该怎么弄就怎么弄:

    private CircleButton mCircleButtonRed;
    mCircleButtonRed = (CircleButton) findViewById(R.id.btn_circle_red);
    mCircleButtonRed.setOnCirclebuttonClickListener(new CircleButton.CirclebuttonClickListener() {
                @Override
                public void circleButtonClick() {
                   //事件处理
                }
            });
    

    ok,大功告成,l来个效果图

    效果图

    接下来就是完整的代码

    MainActivity.java

    package com.nlte.test_diyview;
    
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    
    public class MainActivity extends AppCompatActivity {
        private CircleButton mCircleButtonGray;
        private CircleButton mCircleButtonRed;
        private static int number = 69;
        private float angle = 0;
        private float EachAngle = 0;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mCircleButtonRed = (CircleButton) findViewById(R.id.btn_circle_red);
            mCircleButtonRed.setText(number+"");
    
            //必须要将360*1.0 / number转为float或者double,这样可以避免因为取整的问题而导致没有完全平分360度
            EachAngle = Float.parseFloat(String.valueOf(360*1.0 / number));
    
            mCircleButtonRed.setOnCirclebuttonClickListener(new CircleButton.CirclebuttonClickListener() {
                @Override
                public void circleButtonClick() {
                    mCircleButtonRed.setText(--number+"");
                    angle += EachAngle;
                    System.out.println(angle);
                    mCircleButtonRed.setAngle(angle);
                }
            });
    
        }
    }
    
    

    CircleButton

    package com.nlte.test_diyview;
    
    import android.annotation.TargetApi;
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Rect;
    import android.graphics.Typeface;
    import android.os.Build;
    import android.util.AttributeSet;
    import android.view.View;
    
    /**
     * Founction:
     * NLTE
     * 2016/5/15 0015
     */
    public class CircleButton extends View {
    
        private Paint mPaint;
        private Rect mRect;
        private float mAngle;//弧的角度
    
        private int backgroundColor;
        private float textSize;
        private String text;
        private int textColor;
    
        /*通过接口回调的方式实现控件的点击事件添加和处理
       * 步骤:
       *   1.首先定义一个接口 interface CirclebuttonClickListener
       *   2.创建接口变量  CirclebuttonClickListener mListener
       *   3.暴露一个方法给调用者来注册接口回调,通过接口来获得回调者对接口方法的实现
       *   */
        private CirclebuttonClickListener mListener;
        public interface CirclebuttonClickListener{
            void circleButtonClick();
        }
        public void setOnCirclebuttonClickListener(CirclebuttonClickListener listener){
            mListener = listener;
        }
    
        /*构造方法*/
        public CircleButton(Context context) {
            this(context, null);
        }
    
        public CircleButton(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public CircleButton(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView(context, attrs);
        }
    
        /**
         * Founction:Init the View
         * NLTE
         * 2016/5/15
         * @param context
         * @param attrs
         */
        private void initView(Context context, AttributeSet attrs) {
            mPaint = new Paint();
            //给Paint加上抗锯齿标志
            mPaint.setAntiAlias(true);
            mRect = new Rect();
            this.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    //方法一:
                   /* number--;
                    //刷新视图
                    invalidate();*/
    
                    //方法二:
                    mListener.circleButtonClick();
                }
            });
    
            /*获取自定义属性并赋值*/
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleButton);
    
            backgroundColor = typedArray.getColor(R.styleable.CircleButton_backgroundColor, Color.RED);
            textColor = typedArray.getColor(R.styleable.CircleButton_textColor, Color.WHITE);
            textSize = typedArray.getDimension(R.styleable.CircleButton_textSize, 80);
            text = typedArray.getString(R.styleable.CircleButton_text);
            if (text == null){
                text = "";
            }
            //回收,避免浪费
            typedArray.recycle();
    
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            //画圆
            mPaint.setColor(backgroundColor);
            mPaint.setStrokeWidth(4); //设置圆弧的宽度
            mPaint.setStyle(Paint.Style.STROKE); //设置圆弧
    //        canvas.drawCircle(getWidth()/2, getHeight()/2, getWidth()/2-15, mPaint);
            /*
            * 第一 二个参数:暂且理解为圆的外切正方形左上角的坐标
            * 第三 四个参数:暂且理解为圆的外切正方形右下角的坐标
            * 第五 六个参数:绘制的起始点和终点
            * 第七个参数:是否绘制扇形
            * 第八个参数:画笔
            * */
            canvas.drawArc(10, 10, getWidth()-10, getHeight()-10, -90, -360+mAngle, true, mPaint);
    
            //中间有一个白色的数字 mRect是数字四周的边距
            mPaint.setColor(textColor);
            mPaint.setTextSize(textSize);
            mPaint.setTypeface(Typeface.DEFAULT);
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setStrokeWidth(0.1f);
            mPaint.getTextBounds(text, 0, text.length(), mRect);
            int textWidth = mRect.width();
            int textHeight = mRect.height();
            canvas.drawText(text, (getWidth()-textWidth) / 2, (getHeight()+textHeight) / 2, mPaint);
        }
    
        public int getBackgroundColor() {
            return backgroundColor;
        }
    
        @Override
        public void setBackgroundColor(int backgroundColor) {
            invalidate();
            this.backgroundColor = backgroundColor;
        }
    
        public float getTextSize() {
            return textSize;
        }
    
        public void setTextSize(float textSize) {
            invalidate();
            this.textSize = textSize;
        }
    
        public String getText() {
            return text;
        }
    
        public void setText(String text) {
            invalidate();
            this.text = text;
        }
    
        public int getTextColor() {
            return textColor;
        }
    
        public void setTextColor(int textColor) {
            invalidate();
            this.textColor = textColor;
        }
    
        public void setAngle(float angle) {
            mAngle = angle;
            invalidate();
        }
    }
    
    

    atts.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <!--name:属性的名字  format:该属性可以拥有的数据类型-->
        <declare-styleable name="CircleButton">
            <attr name="backgroundColor" format="color"/>
            <attr name="textSize" format="dimension"/>
            <attr name="text" format="string"/>
            <attr name="textColor" format="color"/>
        </declare-styleable>
    </resources>
    

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:circleBtn="http://schemas.android.com/apk/res-auto"
        android:background="@color/colorPrimary"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.nlte.test_diyview.MainActivity">
    
        <com.nlte.test_diyview.CircleButton
            android:id="@+id/btn_circle_gray"
            android:layout_gravity="center"
            android:layout_width="200dp"
            android:layout_height="200dp"
            circleBtn:backgroundColor="#b0adad"
            circleBtn:textSize="30dp"
            circleBtn:textColor="@android:color/holo_red_light" />
    
        <com.nlte.test_diyview.CircleButton
            android:id="@+id/btn_circle_red"
            android:layout_gravity="center"
            android:layout_width="200dp"
            android:layout_height="200dp"
            circleBtn:backgroundColor="#ff0202"
            circleBtn:text="20"
            circleBtn:textSize="30dp"
            circleBtn:textColor="@android:color/holo_red_light" />
    
    </FrameLayout>
    
    

    ps:如有错误或不足或需要优化的地方,请多多指教,不胜感激!!

    相关文章

      网友评论

      本文标题:如何自定义圆弧按钮?

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