在自定义View系列中以上9篇都是”谷歌的小弟”的原创博文,在这个系列教程中对大部分知识点都做了详细的阐述。在我通读了以上文章后受益匪浅啊,原理明白了就算不强记以后很容易能想得到但是一些用法之类的查找确实麻烦,所以再来一篇总结,结合自己的理解和应用加深理解且以后回忆起来也提供一条思路。顺序就按照小弟的来。
Scroller.class:下节总结。
ViewDragHelper.class:处理拖拽动作的类。
mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {});创建实例,三个参数基本就是这三个。对应的内部方法。
/** * 唯一的抽象接口 * 1. 返回true表示可以捕获这个View的动作 * 2. 我们可以通过它的参数来判断哪些 */public abstract boolean tryCaptureView(View child, int pointerId);/** * 处理水平方向的越界 * 1. 返回值是我们最终拖拽的距离 * 2. 参数left是手势拖拽的距离 * 3. 判定X是否越界, * 最小位移min: paddingLeft * 最大位移max:parent.width-paddingRight-childView.width * a. left<min return min; * b. left>max return max; * c. else return left; */public int clampViewPositionHorizontal(View child, int left, int dx);/** * 处理垂直方向的越界(参照水平方向) */ public int clampViewPositionVertical(View child, int top, int dy);/** * 捕获子View动作 */ public void onViewCaptured(View capturedChild, int activePointerId);/** * 释放子View动作 */ public void onViewReleased(View releasedChild, float xvel, float yvel);提供一个图给大家脑补…
/*** ViewGroup的事件分发交给ViewDragHelper处理*/@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) { return mDragHelper.shouldInterceptTouchEvent(ev);}/*** ViewGroup的事件消费交给ViewDragHelper处理*/@Overridepublic boolean onTouchEvent(MotionEvent event) { mDragHelper.processTouchEvent(event); return true;}MeasureSpec.class
封装了parentView对childView布局的要求;32位,高2位是mode,低30位是size;EXACTLY,精确模式,已知大小AT_MOST,未检测出大小,但最大不超过其sizeUNSPECIFIED,不做考虑View的测量过程,我们通过源码可以知道,View的大小不仅仅它本身的布局有关,还和parentView的MeasureSpec相关,view的size由parentView的MeasureSpec.mode和其本身的布局共同决定,对源码的分析可以得出以下结论。
① 当View的宽高为具体值value时,不管其parentView的MeasureSpec.mode。 size = value; mode = EXACTLY;② 当View的宽高布局为match_parent,parentView.mode为EXACTLY。 size = parentLeftSize; mode = EXACTLY;③ 当View的宽高布局为match_parent,parentView.mode为AT_MOST。 size = parentLeftSize; mode = AT_MOST;④ 当View的宽高布局为wrap_content,不管其parentView的MeasureSpec.mode。 size = parentLeftSize; mode = AT_MOST;
parentLeftSiz表示parentView剩下的空间;
onMeasure()分析 在了解了MeasureSpec后,我们具体分析View如何获取大小的。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }protected int getSuggestedMinimumWidth()。看是否有background,没有的话size=minWidth(或minHeight),有的话取两者中较大的值为size;public static int getDefaultSize(int size, int measureSpec)。可知size=MeasureSpec.getSize(measureSpec);就是说onMeasure()过程中View的size是由ViewGroup()绘制 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed);的时候设置的size决定的,也就是这个图。我们看到情况④,当childView的布局为wrap_content时,childView的size一直都是parentLeftSize,即父控件剩余空间,也可以说是match_parent,就是填满空间了…,所以我们在自定义View时如果不对这种情况进行处理的话就会造成我们的自定义View布局为wrap_content时,实际效果是match_parent;为什么TextView等等控件在上述情况表现正常呢,这是因为在源码中已经对上述情况作了处理。那么我们应该如何处理?@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = 100; int height = 200; int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if( widthMode==MeasureSpec.AT_MOST && heightMode==MeasureSpec.AT_MOST ) setMeasuredDimension(width, height); else if (widthMode==MeasureSpec.AT_MOST) setMeasuredDimension(width, heightSize); else if ( heightMode==MeasureSpec.AT_MOST ) setMeasuredDimension(widthSize, height);}我们在重写onMeasure()方法时,利用判断childView.mode来甄别布局是否为wrap_content。当mode为AT_MOST时我们就认为是wrap_content。但是我们查看图表可知,情况③也是AT_MOST模式,但是它的属性是match_parent,Why?其实我们通过反证法,可以得出结论此种情况是不存在(不合理)的,所以正常情况不会出现,所以可以忽略。有两个方法有必要讨论一下,getMeasuredWidth()和getWidth()方法。它们有何区别。
getMeasuredWidth()方法查看源码,return mMeasuredWidth变量,查找源码可以看出此变量在 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)方法中给赋值的,在网上查发现,’setMeasuredDimension()’方法调用了setMeasuredDimensionRaw(),而onMeasure()调用了setMeasuredDimension()。getWidth()方法查看源码发现,返回值是”mRight - mLeft”,且它在onLayout()过程后才有的值。那么,getMeasuredWidth()方法的值是在onMeasure()测量过程后获取的大小。并且它的值和我们在setMeasuredDimension()设置的值相关。那么,getWidth()方法的值是在onLayout()摆放过程之后获取的大小,并且它的值是坐标相减的结果。总结完onDraw()之后,会有例子体现。
绘制的过程从以下几个方面来简述。
源码draw(canvas)绘制过程 - 从源码注释可看出View的绘制大体上分为6步。 - 1. 绘制背景drawBackground(canvas); - 2. 保存当前画布的堆栈状态并在该画布上创建Layer用于绘制View在滑动时的边框渐变效果(可忽略); - 3. 绘制View的内容,protected void onDraw(Canvas canvas) {};我们需要实现的方法: - 4. 画子View,dispatchDraw(canvas); - 5. 绘制当前视图在滑动时的边框渐变效果(可忽略); - 6. 绘制View的滚动条;
Canvas、Bitmap、Paint的关系 - 源码中看出我们绘制View时重写onDraw()方法,而方法中只有参数Canvas,查看官方文档可以得到绘制4要素 - 1. 用什么工具画? Paint类 。 - 2. 把画画在哪里?Bitmap上,Bitmap承载和呈现了画的各种图形。 - 3. 画的内容?根据自己的需求画圆,画直线,画路径。 - 4. 怎么画? canvas各种操作。
Canvas类的常用操作 - canvas.translate(x, y); 移动坐标系 - canvas.rotate(angle); 旋转坐标系 - canvas.clipXxx(); 裁剪某个形状,就是把坐标系放入到裁剪区域 - canvas.save()生成一个透明的图层Layer; - canvas.restore()Layer操作的东西覆盖到原来的图形上;
PorterDuffXfermode图形合成的规则 -
Bitmap和Matrix矩阵处理图像 -
Shader渲染图像 -
PathEffect画路径时样式效果 -
新闻热点
疑难解答