什么是自定义view
顾名思义就是自己定义的view 继承自view ,可以展现Android系统没有的效果,Android页面多姿多彩丰富的控件大多都是自定义view来实现的,在开发过程中也会经常使用自定义view来优化自己的页面
自定义view的步骤
前两步是一个通用模板,所有的自定义view都可以使用这种方法进行绘制,区别就在于第三步,每个view的绘制流程和方法都不同,这还需要点数学的知识
一. 自定义属性的声明与获取
1 分析需要的自定义属性(例如字体颜色、线条宽度等)
2 在res/values/下新建一个attrs.xml定义声明
3 在layout文件中进行使用
4 在view的构造方法中进行获取(自定义类继承自View)
这个attrs里面定义了两个View的类型 一个是bubbleView 还有一个是visualizerView
再里面的一层就是他们的名字和对应的类型了,宽高大小对应的就是dimension,color对应的类型就是color,enum是枚举类型,里面可以声明几个对应的属性
<resources>
<declare-styleable name="BubbleView">
<attr name="arrowWidth" format="dimension" />
<attr name="angle" format="dimension" />
<attr name="arrowHeight" format="dimension" />
<attr name="arrowPosition" format="dimension" />
<attr name="bubbleColor" format="color" />
<attr name="arrowLocation" format="enum">
<enum name="left" value="0x00" />
<enum name="right" value="0x01" />
<enum name="top" value="0x02" />
<enum name="bottom" value="0x03" />
</attr>
</declare-styleable>
<declare-styleable name="visualizerView">
<attr name="numColumns" format="integer" />
<attr name="renderColor" format="color" />
<attr name="renderRange">
<enum name="top" value="0" />
<enum name="bottom" value="1" />
<enum name="both" value="2" />
</attr>
<attr name="renderType">
<flag name="bar" value="0x1" />
<flag name="pixel" value="0x2" />
<flag name="fade" value="0x4" />
</attr>
</declare-styleable>
</resources>
二.测量OnMeasure
1 获取宽高的模式:MeasureSpec.getMode 模式一共有三种,如下:
MeasureSpec.EXACTLY 在布局中指定了宽高 是一个确切的值(比如100dp 可以直接使用) MeasureSpec.ATMOST AT_MOST是最大的意思,这时需要测量自己控件需要的的宽高,如果大于父控件的,那么就用父控件的大小,如果小于父控件,就使用父控件大小 总结下来就是尽可能地给最大 但是不能超过父控件MeasureSpec.UNSPECIFIED 表示尽可能大的显示 在listview和scrollview中就是这样测量宽高的 因为是滚动控件,一般自己定义时很少用到 看scrollview的源码会看到 他的模式是获取最大的显示方式 childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec( Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding), MeasureSpec.UNSPECIFIED);
用代码感受下获取宽高的模式该怎么使用
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int result=0;
if (widthMode==MeasureSpec.EXACTLY){
result=size;
}else{
//计算控件本身需要的宽高
result=getNeedWidth()+getPaddingTop()+getPaddingBottom();
if (widthMode==MeasureSpec.AT_MOST){
result=Math.min(result,size);
}
}
return result;
2 setMeasuredDimension() 保存宽高
三.绘制OnDraw
1 绘制内容区域
2 invalidate() postInvalidata()
3 Canvas.drawXXX (可以drawCircle、Text、Points等)
4 save() restore()
四 状态的存储与恢复
1 onSaveInstanceState
2 onRestoreInstanceState
实战
自定义view流程就是这样,但是控件样式多种多样,实践才能加深印象,现在将这些方法用到实践中去吧!这里以一个进度条的加载为例子,先看效果:
processbar
1 创建一个新工程
2 分析需要的自定义属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ProgressBar">
<!-- 圆弧的颜色-->
<attr name="innerBackground" format="color"/>
<attr name="outerBackground" format="color"/>
<!-- 圆弧的大小-->
<attr name="roundWidth" format="dimension"/>
<!-- 字体大小、颜色-->
<attr name="progressTextSize" format="dimension"/>
<attr name="progressTextColor" format="color"/>
</declare-styleable>
</resources>
3 在main.xml文件中引用我们自己创建的progressBar这里的属性都是刚才自定义的 颜色和属性根据自己的需求给
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical">
<com.example.viewtest.ProgressBar
android:id="@+id/progress_bar"
app:progressTextSize="20sp"
app:progressTextColor="#ff7788"
app:innerBackground="#ee9933"
app:outerBackground="#229933"
app:roundWidth="5dp"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!-- 测试按钮-->
<Button
android:id="@+id/test"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_marginTop="30dp"
android:layout_gravity="center"
android:text="测试"
android:background="@color/black"
android:textColor="@color/white"/>
</LinearLayout>
4 创建一个类继承View 实现他的构造方法 然后编写里面的方法
public class ProgressBar extends View {
private static final String TAG = "ProgressBar";
//声明自定义属性
private int mInnerBackground = Color.RED;
private int mOuterBackground = Color.GREEN;
private int mRoundWidth = 30;
private int mProgressTextSize = 70;
private int mProgressTextColor = Color.GREEN;
//创建画笔
private Paint mInnerPaint=new Paint();
private Paint mOutterPaint = new Paint();
private Paint mTextPaint = new Paint();
//进度条的最大进度
private int mMax=100;
//当前进度
private int mCurrentProgress=50;
//new对象的时候调用该方法
public ProgressBar(Context context) {
super(context);
}
//在布局中使用 xml文件中使用
public ProgressBar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
// 在layout中调用 自定义样式、有固定style的时候使用
public ProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义属性
TypedArray array= context.obtainStyledAttributes(attrs,R.styleable.ProgressBar);
mInnerBackground=array.getColor(R.styleable.ProgressBar_innerBackground,mInnerBackground);
mOuterBackground=array.getColor(R.styleable.ProgressBar_outerBackground,mOuterBackground);
mProgressTextColor= array.getColor(R.styleable.ProgressBar_progressTextColor,mProgressTextColor);
mProgressTextSize= (int) array.getDimension(R.styleable.ProgressBar_progressTextSize,sp2px(mProgressTextSize));
mRoundWidth= (int) array.getDimension(R.styleable.ProgressBar_roundWidth,dip2px(mRoundWidth));
array.recycle();
// mInnerPaint = new Paint();
}
private float sp2px(int sp) {
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics());
}
private float dip2px(int dip) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics());
}
public ProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* onMeasure
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//拿到宽高
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
//存储view 的宽高 在这里取最小值 保证是一个正方形即可
setMeasuredDimension(Math.min(width,height),Math.min(width,height));
}
/**
* onDraw
*/
@Override
protected void onDraw(Canvas canvas) {
mInnerPaint.setAntiAlias(true);//抗锯齿
mInnerPaint.setColor(mInnerBackground);
mInnerPaint.setStrokeWidth(mRoundWidth);//线条宽度
mInnerPaint.setStyle(Paint.Style.STROKE);//空心
mOutterPaint.setAntiAlias(true);//抗锯齿
mOutterPaint.setColor(mOuterBackground);
mOutterPaint.setStrokeWidth(mRoundWidth);//线条宽度
mOutterPaint.setStyle(Paint.Style.STROKE);//空心
mOutterPaint.setAntiAlias(true);//抗锯齿
mTextPaint.setColor(mProgressTextColor);
mTextPaint.setStrokeWidth(mRoundWidth);
mTextPaint.setTextSize(mProgressTextSize);
// super.onDraw(canvas);
//先画内圆
int center = getWidth()/2;
Log.d(TAG, "onDraw: "+center);
canvas.drawCircle(center,center,center-mRoundWidth/2,mInnerPaint);
//再画外层圆
RectF rectF=new RectF(0+mRoundWidth/2,0+mRoundWidth/2,getWidth()-mRoundWidth/2,getHeight()-mRoundWidth/2);
if (mCurrentProgress==0){
return;
}
float percent = (float) mCurrentProgress/mMax;
canvas.drawArc(rectF,0,percent*360,false,mOutterPaint);
//画进度文字
String text=((int)(percent*100))+"%";
Rect textBounds=new Rect();
mTextPaint.getTextBounds(text,0,text.length(),textBounds);
int x= getWidth()/2-textBounds.width()/2;
Paint.FontMetricsInt fontMetrics=mTextPaint.getFontMetricsInt();
int dy=(fontMetrics.bottom-fontMetrics.top)/2 - fontMetrics.bottom;
int baseLineY=getHeight()/2+dy;
canvas.drawText(text,x,baseLineY,mTextPaint);
}
public synchronized void setMax(int max){
if (max<0){
Log.d(TAG, "setMax: max<0 不合法"+max);
}
this.mMax=max;
}
public synchronized void setProgress(int process){
if (process<0){
Log.d(TAG, "setMax: max<0 不合法"+process);
}
this.mCurrentProgress=process;
invalidate();//刷新
}
}
5 在mainActivity中 调用 测试
public class MainActivity extends AppCompatActivity {
private ProgressBar mProgressBar;
private Button mTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
}
private void initData() {
mTest=findViewById(R.id.test);
mProgressBar=(ProgressBar) findViewById(R.id.progress_bar);
mProgressBar.setMax(1000);
mTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
test();
}
});
}
private void test() {
//值不断变化
ValueAnimator animator= ObjectAnimator.ofFloat(0,1000);
animator.setDuration(3000);
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float progress = (float) animation.getAnimatedValue();
mProgressBar.setProgress((int) progress);
}
});
}
}
这样就OK啦
关于view的绘制还有很多要学习的东西,比如看到很多可以跳动的view,各种形状,还有和手机重力感应有关的有趣的view,后面将会继续学习。