赞
踩
当我们需要实现的效果是一个不规则效果的时候,那么这时就需要继承 View 来实现了,我们需要重写 onDraw 方法,在该方法里实现各种不规则的图形和效果。当我们使用这种方式的时候,需要自己去处理 warp_content 和 padding。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height =0;
if(widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
}else {
//widthMode == MeasureSpec.AT_MOST模式 自己设置控件宽度
//当是wrap_content或者给具体dp的时候会走这里
width = mRadius * 2 + getPaddingRight() + getPaddingLeft();
}
if(heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
}else {
height = mRadius * 2 + getPaddingTop() + getPaddingBottom();
}
//注意最后 调用这个方法 让属性生效
setMeasuredDimension(width,height);
}
当系统所提供的 LinearLayout、FrameLayout 等布局控件无法满足我们的需求时,这时我们就需要使用这种方式来实现自己想要的布局效果了。当我们使用这种方式的时候,需要重写 onLayout 方法来对子 View 进行布局,以及测量本身和子 View 宽高,还需要处理本身的 padding 和子 View 的 margin。
当我们需要基于已有的 View 进行扩展或修改的时候,那么就可以使用这种方式。比如说,我们需要一个圆角的 ImageView,那么这时就可以继承 ImageView 进行修改了。当我们使用这种方式的时候,一般不需要自己去处理 wrap_content 和 padding 等,因为系统控件已经帮我们做好了。
这种方式也叫做:自定义组合 View。该方式比较简单,当我们需要将一组 View 组合在一起,方便后期复用的时候,就可以使用该方法。当我们使用这种方式的时候,不需要去处理 ViewGroup 的测量和布局流程,因为系统控件已经帮我们做好了。
每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw()
1)、Activity的attach 方法里创建PhoneWindow。
2)、onCreate方法里的 setContentView 会调用PhoneWindow的setContentView方法,创建DecorView并且把xml布局解析然后添加到DecorView中。
3)、在onResume方法执行后,会创建ViewRootImpl,它是最顶级的View,是DecorView的parent,创建之后会调用setView方法。
4)、ViewRootImpl 的 setView方法,会将PhoneWindow添加到WMS中,通过 Session作为媒介。setView方法里面会调用requestLayout,发起绘制请求。
5)、requestLayout 一旦发起,最终会调用 performTraversals 方法,里面将会调用View的三个measure、layout、draw方法,其中View的draw 方法需要一个传一个Canvas参数。
6)、最后通过relayoutWindow 方法将Surface跟当前Window绑定,通过Surface的lockCanvas方法获取Surface的的Canvas,然后View的绘制就通过这个Canvas,最后通过Surface的unlockCanvasAndPost 方法提交绘制的数据,最终将绘制的数据交给SurfaceFlinger去提交给屏幕显示。
onMeasure()是用于测量视图的大小的。
measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec【它俩是int类型32位,前2位表示specMode,后30位表示specSize,这么做是为了减少对象的创建】,这两个值分别用于确定视图的宽度和高度的规格和大小。
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。
specMode一共有三种类型
widthMeasureSpec和heightMeasureSpec这两个值,通常情况下,都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?
/*****desiredWindowWidth是window的宽,lp是WindowManager.LayoutParams***/
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
/*****getRootMeasureSpec()*****/
private int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
由以上代码可以看出
1)我们需要自己去处理 warp_content 和 padding。因为无论是match_parent还是warp_content ,size都是windowSize。总是充满全屏的。
2)根视图的widthMeasureSpec和heightMeasureSpec就是window的宽高
这里才是真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大小
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以看到mBackground == null 为没有设置背景,那么返回mMinWidth ,也就是android:minWidth 这个属性所指定的值,这个值可以是0 ;如果View 设置了背景,则返回mMinWidth 与背景的最小宽度这两者的最大值。
getSuggestedMinimumWidth() 的返回值就是View 在UNSPECIFIED 情况下的测量宽。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
如果是ViewGroup,ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小
首先会去遍历当前布局下的所有子视图,然后逐个调用measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)方法来测量相应子视图的大小
然后分别调用了getChildMeasureSpec()方法来去计算子视图的width/heightMeasureSpec
最后调用子视图的measure()方法,并把计算出的width/heightMeasureSpec传递进去,之后的流程就和前面所介绍的一样了
需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。
measure过程结束后,视图的大小就已经测量好了,接下来就是layout的过程了。这个方法是用于给视图进行布局的,也就是确定视图的位置。ViewRootImpl的performTraversals()方法会在measure结束后继续执行,并调用View的layout()方法来执行此过程
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。可以看到,这里还把刚才测量出的宽度和高度传到了layout()方法中。
在layout()方法中,首先会调用setFrame()方法来判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘,同时还会在这里把传递过来的四个参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量。接下来会调用onLayout()方法
View中的onLayout()方法就是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图(ViewGroup)决定子视图的显示位置
ViewGroup中的onLayout()方法竟然是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法
/**如果你想改变View显示的位置,只需要改变childView.layout()方法的四个参数就行了**/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() > 0) {
View childView = getChildAt(0);
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
}
问:getWidth()方法和getMeasureWidth()方法到底有什么区别?
答:首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
//getWidth()=childView.getMeasuredWidth()-0 == childView.getMeasuredWidth()
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
//getWidth()=200-0 != childView.getMeasuredWidth()
childView.layout(0, 0, 200, 200);
measure和layout的过程都结束后,接下来就进入到draw的过程了。这里才真正地开始对视图进行绘制。ViewRootImpl中会继续执行并创建出一个Canvas对象,然后调用View的draw()方法并传递过来Canvas,来执行具体的绘制工作
在draw()中去调用了一下onDraw()方法,那么onDraw()方法里又写了什么代码呢?进去一看你会发现,原来又是个空方法啊。其实也可以理解,因为每个视图的内容部分肯定都是各不相同的,这部分的功能交给子类来去实现也是理所当然的。
然后对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图,那么也就不需要进行绘制了。因此你会发现View中的dispatchDraw()方法又是一个空方法,而ViewGroup的dispatchDraw()方法中就会有具体的绘制代码。
view的invalidate()—>view的invalidateInternal()—>ViewParent接口的invalidateChild()—>ViewGroup的invalidateChild()【因为ViewGroup实现了ViewParent接口】—>ViewRootImpl的invalidateChildInParent()—>最终在scheduleTraversals()方法中postCallback()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
执行doTraversal();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
又重新执行了performTraversals();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//重点在这
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
invalidate
view调用invalidate将导致当前view的重绘(draw调用),view的父类将不会执行draw方法;viewGroup调用invalidate会使viewGroup的子view调用draw,也就是viewGroup内部的子view进行重绘;
postinvalidate
在非UI线程调用,将非UI线程切换到UI线程,最后也是调用invalidate
requestLayout
只会导致当前view的measure和layout,而draw不一定被执行,只有当view的位置发生改变才会执行draw方法,因此如果要使当前view重绘需要调用invalidate
1、继承EditView,首先画一个圆角矩形
2、再画五个竖线
3、在画圆点,按输入的字符长度画
@SuppressLint("AppCompatCustomView")
public class PayEditTextView extends EditText {
public PayEditTextView(Context context) {
super(context);
}
public PayEditTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
initPaint();
}
private int maxCount = 6;
private int borderColor = Color.GRAY;
private int circleColor = Color.BLACK;
private Paint mPaint;
private Paint borderPaint;
private Paint divideLinePaint;
private Paint circlePaint;
private RectF rectF = new RectF();
private void initView() {
setBackgroundColor(Color.TRANSPARENT);//将背景变透明,去掉EditView的下横线
setCursorVisible(false);//隐藏光标
setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxCount)});//设置输入过滤器,输入的最大长度
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
borderPaint = getPaint(3, Paint.Style.STROKE, borderColor);
divideLinePaint = getPaint(3, Paint.Style.FILL, borderColor);
circlePaint = getPaint(5, Paint.Style.FILL, circleColor);
}
/**
* 设置画笔
*
* @param strokeWidth 画笔宽度
* @param style 画笔风格
* @param color 画笔颜色
* @return
*/
private Paint getPaint(int strokeWidth, Paint.Style style, int color) {
Paint paint = new Paint(ANTI_ALIAS_FLAG);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(style);
paint.setColor(color);
paint.setAntiAlias(true);
return paint;
}
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);//禁止EditView的默认实现
rectF.left = 0;
rectF.top = 0;
rectF.right = getWidth();
rectF.bottom = getHeight();
canvas.drawRoundRect(rectF, 12, 12, borderPaint);
int divideLineWStartX = getWidth() / maxCount;
for (int i = 0; i < maxCount - 1; i++) {
canvas.drawLine((i + 1) * divideLineWStartX, 0, (i + 1) * divideLineWStartX, getHeight(), divideLinePaint);
}
int startX = getWidth() / maxCount / 2;
LogU.Companion.d("textLength==" + textLength);
for (int i = 1; i <= textLength; i++) {
canvas.drawCircle(getWidth() / maxCount * i - startX, getHeight() / 2, getHeight() / 4, circlePaint);
}
}
private int textLength = 0;
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
textLength = text.toString().length();
}
}
主要是通过ValueAnimator.addUpdateListener监听动画执行过程改变值来重绘view的大小、样式
ValueAnimator.addUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
two_circle_distance = (int) animation.getAnimatedValue();
//default_two_circle_distance=(w-h)/2,意思是椭圆变成圆的所需的总值
int alpha = 255 - (two_circle_distance * 255) / default_two_circle_distance;
textPaint.setAlpha(alpha);
invalidate();
}
});
public class AnimationButton extends View {
/**
* view的宽度
*/
private int width;
/**
* view的高度
*/
private int height;
/**
* 圆角半径
*/
private int circleAngle;
/**
* 默认两圆圆心之间的距离=需要移动的距离
*/
private int default_two_circle_distance;
/**
* 两圆圆心之间的距离
*/
private int two_circle_distance;
/**
* 背景颜色
*/
private int bg_color = 0xffbc7d53;
/**
* 按钮文字字符串
*/
private String buttonString = "确认完成";
/**
* 动画执行时间
*/
private int duration = 1000;
/**
* view向上移动距离
*/
private int move_distance = 300;
/**
* 圆角矩形画笔
*/
private Paint paint;
/**
* 文字画笔
*/
private Paint textPaint;
/**
* 对勾(√)画笔
*/
private Paint okPaint;
/**
* 文字绘制所在矩形
*/
private Rect textRect = new Rect();
/**
* 动画集
*/
private AnimatorSet animatorSet = new AnimatorSet();
/**
* 矩形到圆角矩形过度的动画
*/
private ValueAnimator animator_rect_to_angle;
/**
* 矩形到正方形过度的动画
*/
private ValueAnimator animator_rect_to_square;
/**
* view上移的动画
*/
private ObjectAnimator animator_move_to_up;
/**
* 绘制对勾(√)的动画
*/
private ValueAnimator animator_draw_ok;
/**
* 是否开始绘制对勾
*/
private boolean startDrawOk = false;
/**
* 根据view的大小设置成矩形
*/
private RectF rectf = new RectF();
/**
* 路径--用来获取对勾的路径
*/
private Path path = new Path();
/**
* 取路径的长度
*/
private PathMeasure pathMeasure;
/**
* 对路径处理实现绘制动画效果
*/
private PathEffect effect;
public AnimationButton(Context context) {
this(context, null);
}
public AnimationButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AnimationButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LogU.Companion.d("AnimationButton");
initPaint();
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
start();
}
});
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
reset();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
/**
* 初始化所有动画
*/
private void initAnimation() {
set_rect_to_angle_animation();
set_rect_to_circle_animation();
set_move_to_up_animation();
set_draw_ok_animation();
animatorSet
.play(animator_move_to_up)
.before(animator_draw_ok)//在play动画之后执行
.after(animator_rect_to_square)//在play动画之前执行
.after(animator_rect_to_angle);//在play动画之前执行
}
/**
* 设置矩形过度到圆角矩形的动画
*/
private void set_rect_to_angle_animation() {
animator_rect_to_angle = ValueAnimator.ofInt(0, height / 2);
animator_rect_to_angle.setDuration(duration);
animator_rect_to_angle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleAngle = (int) animation.getAnimatedValue();
invalidate();
}
});
}
/**
* 设置圆角矩形过度到圆的动画
*/
private void set_rect_to_circle_animation() {
animator_rect_to_square = ValueAnimator.ofInt(0, default_two_circle_distance);
animator_rect_to_square.setDuration(duration);
animator_rect_to_square.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
two_circle_distance = (int) animation.getAnimatedValue();
//default_two_circle_distance=(w-h)/2,意思是椭圆变成圆的所需的总值
int alpha = 255 - (two_circle_distance * 255) / default_two_circle_distance;
textPaint.setAlpha(alpha);
invalidate();
}
});
}
/**
* 设置view上移的动画
*/
private void set_move_to_up_animation() {
final float curTranslationY = this.getTranslationY();
animator_move_to_up = ObjectAnimator.ofFloat(this, "translationY", curTranslationY, curTranslationY - move_distance);
animator_move_to_up.setDuration(duration);
animator_move_to_up.setInterpolator(new AccelerateDecelerateInterpolator());
}
/**
* 绘制对勾的动画
*/
private void set_draw_ok_animation() {
animator_draw_ok = ValueAnimator.ofFloat(1, 0);
animator_draw_ok.setDuration(duration);
animator_draw_ok.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
startDrawOk = true;
float value = (Float) animation.getAnimatedValue();
effect = new DashPathEffect(new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, value * pathMeasure.getLength());
okPaint.setPathEffect(effect);
invalidate();
}
});
}
private void initPaint() {
paint = new Paint();
paint.setStrokeWidth(4);
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
paint.setColor(bg_color);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextSize(40);
textPaint.setColor(Color.WHITE);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setAntiAlias(true);
okPaint = new Paint();
okPaint.setStrokeWidth(10);
okPaint.setStyle(Paint.Style.STROKE);
okPaint.setAntiAlias(true);
okPaint.setColor(Color.WHITE);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
LogU.Companion.d("onSizeChanged");
width = w;
height = h;
default_two_circle_distance = (w - h) / 2;
initOk();
initAnimation();
}
private int mWidth;
private int mHeight;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
int widthSize=MeasureSpec.getSize(widthMeasureSpec);
int heightSize=MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mWidth=widthSize;
}else {
mWidth=80*2+getPaddingLeft()+getPaddingRight();
}
if (heightMode == MeasureSpec.EXACTLY) {
mHeight=heightSize;
}else {
mHeight=80*2+getPaddingTop()+getPaddingBottom();
}
setMeasuredDimension(mWidth,mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
draw_oval_to_circle(canvas);
drawText(canvas);
if (startDrawOk) {
canvas.drawPath(path, okPaint);
}
}
/**
* 绘制长方形变成圆形
*
* @param canvas 画布
*/
private void draw_oval_to_circle(Canvas canvas) {
rectf.left = two_circle_distance;
rectf.top = 0;
rectf.right = width - two_circle_distance;
rectf.bottom = height;
//画圆角矩形
canvas.drawRoundRect(rectf, circleAngle, circleAngle, paint);
}
/**
* 绘制文字
*
* @param canvas 画布
*/
private void drawText(Canvas canvas) {
textRect.left = 0;
textRect.top = 0;
textRect.right = width;
textRect.bottom = height;
Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
//文字绘制到整个布局的中心位置
canvas.drawText(buttonString, textRect.centerX(), baseline, textPaint);
}
/**
* 绘制对勾
*/
private void initOk() {
//对勾的路径
path.moveTo(default_two_circle_distance + height / 8 * 3, height / 2);
path.lineTo(default_two_circle_distance + height / 2, height / 5 * 3);
path.lineTo(default_two_circle_distance + height / 3 * 2, height / 5 * 2);
pathMeasure = new PathMeasure(path, true);
}
/**
* 启动动画
*/
public void start() {
animatorSet.start();
}
/**
* 动画还原
*/
public void reset() {
// startDrawOk = false;
// circleAngle = 0;
// two_circle_distance = 0;
// default_two_circle_distance = (width - height) / 2;
// textPaint.setAlpha(255);
// setTranslationY(getTranslationY() + move_distance);
// invalidate();
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。