以下为参照博客 http://blog.csdn.net/guimianhao9833/article/details/74858472
首先看下效果图:
步骤:
1.继承view,并需要三个构造函数。
public MyView(Context context) { //注意不要默认实现,记得修改。 this(context, null); } public MyView(Context context, AttributeSet attrs) { //注意不要默认实现,记得修改。 this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //初始化工作 init(context, attrs); }
初始化工作init:
1)在attrs.xml中定义控件需要的属性
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MyView"> <attr name="color" format="color"/> <attr name="text" format="string"/> </declare-styleable> </resources>
2)自定义控件一般都需要定义相关的属性,我们获取它并对它进行相关的设置;
由效果图可知,这个自定义控件需要定义一个画笔用来画图形路径;
需要定义一个画笔用来画文字;
需要Path用来定义闭合波浪路径;
为了实现控件无线循环的动画效果,需要对其进行相关的监听动作并实时刷新。
private void init(Context context, AttributeSet attrs) { //1.定义需要的属性 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView); //颜色和文字 color = a.getColor(R.styleable.MyView_color, Color.rgb(238,121,66)); text = a.getString(R.styleable.MyView_text); //记得调用这个方法回收 a.recycle(); //2.1定义图形及路径填充画笔的相关值(抗锯齿、填充、防抖动) mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(color); mPaint.setDither(true); //2.2定义文字画笔的相关值(抗锯齿、白色、粗体) textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textPaint.setColor(Color.WHITE); textPaint.setTypeface(Typeface.DEFAULT_BOLD); //2.3定义闭合波浪路径 path = new Path(); //3.实现无线循环的动画效果 //ValueAnimator定时器,属性动画ObjectAnimator就是它的一个子类 ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //让动画无限重复,每次从头开始,一个周期1000毫秒 animator.setDuration(1000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setRepeatMode(ValueAnimator.RESTART); //补上一个线性插值器,使得动画流畅 animator.setInterpolator(new LinearInterpolator()); //监听获得过程值,实时进行刷新 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { currentPercent = animation.getAnimatedFraction(); //刷新 invalidate(); } }); //动画开始 animator.start(); }
2.onDraw方法,重中之重!
绘制流程:
1)绘制底部蓝色的文字
2)绘制闭合的波浪路径,颜色为蓝色
3)绘制白色的文字
4)根据已有的波浪路径去裁剪文字,使得文字上半部门为蓝色,文字下半部门为白色
(因为先绘制在下部,后绘制的在上部)
5)在已有的画布canvas上画一个圆,也就是裁剪成圆形
@Override protected void onDraw(Canvas canvas) { //super.onDraw(canvas); //1.画底部蓝色的字 textPaint.setColor(color); drawCenterText(canvas, textPaint, "逗"); //2.画上层白色的字 textPaint.setColor(Color.WHITE); //3.生成闭合波浪路径 path = getActionPath(currentPercent); //4.剪裁文字 canvas.clipPath(path); //5.剪裁成圆形(SRC 源像素) canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); //6.画波浪(DST 目标像素) canvas.drawPath(path, mPaint); mPaint.setXfermode(null); drawCenterText(canvas, textPaint, "逗"); }
生成闭合的波浪路径的方法getActionPath:
这个波浪可以由
二阶贝塞尔曲线。如下的例子,起点为(100,100),然后移动相对于起点的两个位置变化为(150,0),(200,100)。
Path path=new Path(); path.moveTo(100,100); path.rQuadTo(50,-100,100,0);
图解:
1)画两个周期的波浪
2)画右侧的直线
3)画下边的直线
private Path getActionPath(float percent) { Path path = new Path(); int x = -mWidth; //1.当前x点坐标(根据动画进度水平推移,一个动画周期推移的距离为一个mWidth) x += percent * mWidth; //2.波形的起点 path.moveTo(x, mHeight / 2); //控制点的相对宽度 int kongzhiWidth = mWidth / 4; //控制点的相对高度=mHeight的二十分之3 int kongzhiHeight = mHeight / 20 * 3; //3.画第一个周期,一个周期为一上一下 path.rQuadTo(kongzhiWidth, kongzhiHeight, kongzhiWidth * 2, 0); path.rQuadTo(kongzhiWidth, -kongzhiHeight, kongzhiWidth * 2, 0); //画第二个周期,如此循环下去 path.rQuadTo(kongzhiWidth, kongzhiHeight, kongzhiWidth * 2, 0); path.rQuadTo(kongzhiWidth, -kongzhiHeight, kongzhiWidth * 2, 0); //4.画右侧的直线 path.lineTo(x + mWidth * 2, mHeight); //5.画下边的直线 path.lineTo(x, mHeight); //自动闭合补出左边的直线 path.close(); return path; }
画中间的文字drawCenterText:
private void drawCenterText(Canvas canvas, Paint textPaint, String text) { //画一个规定位置的矩形 Rect rect = new Rect(0, 0, mWidth, mHeight); //由于默认是位于左边的,所以这样设置到中间来。 textPaint.setTextAlign(Paint.Align.CENTER); Paint.FontMetrics fontMetrics = textPaint.getFontMetrics(); float top = fontMetrics.top; float bottom = fontMetrics.bottom; int centerY = (int) (rect.centerY() - top / 2 - bottom / 2); canvas.drawText(text, rect.centerX(), centerY, textPaint); }
3.onMeasure方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { mWidth = widthSize; } if (heightMode == MeasureSpec.EXACTLY) { mHeight = heightSize; } setMeasuredDimension(mWidth, mHeight); textSize = mWidth / 2; textPaint.setTextSize(textSize); }自定义控件,要好好学哦。