老套路先上图:
整个view非常简单,我自定义view里面都有详细的注释说明
先看自定义view部分代码:
package cn.xiayiye5.customizestudy.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import cn.xiayiye5.customizestudy.R;
/**
* @author : xiayiye5
* @date : 2021/3/18 15:50
* 类描述 :自定义view - 画圆基础练习
*/
public class BasicView extends View {
private int measuredWidth;
private int measuredHeight;
private Paint paint;
private float paintWidth;
private int paintColor;
private int paintStroke;
private Paint textPaint;
private final float textWidth;
private final int textColor;
private final float textSize;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public BasicView(Context context) {
this(context, null);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public BasicView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public BasicView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public BasicView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BasicView);
paintWidth = typedArray.getDimension(R.styleable.BasicView_paint_width, 10);
paintColor = typedArray.getColor(R.styleable.BasicView_paint_color, Color.GREEN);
paintStroke = typedArray.getInt(R.styleable.BasicView_paint_stroke, 1);
//获取文字颜色,宽度,大小等
textWidth = typedArray.getDimension(R.styleable.BasicView_text_width, 10);
textColor = typedArray.getColor(R.styleable.BasicView_text_color, Color.GREEN);
textSize = typedArray.getDimension(R.styleable.BasicView_text_size, 20);
typedArray.recycle();
initView();
}
private void initView() {
//文字用到的画笔
textPaint = new Paint();
//圆用到的画笔
paint = new Paint();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置中间文字颜色大小宽度等
textPaint.setTextSize(textSize);
textPaint.setStrokeWidth(textWidth);
textPaint.setColor(textColor);
//设置画笔圆的大小颜色宽度等
paint.setColor(paintColor);
paint.setTextSize(14f);
paint.setStrokeWidth(paintWidth);
if (paintStroke == StrokeModel.STROKE_ZERO) {
paint.setStyle(Paint.Style.FILL);
} else if (paintStroke == StrokeModel.STROKE_ONE) {
paint.setStyle(Paint.Style.STROKE);
} else if (paintStroke == StrokeModel.STROKE_TWO) {
paint.setStyle(Paint.Style.FILL_AND_STROKE);
} else {
throw new NumberFormatException("The value is only 0(BasicView.StrokeModel.STROKE_ZERO) or 1(BasicView.StrokeModel.STROKE_ONE) or 2(BasicView.StrokeModel.STROKE_TWO)");
}
float length = textPaint.measureText("扬宏豕慧");
float fontHeight = getFontHeight(textPaint);
//写文字在坐标中心居中
canvas.drawText("扬宏豕慧", (measuredWidth - length) / 2f, (measuredHeight + fontHeight / 2f) / 2f, textPaint);
canvas.drawCircle(measuredWidth / 2f, measuredHeight / 2f, (measuredWidth - paintWidth) / 2f, paint);
//画圆的横线
canvas.drawLine(0, measuredHeight / 2f, measuredWidth, measuredHeight / 2f, paint);
//画圆的竖线
canvas.drawLine(measuredWidth / 2f, 0, measuredHeight / 2f, measuredHeight, paint);
}
/**
* @return 返回指定的文字高度
*/
public float getFontHeight(Paint paint) {
Paint.FontMetrics fm = paint.getFontMetrics();
//文字基准线的下部距离-文字基准线的上部距离 = 文字高度
return fm.descent - fm.ascent;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = 0;
int width = 0;
width = setViewMode(widthMeasureSpec, width);
height = setViewMode(heightMeasureSpec, height);
//获取测量模式后设置下即可
setMeasuredDimension(width, height);
measuredWidth = getMeasuredWidth();
measuredHeight = getMeasuredHeight();
Log.e("打印宽高", measuredWidth + "-" + measuredHeight);
}
/**
* 获取测量模式的方法
*
* @param measureSpec 测量模式
* @param viewSize view的大小
* @return 返回 view经过测量后的大小
*/
private int setViewMode(int measureSpec, int viewSize) {
int viewMode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (viewMode == MeasureSpec.AT_MOST) {
// wrap_content模式 测量规格模式:子级可以任意大到指定的大小。
viewSize = getMeasuredWidth() / 3;
} else if (viewMode == MeasureSpec.EXACTLY) {
// 已经设置了具体大小,类似10dp 和match_parent 测量规格模式:父级已确定子级的确切大小。不管孩子想要多大,都要给他这些限制。
viewSize = size;
} else if (viewMode == MeasureSpec.UNSPECIFIED) {
//设置多大就多大可以无限大 度量规范模式:父级未对子级施加任何约束。它可以是它想要的任何尺寸。
viewSize = size;
}
return viewSize;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//获取view在当前屏幕的坐标
float rawX = event.getRawX();
float rawY = event.getRawY();
//获取view在自身的坐标
float x = event.getX();
float y = event.getY();
Log.e("打印屏幕坐标", rawX + "," + rawY);
Log.e("打印view坐标y", x + "," + y);
if (x > 0 && y > 0) {
//证明点击了此view
Toast.makeText(getContext(), "点击了此布局(" + x + "," + y + ")", Toast.LENGTH_LONG).show();
clickViewListener.clicked(this);
}
}
return true;
}
public static class StrokeModel {
private StrokeModel() {
}
public static final int STROKE_ZERO = 0;
public static final int STROKE_ONE = 1;
public static final int STROKE_TWO = 2;
}
public interface ClickViewListener {
/**
* 点击了哪个view
*
* @param view 具体的view
*/
void clicked(View view);
}
ClickViewListener clickViewListener;
public void setClickViewListener(ClickViewListener clickViewListener) {
this.clickViewListener = clickViewListener;
}
/**
* 设置画笔圆的宽度
*
* @param paintWidth 宽度
*/
public void setPaintWidth(float paintWidth) {
this.paintWidth = paintWidth;
requestLayout();
}
/**
* 设置画笔圆的颜色
*
* @param paintColor 颜色
*/
public void setPaintColor(int paintColor) {
this.paintColor = paintColor;
invalidate();
}
/**
* 设置画笔圆的模式
*
* @param paintStroke 模式
*/
public void setPaintStroke(int paintStroke) {
this.paintStroke = paintStroke;
requestLayout();
}
}
再看layout布局代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<cn.xiayiye5.customizestudy.view.BasicView
android:id="@+id/bv_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:paint_color="@color/purple_500"
app:paint_stroke="stroke"
app:paint_width="1dp"
app:text_color="@color/purple_200"
app:text_size="16sp"
app:text_width="10dp" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="changeColor"
android:text="改变颜色"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
再看activity页面代码
package cn.xiayiye5.customizestudy.ui.activity;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Random;
import cn.xiayiye5.customizestudy.R;
import cn.xiayiye5.customizestudy.view.BasicView;
import cn.xiayiye5.customizestudy.view.BasicView.StrokeModel;
/**
* @author : xiayiye5
* @date : 2021/3/19 11:36
* 类描述 :
*/
public class BasicActivity extends AppCompatActivity {
int num = 0;
BasicView bvView;
private ObjectAnimator rotation;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_basic);
bvView = findViewById(R.id.bv_view);
bvView.setClickViewListener(view -> Toast.makeText(BasicActivity.this, "点击了此布局", Toast.LENGTH_LONG).show());
}
public void changeColor(View view) {
if (num % StrokeModel.STROKE_TWO == 0) {
//开始执行动画
startAnim();
Toast.makeText(this, "彩虹红灯开启", Toast.LENGTH_SHORT).show();
handler.sendEmptyMessageDelayed(0, 500);
} else {
Toast.makeText(this, "彩虹红灯关闭", Toast.LENGTH_SHORT).show();
handler.removeCallbacksAndMessages(null);
//关闭动画
rotation.end();
}
num++;
}
/**
* 开始动画的方法
*/
private void startAnim() {
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(bvView, "alpha", 0.0f, 1.0f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(bvView, "scaleX", 0.0f, 1.0f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(bvView, "scaleY", 0.0f, 1.0f);
rotation = ObjectAnimator.ofFloat(bvView, "rotation", 0.0f, 1080f);
rotation.setRepeatCount(100);
//旋转不停顿
rotation.setInterpolator(new LinearInterpolator());
//重复次数
rotation.setDuration(1000);
rotation.start();
// AnimatorSet set = new AnimatorSet();
// set.playTogether(alphaAnim, scaleX, scaleY);
// set.setDuration(3000);
// set.start();
}
private final Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.e("打印", "发送消息");
bvView.setPaintColor(Color.rgb(new Random().nextInt(255), new Random().nextInt(255), new Random().nextInt(255)));
bvView.setPaintStroke(StrokeModel.STROKE_ONE);
bvView.setPaintWidth(new Random().nextInt(30));
//继续发送形成循环模式
sendEmptyMessageDelayed(0, 500);
}
};
@Override
protected void onPause() {
super.onPause();
handler.removeCallbacksAndMessages(null);
}
@Override
protected void onResume() {
super.onResume();
if (num > 0) {
//继续发送形成循环模式
handler.sendEmptyMessageDelayed(0, 500);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
}
在看下attrs.xml属性文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BasicView">
<!--画笔颜色-->
<attr name="paint_color" format="color" />
<!--画笔宽度-->
<attr name="paint_width" format="dimension" />
<!--画笔形状-->
<attr name="paint_stroke" format="enum">
<enum name="fill" value="0" />
<enum name="stroke" value="1" />
<enum name="fill_and_stroke" value="2" />
</attr>
<!--文字颜色-->
<attr name="text_color" format="color" />
<!--文字大小-->
<attr name="text_size" format="dimension" />
<!--文字宽度-->
<attr name="text_width" format="dimension" />
</declare-styleable>
</resources>
整个理偶成非常简单说下具体步骤:
先通过attires文件自定义属性=>然后绘制view将自定义属性通过layout文件与attrs的自定义属性对应设置相应的属性值即可。其它就是API相关的东西了。
感谢博主提供动画结束方法:博主直达
关于view的测量模式可以查看Google官方文档的说明:Google官方文档说明