Android开发Android开发经验谈Android自定义View

Android 自定义进度条

2018-12-06  本文已影响4人  死磕自己

效果

国际惯例,效果图奉上

在这里插入图片描述

目录

在这里插入图片描述

前言

写在前面,由于之前其实已经写了部分自定义View的方法,所以本来应该按照之前的系列,来进行下载暂停动画进度条,但是我把之前的圆形进度条和开始暂停动画效果合并后,出现了一点小问题,让我发现之前写的自定义View,没有使我真正的了解自定义View,那么我觉得还是有很大的问题;那么之后依旧会努力的写自定义View,初步先写静态的自定义View,之后,加上动画的自定义VIew,后续在加上相应的触摸事件,努力的把自定义VIew的方法方式全部给记录下来;努力的融汇贯通。

正文

HorizontalProgressBarWithNumber

横向的进度条;里面有详细的注释

  1. 首先继承自ProgressBar,这个是对基础的ProgressBar 进行进一步的自定义,

    public class HorizontalProgressBarWithNumber extends ProgressBar{
        ******
    }
    
  2. 当我们想要自定义VIew的时候,先要构思效果,那么就要设置各种属性,如下

/**
     * 设置各种默认值
     */
    private static final int DEFAULT_TEXT_SIZE = 10;
    private static final int DEFAULT_TEXT_COLOR = 0XFFFC00D1;
    private static final int DEFAULT_COLOR_UNREACHED_COLOR = 0xFFd3d6da;
    private static final int DEFAULT_HEIGHT_REACHED_PROGRESS_BAR = 2;
    private static final int DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR = 2;
    private static final int DEFAULT_SIZE_TEXT_OFFSET = 10;

/**
     * painter of all drawing things  所有画图所用的画笔
     */
    protected Paint mPaint = new Paint();
    /**
     * color of progress number  进度号码的颜色
     */
    protected int mTextColor = DEFAULT_TEXT_COLOR;
    /**
     * size of text (sp)  文本的大小
     */
    protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE);

    /**
     * offset of draw progress  进度条文本补偿宽度
     */
    protected int mTextOffset = dp2px(DEFAULT_SIZE_TEXT_OFFSET);

    /**
     * height of reached progress bar  进度条高度 
     */
    protected int mReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_REACHED_PROGRESS_BAR);

    /**
     * color of reached bar   成功的文本颜色
     */
    protected int mReachedBarColor = DEFAULT_TEXT_COLOR;
    /**
     * color of unreached bar 未完成的bar颜色
     */
    protected int mUnReachedBarColor = DEFAULT_COLOR_UNREACHED_COLOR;
    /**
     * height of unreached progress bar  未覆盖的进度条高度
     */
    protected int mUnReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR);
    /**
     * view width except padding  除padding外的视图宽度
     */
    protected int mRealWidth;

    protected boolean mIfDrawText = true;

    protected static final int VISIBLE = 0;

  1. 自定义构造函数
    public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs,
            int defStyle)
    {
        super(context, attrs, defStyle);
        obtainStyledAttributes(attrs);//初始化参数
        mPaint.setTextSize(mTextSize);//文本大小
        mPaint.setColor(mTextColor);//文本颜色
    }
  1. 接下来就是初始化的代码:
/**
     * get the styled attributes  获取属性的样式
     * 
     * @param attrs
     */
    private void obtainStyledAttributes(AttributeSet attrs)
    {
        // init values from custom attributes
        final TypedArray attributes = getContext().obtainStyledAttributes(
                attrs, R.styleable.HorizontalProgressBarWithNumber);

        mTextColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_text_color,
                        DEFAULT_TEXT_COLOR);
        mTextSize = (int) attributes.getDimension(
                R.styleable.HorizontalProgressBarWithNumber_progress_text_size,
                mTextSize);

        mReachedBarColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_reached_color,
                        mTextColor);
        mUnReachedBarColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_unreached_color,
                        DEFAULT_COLOR_UNREACHED_COLOR);
        mReachedProgressBarHeight = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_reached_bar_height,
                        mReachedProgressBarHeight);
        mUnReachedProgressBarHeight = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_unreached_bar_height,
                        mUnReachedProgressBarHeight);
        mTextOffset = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_text_offset,
                        mTextOffset);

        int textVisible = attributes
                .getInt(R.styleable.HorizontalProgressBarWithNumber_progress_text_visibility,
                        VISIBLE);
        if (textVisible != VISIBLE)
        {
            mIfDrawText = false;
        }
        attributes.recycle();
    }

  1. 上面当中的参数和属性都在attr.xml中声明的,如下
<resources>

   <declare-styleable name="HorizontalProgressBarWithNumber">
       <attr name="progress_unreached_color" format="color" />
       <attr name="progress_reached_color" format="color" />
       <attr name="progress_reached_bar_height" format="dimension" />
       <attr name="progress_unreached_bar_height" format="dimension" />
       <attr name="progress_text_size" format="dimension" />
       <attr name="progress_text_color" format="color" />
       <attr name="progress_text_offset" format="dimension" />
       <attr name="progress_text_visibility" format="enum">
           <enum name="visible" value="0" />
           <enum name="invisible" value="1" />
       </attr>
   </declare-styleable>
   
   <declare-styleable name="RoundProgressBarWidthNumber">
       <attr name="radius" format="dimension" />
   </declare-styleable>

</resources>
  1. 接下来,我们要重写onMeasure方法,在其中获取父控件传递过来的参数

     protected synchronized void onMeasure(int widthMeasureSpec,
             int heightMeasureSpec)
     {
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = measureHeight(heightMeasureSpec);//高度
         setMeasuredDimension(width, height);//必须调用该方法来存储View经过测量的到的宽度和高度
    
         mRealWidth = getMeasuredWidth() - getPaddingRight() - getPaddingLeft();//真正的宽度值是减去左右padding
     }
    
  2. 其中measureHeight() 方法是获取视图的高度,视图的样式中拥有进度条和文本,要判断文本的高度和进度条的高度;


    /**
     *  EXACTLY:父控件告诉我们子控件了一个确定的大小,你就按这个大小来布局。比如我们指定了确定的dp值和macth_parent的情况。 
     *  AT_MOST:当前控件不能超过一个固定的最大值,一般是wrap_content的情况。 
     *  UNSPECIFIED:当前控件没有限制,要多大就有多大,这种情况很少出现。
     * @param measureSpec  
     * @return  视图的高度
     */
    private int measureHeight(int measureSpec)
    {
        
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);//父布局告诉我们控件的类型
        int specSize = MeasureSpec.getSize(measureSpec);//父布局传过来的视图大小
        if (specMode == MeasureSpec.EXACTLY)
        {
            result = specSize;
        } else
        {
            /**
             * mPaint.descent() 最高点的高度
             * mPaint.ascent() 最低点的高度
             */
            float textHeight = (mPaint.descent() - mPaint.ascent());// 设置文本的高度
            /**
             * Math.abs() 返回绝对值
             *  Math.max 返回最大值
             *  Math.min 返回最小值
             */
            result = (int) (getPaddingTop() + getPaddingBottom() + Math.max(
                    Math.max(mReachedProgressBarHeight,
                            mUnReachedProgressBarHeight), Math.abs(textHeight)));
            if (specMode == MeasureSpec.AT_MOST)
            {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }
  1. 以上全部完成后,就可以开始onDraw()方法了;
    
    /**
     * 开始画
     */
    @Override
    protected synchronized void onDraw(Canvas canvas)
    {

        canvas.save();
        /**
         * 设置偏移后的坐标原点 以原来为基础上偏移后, 例如: (100,100), translate(1,1), 坐标原点(101,101);
         */
        canvas.translate(getPaddingLeft(), getHeight() / 2);
        boolean noNeedBg = false;
        float radio = getProgress() * 1.0f / getMax();//设置进度
        float progressPosX = (int) (mRealWidth * radio);//设置当前进度的宽度
        String text = getProgress() + "%";//设置文本
//       mPaint.getTextBounds(text, 0, text.length(), mTextBound);

        float textWidth = mPaint.measureText(text);//返回文本的宽度
        float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;//设置文本的高度

        if (progressPosX + textWidth > mRealWidth)
        {//当文本和当前进度的宽度大于bar的宽度时
            progressPosX = mRealWidth - textWidth;
            noNeedBg = true;
        }

        // draw reached bar   画出bar
        float endX = progressPosX - mTextOffset / 2;//绘制已到达的进度
        if (endX > 0)
        {//绘制已到达的进度
            mPaint.setColor(mReachedBarColor);
            mPaint.setStrokeWidth(mReachedProgressBarHeight);
            canvas.drawLine(0, 0, endX, 0, mPaint);
        }
        // draw progress bar
        // measure text bound
        if (mIfDrawText)
        {//绘制文本
            mPaint.setColor(mTextColor);
            canvas.drawText(text, progressPosX, -textHeight, mPaint);
        }

        // draw unreached bar
        if (!noNeedBg)
        {//绘制未到达的进度条
            float start = progressPosX + mTextOffset / 2 + textWidth;
            mPaint.setColor(mUnReachedBarColor);
            mPaint.setStrokeWidth(mUnReachedProgressBarHeight);
            canvas.drawLine(start, 0, mRealWidth, 0, mPaint);
        }
        canvas.restore();
    }
  1. 在分享两个转换的方法
/**
     * dp 2 px
     * 
     * @param dpVal
     */
    protected int dp2px(int dpVal)
    {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }

    /**
     * sp 2 px
     * 
     * @param spVal
     * @return
     */
    protected int sp2px(int spVal)
    {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());

    }
  1. 最后自定义View 全部实现,奉上全部代码
public class HorizontalProgressBarWithNumber extends ProgressBar
{

    /**
     * 设置各种默认值
     */
    private static final int DEFAULT_TEXT_SIZE = 10;
    private static final int DEFAULT_TEXT_COLOR = 0XFFFC00D1;
    private static final int DEFAULT_COLOR_UNREACHED_COLOR = 0xFFd3d6da;
    private static final int DEFAULT_HEIGHT_REACHED_PROGRESS_BAR = 2;
    private static final int DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR = 2;
    private static final int DEFAULT_SIZE_TEXT_OFFSET = 10;

    
    /**
     * painter of all drawing things  所有画图所用的画笔
     */
    protected Paint mPaint = new Paint();
    /**
     * color of progress number  进度号码的颜色
     */
    protected int mTextColor = DEFAULT_TEXT_COLOR;
    /**
     * size of text (sp)  文本的大小
     */
    protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE);

    /**
     * offset of draw progress  进度条文本补偿宽度
     */
    protected int mTextOffset = dp2px(DEFAULT_SIZE_TEXT_OFFSET);

    /**
     * height of reached progress bar  进度条高度 
     */
    protected int mReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_REACHED_PROGRESS_BAR);

    /**
     * color of reached bar   成功的文本颜色
     */
    protected int mReachedBarColor = DEFAULT_TEXT_COLOR;
    /**
     * color of unreached bar 未完成的bar颜色
     */
    protected int mUnReachedBarColor = DEFAULT_COLOR_UNREACHED_COLOR;
    /**
     * height of unreached progress bar  未覆盖的进度条高度
     */
    protected int mUnReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR);
    /**
     * view width except padding  除padding外的视图宽度
     */
    protected int mRealWidth;

    protected boolean mIfDrawText = true;

    protected static final int VISIBLE = 0;

    public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs,
            int defStyle)
    {
        super(context, attrs, defStyle);
        obtainStyledAttributes(attrs);//初始化参数
        mPaint.setTextSize(mTextSize);//文本大小
        mPaint.setColor(mTextColor);//文本颜色
    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec,
            int heightMeasureSpec)
    {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = measureHeight(heightMeasureSpec);//高度
        setMeasuredDimension(width, height);//必须调用该方法来存储View经过测量的到的宽度和高度

        mRealWidth = getMeasuredWidth() - getPaddingRight() - getPaddingLeft();//真正的宽度值是减去左右padding
    }


    /**
     *  EXACTLY:父控件告诉我们子控件了一个确定的大小,你就按这个大小来布局。比如我们指定了确定的dp值和macth_parent的情况。 
     *  AT_MOST:当前控件不能超过一个固定的最大值,一般是wrap_content的情况。 
     *  UNSPECIFIED:当前控件没有限制,要多大就有多大,这种情况很少出现。
     * @param measureSpec  
     * @return  视图的高度
     */
    private int measureHeight(int measureSpec)
    {
        
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);//父布局告诉我们控件的类型
        int specSize = MeasureSpec.getSize(measureSpec);//父布局传过来的视图大小
        if (specMode == MeasureSpec.EXACTLY)
        {
            result = specSize;
        } else
        {
            /**
             * mPaint.descent() 最高点的高度
             * mPaint.ascent() 最低点的高度
             */
            float textHeight = (mPaint.descent() - mPaint.ascent());// 设置文本的高度
            /**
             * Math.abs() 返回绝对值
             *  Math.max 返回最大值
             *  Math.min 返回最小值
             */
            result = (int) (getPaddingTop() + getPaddingBottom() + Math.max(
                    Math.max(mReachedProgressBarHeight,
                            mUnReachedProgressBarHeight), Math.abs(textHeight)));
            if (specMode == MeasureSpec.AT_MOST)
            {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    /**
     * get the styled attributes  获取属性的样式
     * 
     * @param attrs
     */
    private void obtainStyledAttributes(AttributeSet attrs)
    {
        // init values from custom attributes
        final TypedArray attributes = getContext().obtainStyledAttributes(
                attrs, R.styleable.HorizontalProgressBarWithNumber);

        mTextColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_text_color,
                        DEFAULT_TEXT_COLOR);
        mTextSize = (int) attributes.getDimension(
                R.styleable.HorizontalProgressBarWithNumber_progress_text_size,
                mTextSize);

        mReachedBarColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_reached_color,
                        mTextColor);
        mUnReachedBarColor = attributes
                .getColor(
                        R.styleable.HorizontalProgressBarWithNumber_progress_unreached_color,
                        DEFAULT_COLOR_UNREACHED_COLOR);
        mReachedProgressBarHeight = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_reached_bar_height,
                        mReachedProgressBarHeight);
        mUnReachedProgressBarHeight = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_unreached_bar_height,
                        mUnReachedProgressBarHeight);
        mTextOffset = (int) attributes
                .getDimension(
                        R.styleable.HorizontalProgressBarWithNumber_progress_text_offset,
                        mTextOffset);

        int textVisible = attributes
                .getInt(R.styleable.HorizontalProgressBarWithNumber_progress_text_visibility,
                        VISIBLE);
        if (textVisible != VISIBLE)
        {
            mIfDrawText = false;
        }
        attributes.recycle();
    }

    
    /**
     * 开始画
     */
    @Override
    protected synchronized void onDraw(Canvas canvas)
    {

        canvas.save();
        /**
         * 设置偏移后的坐标原点 以原来为基础上偏移后, 例如: (100,100), translate(1,1), 坐标原点(101,101);
         */
        canvas.translate(getPaddingLeft(), getHeight() / 2);
        boolean noNeedBg = false;
        float radio = getProgress() * 1.0f / getMax();//设置进度
        float progressPosX = (int) (mRealWidth * radio);//设置当前进度的宽度
        String text = getProgress() + "%";//设置文本
//       mPaint.getTextBounds(text, 0, text.length(), mTextBound);

        float textWidth = mPaint.measureText(text);//返回文本的宽度
        float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;//设置文本的高度

        if (progressPosX + textWidth > mRealWidth)
        {//当文本和当前进度的宽度大于bar的宽度时
            progressPosX = mRealWidth - textWidth;
            noNeedBg = true;
        }

        // draw reached bar   画出bar
        float endX = progressPosX - mTextOffset / 2;//设置文本补偿宽度
        if (endX > 0)
        {
            mPaint.setColor(mReachedBarColor);
            mPaint.setStrokeWidth(mReachedProgressBarHeight);
            canvas.drawLine(0, 0, endX, 0, mPaint);
        }
        // draw progress bar
        // measure text bound
        if (mIfDrawText)
        {
            mPaint.setColor(mTextColor);
            canvas.drawText(text, progressPosX, -textHeight, mPaint);
        }

        // draw unreached bar
        if (!noNeedBg)
        {
            float start = progressPosX + mTextOffset / 2 + textWidth;
            mPaint.setColor(mUnReachedBarColor);
            mPaint.setStrokeWidth(mUnReachedProgressBarHeight);
            canvas.drawLine(start, 0, mRealWidth, 0, mPaint);
        }

        canvas.restore();

    }

    /**
     * dp 2 px
     * 
     * @param dpVal
     */
    protected int dp2px(int dpVal)
    {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, getResources().getDisplayMetrics());
    }

    /**
     * sp 2 px
     * 
     * @param spVal
     * @return
     */
    protected int sp2px(int spVal)
    {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, getResources().getDisplayMetrics());

    }
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRealWidth = w - getPaddingRight() - getPaddingLeft();
    }

}

实现

  1. xml界面
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:zhy="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

<com.zhy.view.HorizontalProgressBarWithNumber
            android:id="@+id/id_progressbar01"
            android:layout_width="300dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dip"
            android:padding="5dp" />
            
</ScrollView>
  1. mainActivity() 实现
public class MainActivity extends Activity {
private HorizontalProgressBarWithNumber mProgressBar;

private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            int progress = mProgressBar.getProgress();
            mProgressBar.setProgress(++progress);
            if (progress >= 100) {
                mHandler.removeMessages(MSG_PROGRESS_UPDATE);
            }
            mHandler.sendEmptyMessageDelayed(MSG_PROGRESS_UPDATE, 100);
        };
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mProgressBar = (HorizontalProgressBarWithNumber) findViewById(R.id.id_progressbar01);
        mHandler.sendEmptyMessage(MSG_PROGRESS_UPDATE);
    }

  1. 以上就是全部功能的实现了

总结

自定义View

  1. 先构思效果
  2. 根据效果 , 声明配置相应参数
  3. 想好怎么计算View的宽度和高度
  4. 如果画出来
  5. 开始做吧

感谢

这个是从鸿阳大神的Github上找到的例子,嗯,值得学习,感谢鸿阳大神;

上一篇下一篇

猜你喜欢

热点阅读