Android自定义view(1)
开始写博客,不在懒惰,不在懒惰,不在懒惰,重要的事情说三遍!哈哈,开始自定义view之旅
首先上图:
如何达到该View的效果?波浪如何绘制?
(1)在实现该View之前,先要了解Xfermode
什么是Xfermode:称之为图像混合模式,Android官方给了张图来解释
也就是图像的集合操作
其中我们先绘制Dst,然后给Paint设置Xfermode模式,然后在绘制Src,呈现的效果就如上图所示
几个常用的
SrcIn:取两层交集,显示上层的,DstIn与之相反
SrcOut:取上层图像的非交集部分,DstOut与之相反等等
那么来看我们实现该View的思路:
这种效果可以使用PorterDuff.Mode.SRC_IN模式,也就是取两层交集,显示上层的,即会先绘制一个圆, 然后设置Xfermode的模式为SrcIn,然后在绘制一个波浪,使其与圆相交,不断的移动绘制的波浪就可以达到波动的效果,同时将波浪向上移动,一次有水流逐渐上升的效果。
其中使用到的知识有
(1)Xfermode
(2)使用Handler控制动画逻辑
(3)使用贝塞尔曲线实现波浪效果
来看原理图:
(1)使用Path的贝塞尔曲线方法rQuadTo构建波浪曲线,代码如下:
mPath.reset();
mPath.moveTo(-2 * radius + count, 2 * radius - progress);
mPath.rQuadTo(radius / 2, A, radius, 0);
mPath.rQuadTo(radius / 2, -A, radius, 0);
mPath.rQuadTo(radius / 2, A, radius, 0);
mPath.rQuadTo(radius / 2, -A, radius, 0);
mPath.lineTo(2 * radius + count, 2 * radius);
mPath.lineTo(-2 * radius + count, 2 * radius);
mPath.close();
可以看到使用Path的rQuadTo方法来构建波浪,最后使其封闭,其中rQuadTo的方法相对于前一个点的步进,类似scrollby。在构建完波浪后,我们通过不停的mPath.moveTo,使波浪超前移动,在与圆相交后显示出来,这里注意,波浪与圆的位置关系,波浪在圆的正下方并且有两个完整的波,这样当我们moveTo到一个波浪时,我们在讲其moveTo到原始的位置,波浪有回到了远点,就源源不断的流下来了。
代码如下,当波浪的步进移动了一个完整的波时,就置为为0,让其回到远点
if (count >= 2 * radius) {
count = 0;
}
(2)Xfermode的使用:代码如下:
xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制圆
mPaint.setColor(circleColor);
canvas.drawCircle(radius, radius, radius, mPaint);
//设置Xfermode
mPaint.setXfermode(xfermode);
resetPath2();
mPaint.setColor(textColor);
//画波浪
canvas.drawPath(mPath, mPaint);
mPaint.setXfermode(null);
if (A < radius / 4) {
textPaint.setAlpha(getAlpha(A));
canvas.drawText(mTragetValue, radius - mTextWidth / 2, radius + mTextHeight / 2, textPaint);
}
}
(3)动画逻辑的判断:
使用Handler的sendEmptyMessage方法,通过在Handler的handleMessage方法中sendEmptyMessage(0);以此达到不停回调的效果,注意Handler的退出条件
完整的代码,有详细注释,希望大家能学到知识,共同进步
public class CustomCircle extends View {
/**
* 退出Handler的MSG
*/
public static final int EXIT_FLAG = 1;
/**
* 进行动画的的MSG
*/
public static final int GOON_FLAG = 0;
public static final int MAX_PROGRESS = 100;
/**
* 圆的半径
*/
private int radius;
private Paint mPaint;
private Path mPath;
private Paint textPaint;
/**
* 图像混合模式
*/
private Xfermode xfermode;
/**
* 颜色
*/
private int textColor;
private int circleColor;
private int progress = 0;
//y = Asin(bx+c);
public static final int CIRCLE_DEGREE = 180;
/**
* 振幅
*/
private int A = 50;
private double[] pis = new double[360];
/**
* 要达到的进度
*/
private String mTragetValue = "";
/**
* 波浪前进的step
*/
private int count = 0;
/**
* 真实的进度
*/
private int mActualHei;
private int mTextWidth;
private int mTextHeight;
private int alpha;
public LoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.loadingCircle, 0, 0);
int count = typedArray.getIndexCount();
int attr = 0;
for (int i = 0; i < count; i++) {
attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.loadingCircle_radius:
radius = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics()));
break;
case R.styleable.loadingCircle_circleColor:
circleColor = typedArray.getColor(attr, Color.BLUE);
break;
case R.styleable.loadingCircle_textColor:
textColor = typedArray.getColor(attr, Color.BLACK);
break;
}
}
typedArray.recycle();
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextSize(radius / 3);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(Color.WHITE);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.FILL);
//设置图像的混合模式
xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
mPath = new Path();
//关闭硬件加速,对Xfermode有影响
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制圆
mPaint.setColor(circleColor);
canvas.drawCircle(radius, radius, radius, mPaint);
//设置Xfermode
mPaint.setXfermode(xfermode);
resetPath2();
mPaint.setColor(textColor);
//画波浪
canvas.drawPath(mPath, mPaint);
mPaint.setXfermode(null);
if (A < radius / 4) {
textPaint.setAlpha(getAlpha(A));
canvas.drawText(mTragetValue, radius - mTextWidth / 2, radius + mTextHeight / 2, textPaint);
}
}
//字体淡出的透明度
private int getAlpha(int a) {
if (a<1){
return 255;
}
int b = radius / 4;
int temp = (b - a) * 255 / b;
return temp;
}
//测量字体的宽高
private void getTextWidthOrHei(String msg) {
Rect rect = new Rect();
textPaint.getTextBounds(msg, 0, msg.length(), rect);
mTextWidth = rect.width();
mTextHeight = rect.height();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
//测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
MeasureSpec.getMode(widthMeasureSpec);
MeasureSpec.getSize(widthMeasureSpec);
//测量,强制view的大小为园的大小
setMeasuredDimension(2 * radius, 2 * radius);
}
/**
* 构造我们的path,使用的是贝塞尔曲线
*/
private void resetPath2() {
if (mActualHei <= 0) {
return;
}
mPath.reset();
mPath.moveTo(-2 * radius + count, 2 * radius - progress);
mPath.rQuadTo(radius / 2, A, radius, 0);
mPath.rQuadTo(radius / 2, -A, radius, 0);
mPath.rQuadTo(radius / 2, A, radius, 0);
mPath.rQuadTo(radius / 2, -A, radius, 0);
mPath.lineTo(2 * radius + count, 2 * radius);
mPath.lineTo(-2 * radius + count, 2 * radius);
mPath.close();
}
/**
* 控制动画的逻辑
*/
private Handler mPathHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//退出信号
if (msg.what == EXIT_FLAG) {
return;
}
//如果振幅<0,代表动画的结束
if (A < 0) {
removeMessages(GOON_FLAG);
return;
}
//在进度达到设定的进度时,振幅开始减小
if (progress >= mActualHei) {
A--;
} else {
//波浪的步进,通过moveTo达到移动效果
count += 5;
//波浪高度的步进
progress += 1;
//如果已近移动了一个完整的波浪,那么时时候回到原点了
if (count >= 2 * radius) {
count = 0;
}
}
//更新
invalidate();
sendEmptyMessage(0);
}
};
//停止动画
private void stopAnimator() {
Message msg = mPathHandler.obtainMessage(EXIT_FLAG);
mPathHandler.sendMessageAtFrontOfQueue(msg);
mPathHandler.removeMessages(GOON_FLAG);
progress = 0;
count = 0;
A = radius / 4;
}
//开始动画
public void animatorStart() {
stopAnimator();
mPathHandler.sendEmptyMessage(0);
}
//设置比例
public void setTargetValue(int target) {
mTragetValue = String.valueOf(target);
mActualHei = (int) (2 * radius * (target * 1.f / MAX_PROGRESS));
getTextWidthOrHei(mTragetValue);
}
}
不足之处:
(1)View的测量太粗暴,没能考虑充分考虑父view的意愿。
(2)不支持padding
(3)动画没有时间的设定
以上三点会在后面的博客中一一解决。
最后附上一句”业精于勤而荒于嬉,行成于思而毁于随“勉励自己!