Android 自定义 View 之自定义属性
一:前言
1. 什么是命名空间呢
android 的命名空间和自定义命名空间
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns: 前缀
android:名称,能够自定义
url: 代表的就是空间, 是个没有用的 url, 是对立资源标识符, 绝对于一个常量
2. 配置文件 attrs.xml
在 res 下的 values 文件加下创立一个 attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomNumAnimView">
<attr name="round_radius" format="dimension" />
<attr name="round_color" format="color" />
<attr name="text_color" format="color" />
<attr name="text_size" format="dimension" />
</declare-styleable>
</resources>
// 格局解析
declare-styleable
name:属性汇合名称
attr
name:属性名称
format:格局
共有 11 种格局
1.reference(资源 id)<ImageView android:background = "@drawable/ 图片 ID"/>
2.color
<TextView android:textColor = "#00FF00" />
3.boolean
4.dimension(尺寸)(dp)5.float(浮点值)6.integer(整形值)7.string(字符串)8.fraction(百分比)9.enum(枚举值)<declare-styleable name="名称">
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
</declare-styleable>
10.flag(位或运算)留神:位运算类型的属性在应用的过程中能够应用多个值
11. 混合属性(应用 | 离开多个属性)
3. 获取属性值
public CustomNumAnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);
// 获取自定义属性
TypedArray array=context.obtainStyledAttributes(attrs,R.styleable.CustomNumAnimView,defStyleAttr,0);
int roundColor=array.getColor(R.styleable.CustomNumAnimView_round_color, ContextCompat.getColor(context,R.color.purple_200));
float roundRadius=array.getDimension(R.styleable.CustomNumAnimView_round_radius,50);
int textColor=array.getColor(R.styleable.CustomNumAnimView_text_color, Color.WHITE);
float textSize=array.getDimension(R.styleable.CustomNumAnimView_text_size,30);
array.recycle();}
二:自定义 View 应用自定义属性
public class CustomNumAnimView extends View {
private int roundColor; // 圆的色彩
private int textColor; // 数字的色彩
private float textSize; // 数字字体大小
private float roundRadius; // 圆的半径
private Paint mPaint; // 画笔
private Rect textRect; // 包裹数字的矩形
private boolean isFirstInit = false; // 是否是第一次初始化
private CustomPoint leftPoint; // 右边的数字的实时点
private String leftNum = "9";
private ValueAnimator leftAnim; // 右边数字动画
private boolean isLeftNumInvalidate = false; // 右边数字是否重绘界面
private CustomPoint middlePoint; // 两头的数字的实时点
private String middleNum = "9";
private ValueAnimator middleAnim; // 两头数字动画
private boolean isMiddleNumInvalidate = false; // 两头数字是否重绘界面
private CustomPoint rightPoint; // 左边的数字的实时点
private String rightNum = "9";
private ValueAnimator rightAnim; // 左边数字动画
private boolean isRightNumInvalidate = false; // 左边数字是否重绘界面
public CustomNumAnimView(Context context) {this(context, null);
}
public CustomNumAnimView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);
}
public CustomNumAnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);
// 获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomNumAnimView, defStyleAttr, 0);
roundColor = array.getColor(R.styleable.CustomNumAnimView_round_color, ContextCompat.getColor(context, R.color.purple_200));
roundRadius = array.getDimension(R.styleable.CustomNumAnimView_round_radius, 50);
textColor = array.getColor(R.styleable.CustomNumAnimView_text_color, Color.WHITE);
textSize = array.getDimension(R.styleable.CustomNumAnimView_text_size, 30);
array.recycle();
// 创立画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);// 抗锯齿标记
mPaint.setTextSize(textSize);// 画笔设置文本大小
textRect = new Rect();
// 失去数字矩形的宽高,以用来画数字的时候纠正数字的地位
mPaint.getTextBounds(middleNum, 0, middleNum.length(), textRect);
}
/**
* 测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size;
int mode;
int width;
int height;
size = MeasureSpec.getSize(widthMeasureSpec);
mode = MeasureSpec.getMode(widthMeasureSpec);
if (mode == MeasureSpec.EXACTLY) { // 确定的值或者 MATCH_PARENT
width = size;
} else { // 示意 WARP_CONTENT
width = (int) (2 * roundRadius);
}
mode = MeasureSpec.getMode(heightMeasureSpec);
size = MeasureSpec.getSize(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) { // 确定的值或者 MATCH_PARENT
height = size;
} else { // 示意 WARP_CONTENT
height = (int) (2 * roundRadius);
}
setMeasuredDimension(width, height);
}
/**
* 重写 onDraw 办法
*/
@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);
if (!isFirstInit) {
// 是
// 初始化三串数字
leftPoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2 - roundRadius / 2, (float) (getMeasuredHeight() / 2 - roundRadius * (Math.sqrt(3) / 2) - textRect.height() / 2));
middlePoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2, getMeasuredHeight() / 2 - roundRadius - textRect.height() / 2);
rightPoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2 + roundRadius / 2, (float) (getMeasuredHeight() / 2 - roundRadius * (Math.sqrt(3) / 2) - textRect.height() / 2));
drawText(canvas);
startAnimation(); // 开始动画
isFirstInit = true;
} else {drawText(canvas);
}
}
private boolean isAnimStart(ValueAnimator anim) {return !anim.isStarted();
}
public void startAnim() {if (isAnimStart(leftAnim)) {leftAnim.start();
}
if (isAnimStart(middleAnim)) {middleAnim.start();
}
if (isAnimStart(rightAnim)) {rightAnim.start();
}
}
/**
* 在 onDestroy 办法中调用
*/
public void stopAnim() {leftAnim.end();
middleAnim.end();
rightAnim.end();
leftAnim = null;
middleAnim = null;
rightAnim = null;
}
/**
* 画数字
*/
private void drawText(Canvas canvas) {
// 画圆
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setColor(roundColor);
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, roundRadius, mPaint);
// 写数字
mPaint.setColor(textColor);
mPaint.setTextSize(textSize);
if (isLeftNumInvalidate) {canvas.drawText(leftNum, leftPoint.getX(), leftPoint.getY(), mPaint);
isLeftNumInvalidate = false;
}
if (isMiddleNumInvalidate) {canvas.drawText(middleNum, middlePoint.getX(), middlePoint.getY(), mPaint);
isMiddleNumInvalidate = false;
}
if (isRightNumInvalidate) {canvas.drawText(rightNum, rightPoint.getX(), rightPoint.getY(), mPaint);
isRightNumInvalidate = false;
}
}
/**
* 开始动画
*/
private void startAnimation() {startLeft();
startMiddle();
startRight();}
private void startRight() {final CustomPoint startPoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2 + roundRadius / 2, (float) (getMeasuredHeight() / 2 - roundRadius * (Math.sqrt(3) / 2) - textRect.height() / 2));
final CustomPoint endPoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2 + roundRadius / 2, (float) (getMeasuredHeight() / 2 + roundRadius * (Math.sqrt(3) / 2) + textRect.height() / 2));
rightAnim = ValueAnimator.ofObject(new CustomPointEvaluator(), startPoint, endPoint);
rightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {rightPoint = (CustomPoint) animation.getAnimatedValue();
isRightNumInvalidate = true;
invalidate();}
});
rightAnim.addListener(new CustomAnimListener() {
@Override
public void onAnimationRepeat(Animator animation) {rightNum = getRandom();
}
});
rightAnim.setStartDelay(150);
rightAnim.setDuration(300);
rightAnim.setRepeatCount(ValueAnimator.INFINITE);
}
private void startMiddle() {
// 初始化两头数字的开始点的地位
final CustomPoint startPoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2, getMeasuredHeight() / 2 - roundRadius - textRect.height() / 2);
// 初始化两头数字的完结点的地位
final CustomPoint endPoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2, getMeasuredHeight() / 2 + roundRadius + textRect.height() / 2);
middleAnim = ValueAnimator.ofObject(new CustomPointEvaluator(), startPoint, endPoint);
// 监听从起始点到起点过程中点的变动, 并获取点而后从新绘制界面
middleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {middlePoint = (CustomPoint) animation.getAnimatedValue();
isMiddleNumInvalidate = true;
invalidate();}
});
middleAnim.addListener(new CustomAnimListener() {
@Override
public void onAnimationRepeat(Animator animation) {middleNum = getRandom();
}
});
middleAnim.setDuration(300);
middleAnim.setRepeatCount(ValueAnimator.INFINITE);
}
private void startLeft() {
// 属性动画
final CustomPoint startPoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2 - roundRadius / 2, (float) (getMeasuredHeight() / 2 - roundRadius * (Math.sqrt(3) / 2) - textRect.height() / 2));
final CustomPoint endPoint = new CustomPoint(getMeasuredWidth() / 2 - textRect.width() / 2 - roundRadius / 2, (float) (getMeasuredHeight() / 2 + roundRadius * (Math.sqrt(3) / 2) + textRect.height() / 2));
leftAnim = ValueAnimator.ofObject(new CustomPointEvaluator(), startPoint, endPoint);
leftAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {leftPoint = (CustomPoint) animation.getAnimatedValue();
isLeftNumInvalidate = true;
invalidate();}
});
leftAnim.addListener(new CustomAnimListener() {
@Override
public void onAnimationRepeat(Animator animation) {middleNum = getRandom();
}
});
leftAnim.setStartDelay(100);
leftAnim.setDuration(300);
leftAnim.setRepeatCount(ValueAnimator.INFINITE);
}
/**
* 获取 0 - 9 之间的随机数
*
* @return
*/
private String getRandom() {int random = (int) (Math.random() * 9);
return String.valueOf(random);
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lsp="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.ruan.mygitignore.CustomNumAnimView
android:id="@+id/custom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
lsp:round_radius="50dp"
lsp:text_size="16dp"/>
</LinearLayout>
这是一个自定 view 的应用的自定义属性
结尾:每一个小小的提高,都是日后的财产