欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android自定义View:时钟和圆百分比图--(2)

程序员文章站 2024-02-05 20:44:10
...
  • 自定义时钟
/**
 * 绘制一个钟表的View 
 * 1.时分秒的转动 画完后 需要执行
 * canvas.save(); 
 * canvas.restore(); 才能重新把画布旋转回来
 * @author fanshenxia
 * 
 */
public class MyClockView extends View {

    private Context mContext;
    private Paint mPaint;
    private int mScreenWidth;

    private int mHour = 0;
    private int mMinute = 0;
    private int mSecond = 0;
    private int[] mNums = null;

    public MyClockView(Context context) {
        super(context);
        this.mContext = context;
        init();
    }

    public MyClockView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        init();
    }

    public MyClockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(3);
        mPaint.setStyle(Style.STROKE);
        mPaint.setTextSize(12);
        mPaint.setTextAlign(Align.CENTER);
        //ScreenUtil.getScreenWidth(mContext)获取屏幕宽度
        mScreenWidth = ScreenUtil.getScreenWidth(mContext);
        mNums = new int[] { 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setStyle(Style.STROKE);
        // 绘制中间的点
        canvas.drawCircle(mScreenWidth / 2, mScreenWidth / 4 + 100, 3, mPaint);
        // 绘制外层圆
        canvas.drawCircle(mScreenWidth / 2, mScreenWidth / 4 + 100, mScreenWidth / 4, mPaint);
        mPaint.setStyle(Style.FILL);
        // 绘制刻度
        for (int i = 0; i < 12; i++) {
            canvas.save();
            //这是长点的刻度
            if (i == 0 || i == 3 || i == 6 || i == 9) {
                //每次绘制前需要重新设置宽度
                mPaint.setStrokeWidth(5);
                canvas.drawLine(mScreenWidth / 2, 100, mScreenWidth / 2, 130, mPaint);
            } else {
                mPaint.setStrokeWidth(3);
                canvas.drawLine(mScreenWidth / 2, 100, mScreenWidth / 2, 120, mPaint);
            }
            canvas.drawText("" + mNums[i], mScreenWidth / 2, 70, mPaint);
            // 旋转画布,每次旋转15度,以mScreenWidth / 2, mScreenWidth / 4为中心点旋转
            canvas.rotate(30, mScreenWidth / 2, mScreenWidth / 4 + 100);
        }
        canvas.save();
        // 恢复画布
        canvas.restore();
        // 绘制时钟和分钟 这里将画布平移
        // canvas.translate(mScreenWidth / 2, mScreenWidth / 4);
        // 获取当前时间
        getSystemTime();

        canvas.rotate(6 * mSecond, mScreenWidth / 2, mScreenWidth / 4 + 100);
        // 秒
        canvas.drawLine(mScreenWidth / 2, mScreenWidth / 4 + 100, mScreenWidth / 2, 150, mPaint);
        canvas.save();
        canvas.restore();
        canvas.rotate(-6 * mSecond, mScreenWidth / 2, mScreenWidth / 4 + 100);

        canvas.rotate(6 * mMinute + mSecond / 10, mScreenWidth / 2, mScreenWidth / 4 + 100);
        // 分
        canvas.drawLine(mScreenWidth / 2, mScreenWidth / 4 + 100, mScreenWidth / 2, 180, mPaint);
        canvas.save();
        canvas.restore();
        canvas.rotate(-6 * mMinute - mSecond / 10, mScreenWidth / 2, mScreenWidth / 4 + 100);

        canvas.rotate(mHour * 30 + mMinute / 2, mScreenWidth / 2, mScreenWidth / 4 + 100);
        // 时
        canvas.drawLine(mScreenWidth / 2, mScreenWidth / 4 + 100, mScreenWidth / 2, 200, mPaint);
        canvas.save();
        canvas.restore();
        canvas.rotate(-(mHour * 30 + mMinute / 2), mScreenWidth / 2, mScreenWidth / 4 + 100);
        //绘制后延时1s刷新
        postInvalidateDelayed(1 * 1000);
    }

    /**
     * 获取系统时间时分秒
     */
    @SuppressLint("NewApi")
    private void getSystemTime() {
        Calendar calendar = Calendar.getInstance();
        mHour = calendar.get(Calendar.HOUR_OF_DAY);
        mMinute = calendar.get(Calendar.MINUTE);
        mSecond = calendar.get(Calendar.SECOND);

        // SimpleDateFormat format = new SimpleDateFormat("HH-mm-ss");
        // String time = format.format(new Date(System.currentTimeMillis()));
        // String[] split = time.split("-");
        // mHour = Integer.parseInt(split[0]);
        // mMinute = Integer.parseInt(split[1]);
        // mSecond = Integer.parseInt(split[2]);
    }

}

运行效果
Android自定义View:时钟和圆百分比图--(2)

  • 自定义百分比图
/**
 * 自定义一个控件  写这些是一步步来的
 * 自定义显示百分比的控件 RectF是绘制一个矩形 左上右下的各个位置
 * 1.本来画了两个弧线,改为只画一个,把
 * mPaint.setStyle(Style.STROKE);
 * mPaint.setStrokeWidth(60);设置,就不用两个弧了
 * 2.关于想改变圆弧值,但是它的背景颜色和是否穿过圆心会改变的问题。 这个是画笔的问题,需要设置画笔颜色 
 * 3.drawArc
 * 和RectF中的数值单位不同的问题,这个还没有解决
 * 4.实现动画效果:利用属性动画ValueAnimation
 * 5.实现渐变效果   利用LinearGradient渲染效果
 * 6.attr  资源文件中设置数据值
 * @author fanshenxia
 *
 */
public class MyPercentView extends View {

    private Context mContext;
    private Paint mPaint, mPaint2;
    private RectF mRectF;
    private int percent= 0;
    private Canvas mCanvas;
    private ValueAnimator animator;
    private float nowPro = 0;
    private float x, y;
    //实现渐变
    private LinearGradient mGradient;
    //自定义属性
    private TypedArray mTypedArray;

    public MyPercentView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        initAttr(attrs);
        init();
    }

    public MyPercentView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        initAttr(attrs);
        init();
    }
    //上篇文章说了,在xml中写布局会调用2个参数的构造函数,那么那些我们设置的属性是从哪里设置进我们的布局中的呢,就是这里了
    private void initAttr(AttributeSet attrs) {
        //获取属性
        mTypedArray = mContext.obtainStyledAttributes(attrs,com.fsx.customviewpro.R.styleable.PercentView);
        //从TypedArray中获取对应的值来为要设置的属性赋值,对应着在res-values-attrs.xml中定义的属性名
        this.percent = mTypedArray.getInteger(com.fsx.customviewpro.R.styleable.PercentView_percent,0);
    }

    public MyPercentView(Context context) {
        super(context);
        this.mContext = context;
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        x = ScreenUtil.getScreenWidth(mContext);
        y = ScreenUtil.getScreenHeight(mContext);

        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Style.STROKE);
        //设置圆角
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(60);
        //设置渐变  参数五必须两个值以上,不然直接崩掉
//      mGradient = new LinearGradient(0, 0, 30, 30, new int[]{Color.BLUE}, null, TileMode.MIRROR);
        //还有种设置方法
        mGradient = new LinearGradient(60, (y - x) / 2 + 60, x - 60, x + (y - x) / 2 - 60, new int[]{mPaint.getColor(),Color.CYAN}, null,
                Shader.TileMode.REPEAT);
        mPaint.setShader(mGradient);
        mRectF = new RectF(60, (y - x) / 2 + 60, x - 60, x + (y - x) / 2 - 60);

        mPaint2 = new Paint();
        mPaint2.setColor(Color.CYAN);
        mPaint2.setTextSize(25);
        mPaint2.setStrokeWidth(15);
        // 设置文字时文字设置在哪里 CENTER表示水平点在文字的中心
        mPaint2.setTextAlign(Align.CENTER);
        mPaint2.setStyle(Style.FILL);

        startMyAnimation();
    }

    /**
     * 开启动画
     */
    private void startMyAnimation() {
        //动画  1.定义animation
        animator = ValueAnimator.ofFloat(0, percent);
        //设置属性动画市场
        animator.setDuration(1800);
        //插值器,这是设置动画效果设置动画类型 如加速,减速,先加后减,弹回等效果
        /**
         * 1、AccelerateInterpolator:动画从开始到结束,变化率是一个加速的过程。
           2、DecelerateInterpolator:动画从开始到结束,变化率是一个减速的过程。
           3、CycleInterpolator:动画从开始到结束,变化率是循环给定次数的正弦曲线。
           4、AccelerateDecelerateInterpolator:动画从开始到结束,变化率是先加速后减速的过程。
           5、LinearInterpolator:动画从开始到结束,变化率是线性变化。
           6.OvershootInterpolator:先超过定义的数值,再弹回
         */
//      animator.setInterpolator(new OvershootInterpolator());
        animator.setInterpolator(new DecelerateInterpolator());
        //添加更新监听事件
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
          @Override
          public void onAnimationUpdate(ValueAnimator animation) {
            nowPro = (Float) animation.getAnimatedValue();
            postInvalidate();
          }
        });
        //添加对动画的监听
//      animator.addListener(listener);
        //开始属性动画
        animator.start();
    }

    private void changeColor() {
        mPaint2.setColor(Color.BLACK);
        // mPaint.setTextSize(25);
        // mPaint.setStrokeWidth(15);
        // // 设置文字时文字设置在哪里 CENTER表示水平点在文字的中心
        // mPaint.setTextAlign(Align.CENTER);
        // mPaint.setStyle(Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        this.mCanvas = canvas;
        // mPaint.setColor(Color.WHITE);
        // mRectF.set(x / 4 - 20, y / 2 - x / 4 - 20, x / 4 * 3 + 20, y / 2 + x
        // / 4 + 20);
        // 画里层的弧
        // canvas.drawArc(mRectF, -90, 90, true, mPaint);
        mPaint2.setColor(Color.CYAN);
        // 画中间的那个圆
        canvas.drawCircle(x / 2, (y - x) / 2 + x / 2, x / 4, mPaint2);
        changeColor();
        // 画中间的百分比
        canvas.drawText("百分比:" + (int)nowPro + "%", x / 2, y / 2, mPaint2);
        mPaint2.setColor(Color.BLACK);
        // 画外层的圈圈
        canvas.drawArc(mRectF, -90, (int) (3.6 * nowPro), false, mPaint);

//本想用这种方法实现动画效果,不行
//      for(int i = 1;i<=percent;i++){
//          canvas.drawArc(mRectF, -90, (int) (3.6 * i), false, mPaint);
//          canvas.drawText("百分比:" + i + "%", x / 2, y / 2, mPaint2);
//          postInvalidateDelayed(300);
//      }
    }
    public void setPercent(int percent) {
        this.percent = percent;
        // 这个方法是重新绘制
//      invalidate();
        startMyAnimation();
    }
}

布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <custom.MyPercentView
        android:id="@+id/mypercentview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        custom:percent="80"
        android:visibility="visible"
        />
</LinearLayout>

实现效果:
Android自定义View:时钟和圆百分比图--(2)

上述的代码都没有详细说,就拿这个详细说吧。
步骤,我是一点一点写的:
1.先绘制最基本的圆,圆弧,中间的文字
2.添加自定义属性,设置百分比
3.实现用户输入数字,重新绘制比例图
4.实现百分比和圆弧的动画效果
5.给圆弧添加上动画效果

先说第一个步骤:绘制最基本的圆,圆弧,中间的文字

        1.先给画笔设置颜色
        mPaint2.setColor(Color.CYAN);
        // 画中间的那个圆
        canvas.drawCircle(x / 2, (y - x) / 2 + x / 2, x / 4, mPaint2);
        //改变颜色
        changeColor();
        // 画中间的百分比
        canvas.drawText("百分比:" + (int)nowPro + "%", x / 2, y / 2, mPaint2);
        mPaint2.setColor(Color.BLACK);
        // 画外层的圈圈
        canvas.drawArc(mRectF, -90, (int) (3.6 * nowPro), false, mPaint);

注意:先画相对底层的,如圆,再画添加在它上边的文字,不然那会被覆盖。
画圆弧用的canvas.drawArc(mRectF, -90, (int) (3.6 * nowPro), false, mPaint);
各参数意思:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2013/0111/798.html

第二个步骤:添加自定义属性
我只添加了一个属性,就是比例值
a.在res–values–attrs.xml中添加代码 ,如果在 values包下没有这个文件,自己新建一个就好了(attrs.xml)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PercentView">
        <attr name="percent" format="integer" />
        <attr name="percentColor" format="color|reference"
    </declare-styleable>
</resources>

我们在代码中通过declare-styleable标签声明了使用自定义属性,通过name来确定引用的名称。通过attr标签来申明具体的自定义属性,这里定义了百分比的数值percent,通过format属性来指定属性的类型。有些属性可以是颜色,也可以是引用属性(reference),就像背景,所以我们用|分割开。

format可取的值和意思
reference 表示引用,参考某一资源ID
string 表示字符串
color 表示颜色值
dimension 表示尺寸值
boolean 表示布尔值
integer 表示整型值
float 表示浮点值
fraction 表示百分数
enum 表示枚举值
flag 表示位运算

b.在逻辑代码中取值,系统提供了TypedArray来获取自定义属性集

        //这里的PercentView对应attrs中定义时的name
        mTypedArray = mContext.obtainStyledAttributes(attrs,com.fsx.customviewpro.R.styleable.PercentView);
        //从TypedArray中获取对应的值来为要设置的属性赋值,对应着在res-values-attrs.xml中定义的属性名,如percent
        this.percent = Integer.valueOf(mTypedArray.getString(com.fsx.customviewpro.R.styleable.PercentView_percent));

c.在xml中引用并设置属性值(引用《Android群英传中内容》)
引用UI模板,在引用前,需要指定引用第三方空间的控件名字。在布局代码中,始终有
xmlns:android="http://schemas.android.com/apk/res/android"
这就是在指定引用的名字控件xmls,即xml namespace。这里指定了名字控件为“android”,因此在控件中才可以使用系统属性”android:”。同样,如果要使用自定义属性,就需要自己创建自己的名字空间,在AS中,第三方控件都使用如下代码来引用名字空间。

xmlns:custom="http://schemas.android.com/apk/res-auto"

这里我们将引用的第三方控件名字取为custom,之后在xml中使用自定义属性时,就可以通过这个名字空间来引用,如下:

custom:percent="80"

第三个步骤:实现用户输入数字,重新绘制比例图
这里直接设置一个方法,将percent值设置进来,然后重新调用绘制的方法即可,特别注意,重新调用invalidate相关方法不会重新设置值,会用原来Paint设置过的那些属性,所以调用重绘后需要在对应的地方重新设置颜色等属性。

    public void setPercent(int percent) {
        this.percent = percent;
        // 这个方法是重新绘制
        //invalidate();
        //重新开动动画进行绘制
        startMyAnimation();
    }

第四个步骤:实现百分比和圆弧的动画效果
为了实现这个效果,我首先想到的是利用for循坏,每次绘制一点点,在休眠一会,重新绘制,这个方法不可行,没啥效果。
后来找到了属性动画,ValueAnimator,实现的效果很理想。

/**
     * 开启动画
     */
    private void startMyAnimation() {
         //动画  1.定义animation
        animator = ValueAnimator.ofFloat(0, percent);
        //设置属性动画时长
        animator.setDuration(1800);
        //插值器,这是设置动画效果设置动画类型 如加速,减速,先加后减,弹回等效果
        /**
         * 1、AccelerateInterpolator:动画从开始到结束,变化率是一个加速的过程。
           2、DecelerateInterpolator:动画从开始到结束,变化率是一个减速的过程。
           3、CycleInterpolator:动画从开始到结束,变化率是循环给定次数的正弦曲线。
           4、AccelerateDecelerateInterpolator:动画从开始到结束,变化率是先加速后减速的过程。
           5、LinearInterpolator:动画从开始到结束,变化率是线性变化。
           6.OvershootInterpolator:先超过定义的数值,再弹回
         */
//      animator.setInterpolator(new OvershootInterpolator());
        animator.setInterpolator(new DecelerateInterpolator());
        //添加更新监听事件
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
          @Override
          public void onAnimationUpdate(ValueAnimator animation) {
          //获取当前value
            nowPro = (Float) animation.getAnimatedValue();
            //刷新
            postInvalidate();
          }
        });
        //添加对动画的监听
//      animator.addListener(listener);
        //开始属性动画
        animator.start();
    }

第五个步骤:给圆弧添加上动画效果

    //实现渐变
    private LinearGradient mGradient;
    //参数5:颜色该怎样变化,从什么颜色到什么颜色  参数7,:模式REPEAT重复  CLAMP:重复最后一个像素  MIRROR翻转像素(这个参数不是一下子能说清楚的,具体之后会写到)
        mGradient = new LinearGradient(60, (y - x) / 2 + 60, x - 60, x + (y - x) / 2 - 60, new int[]{mPaint.getColor(),Color.CYAN}, null,
                Shader.TileMode.REPEAT);
                //设置Shader
        mPaint.setShader(mGradient);

就这样一步一步实现的啦。OK了。控件是我想实现什么功能就实现了什么功能,没在意搭配,丑就丑啦。