/*一、自定义属性的声明与获取。 重写方法 TestView(Context context, AttributeSet attrs) 自定义属性并获取 新建一个 #values# 布局文件声明需要的属性 <declare-styleable 跟自定义控件名 用 <attr 添加属性 <attr 里 <enum ></enum> 枚举 自定义的属性,之后可以在 #主布局# 文件使用, 使用前布局文件要自定义前缀 xmlns:hyman="http://schemas.android.com/apk/res-auto" 之后可以在 #自定义View# 里用 TypedArray获取或者第二种循环判断(对于有预定值的) 回收 ta.recycle(); 二、测量 onMeasure 。MeasureSpec、、setMeasuredDimension(width, height); 三、绘制 onDraw 。 Paint、Canvas、mode、size 四、状态的存储与恢复。固定模板 */
public class TestView extends View {
private String mText = "Imooc";
private Paint mPaint;//声明画笔
//重写
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);//父类的属性设置
//在方法中实例化
initPaint();
//第一种自定义属性
//类型数组,环境对象.获得类型属性集的方法,定义的对象用来装TestView属性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TestView);
//挨个 defValue 定义值
boolean booleanTest = ta.getBoolean(R.styleable.TestView_test_boolean, false);
int integerTest = ta.getInteger(R.styleable.TestView_test_integer, -1);
float dimensionTest = ta.getDimension(R.styleable.TestView_test_dimension, 0);
int enumTest = ta.getInt(R.styleable.TestView_test_enum, 1);
// mText = ta.getString(R.styleable.TestView_test_string);//getString不需要默认值
//第二种
//当预定义的有值,判断只有用户声明了这个值,才会去获取
int count = ta.getIndexCount();//返回数组中实际包含数据(有值的属性)的索引数
for (int i = 0; i < count; i++) {
int index = ta.getIndex(i);//取索引id,判断,如果是它就赋值
switch (index) {
case R.styleable.TestView_test_string:
mText = ta.getString(R.styleable.TestView_test_string);
break;
}
}
Log.e("TAG", booleanTest + " , "
+ integerTest + " , "
+ dimensionTest + " , " + enumTest + " ," + mText);
//回收
ta.recycle();
}
//测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//通过 MeasureSpec 拿到mode和size
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//宽
int width = 0;//if确定地,直接给值
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;//
} else {
//自己测量自己所需要的宽度有三个方法组成:测量宽度(自定义)、离左内边距、离右内边距
int needWidth = measureWidth() + getPaddingLeft() + getPaddingRight();
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(needWidth, widthSize);//如果是最多,则不能超过给的值widthSize
} else {
width = needWidth;
}
}
//高,和宽方法一样
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int height = 0;
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
int needHeight = measureHeight() + getPaddingTop() + getPaddingBottom();
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(needHeight, heightSize);
} else {
height = needHeight;
}
}
//设置宽高
setMeasuredDimension(width, height);
}
private int measureHeight() {
return 0;
}
private int measureWidth() {
return 0;
}
//绘制 Canvas 画,油画
@Override
protected void onDraw(Canvas canvas) {
//画圆、线 需要声明一个画笔paint
canvas.drawCircle(getWidth() / 2, getHeight() / 2,
getWidth() / 2 - mPaint.getStrokeWidth() / 2, mPaint);//画笔太宽就减一下
mPaint.setStrokeWidth(1);//线细一点
canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mPaint);
canvas.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight(), mPaint);
//设置文本
mPaint.setTextSize(72);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(0);
//画文本
canvas.drawText(mText, 0, mText.length(), 0, getHeight(), mPaint);
}
//设置画笔
private void initPaint() {
mPaint = new Paint();//先实例化出来
mPaint.setStyle(Paint.Style.STROKE);//空心圆
mPaint.setStrokeWidth(6);//宽度
mPaint.setColor(0xFFFF0000);
mPaint.setAntiAlias(true);//抗锯齿
}
//重写
@Override//点击事件,点击后文本改变
public boolean onTouchEvent(MotionEvent event) {
mText = "8888";
invalidate();//重绘
return true;
}
//固定模板
//定义两个常量
private static final String INSTANCE = "instance";
private static final String KEY_TEXT = "key_text";
//
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putString(KEY_TEXT, mText);//存自己的。
//存父View的。
bundle.putParcelable(INSTANCE, super.onSaveInstanceState());
return bundle;
}
//
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;//
Parcelable parcelable = bundle.getParcelable(INSTANCE);//取
super.onRestoreInstanceState(parcelable);//设
mText = bundle.getString(KEY_TEXT);//设
return;
}
super.onRestoreInstanceState(state);
}//最后最关键要给自定义view设id才能完成。
}
圆形进度条
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View view = findViewById(R.id.id_pb);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator.ofInt(view, "progress", 0, 100).setDuration(3000).start();
}
});
}
}
//圆形进度条
/* */
public class RoundProgressBar extends View {
private int mRadius;//半径
private int mColor;
private int mLineWidth;
private int mTextSize;
private int mProgress;
private Paint mPaint;//画类
//参数:环境,属性集
public RoundProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);//View的构造函数
//类型数组,环境对象.获得类型属性集的方法,定义的对象用来装圆形进度条的属性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar);
mRadius = (int) ta.getDimension(R.styleable.RoundProgressBar_radius, dp2px(30));
mColor = ta.getColor(R.styleable.RoundProgressBar_color, 0xffff0000);
mLineWidth = (int) ta.getDimension(R.styleable.RoundProgressBar_line_width, dp2px(3));
mTextSize = (int) ta.getDimension(R.styleable.RoundProgressBar_android_textSize, dp2px(36));
mProgress = ta.getInt(R.styleable.RoundProgressBar_android_progress, 30);
ta.recycle();
initPaint();
}
//
private float dp2px(int dpVal) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());
}
//
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mColor);
}
public void setProgress(int progress) {
mProgress = progress;
//
invalidate();
}
public int getProgress() {
return mProgress;
}
/*
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int width = 0;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
int needWidth = measureWidth() + getPaddingLeft() + getPaddingRight();
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(needWidth, widthSize);
} else {
width = needWidth;
}
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int height = 0;
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
int needHeight = measureHeight() + getPaddingTop() + getPaddingBottom();
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(needHeight, heightSize);
} else //MeasureSpec.UNSPECIFIED
{
height = needHeight;
}
}
setMeasuredDimension(width, height);
}
private int measureHeight() {
return mRadius * 2;
}
private int measureWidth() {
return mRadius * 2;
}
/*
*/
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mLineWidth * 1.0f / 4);
int width = getWidth();
int height = getHeight();
canvas.drawCircle(width / 2, height / 2,
width / 2 - getPaddingLeft() - mPaint.getStrokeWidth() / 2, mPaint);
mPaint.setStrokeWidth(mLineWidth);
canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop());
float angle = mProgress * 1.0f / 100 * 360;
canvas.drawArc(new RectF(0, 0, width - getPaddingLeft() * 2, height - getPaddingLeft() * 2), 0, angle, false, mPaint);
canvas.restore();
String text = mProgress + "%";
// text = "张鸿洋";
mPaint.setStrokeWidth(0);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize(mTextSize);
int y = getHeight() / 2;
Rect bound = new Rect();
mPaint.getTextBounds(text, 0, text.length(), bound);
int textHeight = bound.height();
canvas.drawText(text, 0, text.length(), getWidth() / 2, y + textHeight / 2, mPaint);
mPaint.setStrokeWidth(0);
// canvas.drawLine(0, height / 2, width, height / 2, mPaint);
}
//固定模板
private static final String INSTANCE = "instance";
private static final String KEY_PROGRESS = "key_progress";
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putInt(KEY_PROGRESS, mProgress);
bundle.putParcelable(INSTANCE, super.onSaveInstanceState());
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
Parcelable parcelable = bundle.getParcelable(INSTANCE);
super.onRestoreInstanceState(parcelable);
mProgress = bundle.getInt(KEY_PROGRESS);
return;
}
super.onRestoreInstanceState(state);
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:hyman="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
tools:context="MainActivity">
<com.jinanchang.myviewapplication.RoundProgressBar
android:id="@+id/id_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="10dp"
android:progress="0"
android:textSize="18sp"
hyman:color="#ea22e4"
hyman:radius="36dp"
/>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RoundProgressBar">
<attr name="color" format="color"></attr>
<attr name="line_width" format="dimension"></attr>
<attr name="radius" format="dimension"></attr>
<attr name="android:progress"></attr>
<attr name="android:textSize"></attr>
</declare-styleable>
<declare-styleable name="TestView">
<attr name="test_boolean" format="boolean"></attr>
<attr name="test_string" format="string"></attr>
<attr name="test_integer" format="integer"></attr>
<attr name="test_enum" format="enum">
<enum name="top" value="1"></enum>
<enum name="bottom" value="2"></enum>
</attr>
<attr name="test_dimension" format="dimension"></attr>
</declare-styleable>
</resources>