03 自定义View目录
java.util.concurrent.TimeoutException: Cannot get spooler! 【异常】:不能再主线程中使用invalidate方法更新UI,也就是重新绘图。
3.2 组合View的自定义属性
public class AddDecreaseView extends RelativeLayout {
private ImageView btnDecrease;
private ImageView btnAdd;
private TextView txtNum;
// 1.提供一个接口
public interface OnAdvClickListener {
void add(int num);
void decrease(int num);
}
// 2.提供一个接口对象
private OnAdvClickListener listener;
public void setOnAdvClickListener(OnAdvClickListener listener) {
this.listener = listener;
}
public AddDecreaseView(Context context) {
this(context, null);
}
public AddDecreaseView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AddDecreaseView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
// 通过obtainStyledAttributes方法返回了一个类型的数组 // 返回值是一个类型的数组
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AddDecreaseView_Style); // 自定义的declare-styleable的名字
// 父层的name+下划线+子层的name
int color = a.getColor(R.styleable.AddDecreaseView_Style_middle_text_color, Color.BLACK); // 设置一个颜色,getColor
int leftImage = a.getResourceId(R.styleable.AddDecreaseView_Style_left_image_src, R.drawable.img_decrease); // 设置图片resourceId,
// 使用结束之后释放资源 // 使用完之后释放重用数据
a.recycle();
// 引入资源文件的时候最后一个参数是this
View.inflate(context, R.layout.item_add_decrease, this);
btnDecrease = findViewById(R.id.btn_decrease);
btnAdd = findViewById(R.id.btn_add);
txtNum = findViewById(R.id.txt_num);
txtNum.setTextColor(color); // 记得把设置好的Color啥的设置给控件
btnDecrease.setImageResource(leftImage);
btnAdd.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
String s = txtNum.getText().toString();
int num = Integer.parseInt(s);
num++;
txtNum.setText(num + "");
// 回调加号的方法
listener.add(num);
}
});
btnDecrease.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
String s = txtNum.getText().toString();
int num = Integer.parseInt(s);
if (num > 0) {
num--;
}
txtNum.setText(num + "");
listener.decrease(num);
}
});
}
}
3.3 画一个View
// 绘制图形要重写的方法
// Canvas 画布
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// new 出来一个paint画笔
Paint paint = new Paint();
paint.setColor(Color.RED);
// 抗锯齿 ,true的画 边角是比较圆滑的,不会毛毛糙糙
paint.setAntiAlias(true);
// 设置绘制样式
// 代码之中的单位全部是px像素
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
paint.setStyle(Paint.Style.FILL); // paint中style的三种属性,代表画线/ 还是填充/ 还是填充+加线
paint.setStyle(Paint.Style.FILL_AND_STROKE);
// 画圆形,圆心的x、y,半径,画笔
canvas.drawCircle(100, 100, 100, paint);
// 画矩形,左、上、右、下
canvas.drawRect(0, 200, 200, 400, paint);
// 画线,起点的x、y,终点的x、y,画笔
canvas.drawLine(0, 0, 200, 200, paint);
// 画椭圆,左、上、右、下,画笔
canvas.drawOval(0, 0, 400, 200, paint);
// 重置画笔,重置之后原来设置的属性均不生效,就等于重新new了一个画笔
paint.reset();
paint.setColor(Color.GREEN);
// 画扇形或弧形,左上右下是圆的范围,startAngle开始角度,以圆的右边距为起点,sweepAngle是扫描过的角度,顺时针方向
// useCenter为true是使用圆内部的空间,false的时候去掉了圆心和半径夹角的三角形
canvas.drawArc(0, 0, 200, 200, 180,
180, false, paint);
paint.setColor(Color.GRAY);
canvas.drawRect(0, 100, 200, 300, paint);
canvas.drawRect(0, 0, 200, 200, paint);
paint.setColor(Color.GREEN);
// 画路径
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(100, 100);
path.lineTo(200, 100);
path.addArc(0, 0, 200, 200, 0, 90);
path.lineTo(100, 200);
path.lineTo(0, 200);
path.lineTo(0, 0);
canvas.drawPath(path, paint);
canvas.drawRect(0, 0, 100, 100, paint);
paint.setTextSize(30);
String text = "hello world";
canvas.drawText(text, 0, 100, paint);
// 画文字,第一个参数是要绘制的文字,start的开始的索引,end是结束的索引,包含start,不包含end,x、y轴是开始得=的坐标
// 以文字的左下角为开始的坐标
canvas.drawText("hello world", 0, 3, 0, 100, paint);
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds); // 画一个text的边框,设置画笔的textBounds为Rect
canvas.drawRect(bounds, paint); // 画出来的就是一个文字的边框
bounds.width(); // 能够获取文字的宽 和 高
bounds.height();
canvas.drawColor(Color.RED); // 画布的背景色
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher), 0, 0, paint); // 画一个图
canvas.drawRect(0, 0, 290, 290, paint);
}
3.4 测量自定义View的宽高(搞明白他的测量模式)
/**
* 测量自定义View的宽高
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 宽度的测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// 测量出的宽度大小
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//高度的测量模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// 测量出的高度大小
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 设置最终测量的宽高 99%的情况下是这样设置宽高
setMeasuredDimension(widthSize / 2, heightSize / 2);
switch (widthMode) {
case MeasureSpec.EXACTLY:
Log.i(TAG, "onMeasure: " + "当前测量模式是精确值");
break;
case MeasureSpec.AT_MOST:
Log.i(TAG, "onMeasure: " + "当前测量模式是最大值");
break;
// 一般用不到
case MeasureSpec.UNSPECIFIED:
Log.i(TAG, "onMeasure: " + "当前没有什么特殊的");
break;
}
Log.i(TAG, "onMeasure: 测量的大小是" + widthSize);
// Log.i(TAG, "onMeasure: " + MeasureSpec.EXACTLY);
// Log.i(TAG, "onMeasure: " + MeasureSpec.AT_MOST);
// Log.i(TAG, "onMeasure: " + MeasureSpec.UNSPECIFIED);
// EXACTLY: 1073741824--- 精确值
// AT_MOST: -2147483648--- 最大值
// UNSPECIFIED: 0
// match_parent
// widthMode: 1073741824---------EXACTLY
// widthSize: 720
// wrap_content
// widthMode: -2147483648----------AT_MOST
// SelfView: widthSize: 720
// 200dp
// widthMode: 1073741824-----------EXACTLY
// widthSize: 300
Log.d(TAG, "widthMode: " + widthMode);
Log.d(TAG, "widthSize: " + widthSize);
// setMeasuredDimension(200, 1920);
}
3.5 自定义一个定时器怎么做
public class MyTextView extends TextView {
public int num = 1;
private Paint paint;
private boolean isStart = true;
public MyTextView(Context context) {
this(context, null);
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
paint = new Paint();
paint.setColor(Color.RED);
paint.setAntiAlias(true);
paint.setTextSize(100);
}
private Canvas canvas;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.canvas = canvas;
canvas.drawText(num + "", 300, 300, paint);
}
public void add() {
num++;
// draw(canvas);
// 每次调用invalidate会重新调用onDraw方法,也就是重新绘制
invalidate(); // 运行在主线程UI线程中
// 内部又创建了一个Handler,效率会变低
postInvalidate();
}
public void start() {
isStart = true;
new Thread(new Runnable() {
@Override
public void run() {
while (isStart) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num++;
// 在子线程不能使用invalidate来更新UI
// invalidate();
// 子线程重新调用onDraw方法的时候需要使用postInvalidate
postInvalidate();
}
}
}).start();
}
public void stop() {
isStart = false;
}
}
3.6 重写onLayout,继承ViewGroup
3.6 梯形布局怎么写
public class LadderView extends ViewGroup {
private static final String TAG = "LadderView";
public LadderView(Context context) {
this(context, null);
}
public LadderView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LadderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// ViewGroup中是可以调用onDraw方法,一般在ViewGroup中不用onDraw
/*@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setAntiAlias(true);
canvas.drawCircle(100, 100, 100, paint);
}*/
/**
* 在继承自ViewGroup时可以调用到onMeasure,并且非常重要
* 测量的是ViewGroup的宽高
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
}
// 继承自ViewGroup必须要重写的方法
// 布局的方法
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
// 获取子控件的数据
int count = getChildCount();
Log.i(TAG, "count: " + count);
measureChildren(0, 0);
/**
* 纵向
*/
/*int sumHeight = 0;
// 循环取出每一个子控件
for (int j = 0; j < count; j++) {
View view = getChildAt(j);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
Log.i(TAG, "第" + i + "个view的宽是: " + width);
Log.i(TAG, "第" + i + "个view的高是: " + height);
// 左上右下
view.layout(0, sumHeight, view.getMeasuredWidth(), sumHeight + view.getMeasuredHeight());
sumHeight += view.getMeasuredHeight();
}*/
/**
* 横向
*/
/*int sumWidth = 0;
for (int j = 0; j < count; j++) {
View view = getChildAt(j);
view.layout(sumWidth, 0, sumWidth + getMeasuredWidth(), view.getMeasuredWidth());
sumWidth = sumWidth + view.getMeasuredWidth();
}*/
/**
* 梯形布局
*/
int sumWidth = 0;
int sumHeight = 0;
for (int j = 0; j < count; j++) {
View view = getChildAt(j);
view.layout(sumWidth, sumHeight, sumWidth + view.getMeasuredWidth(), sumHeight + view.getMeasuredHeight());
sumWidth += view.getMeasuredWidth();
sumHeight += view.getMeasuredHeight();
}
}
}