导读
1.简介
2.基本实现
自定义属性的声明与获取
测量onMeasure
绘制onDraw
状态的存储与恢复
3.案例:圆形进度条
简介
基本实现
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<!--declare-styleable name默认与自定义类的名字相同-->
<resources>
<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>
TestView.java
package com.hala.custom_view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class TestView extends View {
private Paint mPaint;
private String mText="Emilia";
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initPaint();
TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.TestView);
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);
String StringTest=ta.getString(R.styleable.TestView_test_string);
//第二种方法:这种方法的好处是如果不设置初始值会使用默认的,而不会返回null,比较安全
// int count = ta.getIndexCount();
// for (int i = 0; i < count; i++)
// {
// int index = ta.getIndex(i);
// switch (index)
// {
// case R.styleable.TestView_test_string:
// mText = ta.getString(R.styleable.TestView_test_string);
// break;
// }
// }
Log.e("TAG", booleanTest + " , "
+ integerTest + " , "
+ dimensionTest + " , " + enumTest + " ," + StringTest);
ta.recycle();
}
/**
* 测量
* @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 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 0;
}
private int measureWidth()
{
return 0;
}
//初始化圆
private void initPaint(){
mPaint=new Paint();
//圆的类型STROKE 为空心 FILL为实心
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(6);
mPaint.setColor(0xffff0000);
mPaint.setAntiAlias(true);
}
/**
* 绘制
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画圆
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);
}
//触摸事件
@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";
/**
* 存储状态
* @return
*/
@Override
protected Parcelable onSaveInstanceState()
{
Bundle bundle = new Bundle();
//存储自己的设置
bundle.putString(KEY_TEXT, mText);
//存储父类的设置
bundle.putParcelable(INSTANCE, super.onSaveInstanceState());
return bundle;
}
/**
* 注意重新获得布局中控件一定要设置id属性
* 重新获得状态
* @param state
*/
@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);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!--xmlns:app 自定义view要写自己的标签,其中app可以自定义 结尾要是res-auto-->
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.hala.custom_view.TestView
android:id="@+id/id_pb"
android:background="#ff0000"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:test_boolean="true"
app:test_dimension="100dp"
app:test_enum="bottom"
app:test_integer="10086"
app:test_string="Emilia" />
</android.support.constraint.ConstraintLayout>
案例:圆形进度条
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<!--declare-styleable name默认与自定义类的名字相同-->
<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>
</resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!--xmlns:app 自定义view要写自己的标签,其中app可以自定义 结尾要是res-auto-->
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.hala.custom_view.RoundProgressBar
android:id="@+id/id_pb"
android:progress="0"
android:textSize="16sp"
app:color="#d201f7"
app:radius="40dp"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
RoundProgressBar.java
package com.hala.custom_view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
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, @Nullable AttributeSet attrs) {
super(context, attrs);
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.setColor(mColor);
mPaint.setAntiAlias(true);
}
/**
* 测量
* @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 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;
}
}
width=Math.min(width,height);
setMeasuredDimension(width, height);
}
private int measureHeight()
{
return mRadius*2;
}
private int measureWidth()
{
return mRadius*2;
}
/**
* 绘制
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(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(mPaint.getStrokeWidth()/2,mPaint.getStrokeWidth()/2,width-getPaddingLeft()*2-mPaint.getStrokeWidth()/2,height-getPaddingLeft()*2-mPaint.getStrokeWidth()/2),
0,angle,false,mPaint);
canvas.restore();
//绘制文本
String text=mProgress+"%";
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);
// 这里其实是以文字的基线为准的,如果要除去这部分影响,要用下边这个
// canvas.drawText(text, 0, text.length(), getWidth() / 2, y + textHeight / 2-mPaint.descent(), mPaint);
}
public void setProgress(int progress)
{
mProgress = progress;
invalidate();
}
public int getProgress()
{
return mProgress;
}
private static final String INSTANCE = "instance";
private static final String KEY_TEXT = "key_progress";
/**
* 存储状态
* @return
*/
@Override
protected Parcelable onSaveInstanceState()
{
Bundle bundle = new Bundle();
//存储自己的设置
bundle.putInt(KEY_TEXT, mProgress);
//存储父类的设置
bundle.putParcelable(INSTANCE, super.onSaveInstanceState());
return bundle;
}
/**
* 注意重新获得布局中控件一定要设置id属性
* 重新获得状态
* @param state
*/
@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_TEXT);
return;
}
super.onRestoreInstanceState(state);
}
}
MainActivity.java
package com.hala.custom_view;
import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@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();
}
});
}
}