您的位置:首页 > 移动开发 > Android开发

Android 自绘控件开发探索1

2017-04-06 16:52 381 查看
Android 自绘控件,听起来是高大上的对吧,然鹅,真正开发起来是一件非常苦逼的事情。对于初学者来说,总是有这样或者那样的坑等着去踩,为了以后少踩些坑,特奉上自绘控件的一些心得,有不对的地方欢迎大神批评指正0.0首先,你需要知道屏幕坐标系是个什么鬼,不知道坐标系很容易被各种坐标值搞晕:坐标系统以左上角为原点,右方向为x正向,下方向为y正向。获取左上右下的坐标,是显示区域左上右下边界的坐标。相对坐标是相对于父控件的坐标,绝对坐标是相对于屏幕坐标系的坐标。控件,说白了就是一块有特定功能的显示区域。那么问题来了,从你的代码开始,它是如何显示在屏幕上的呢?拢共分三步:1测量,2布局,3绘制。1:measure 测量大小,你首先需要知道这个控件要多大的显示区域。对于不包含子view的自绘控件(也就是原始view)来说,measure过程是测量自身大小;对于ViewGroup,除测量自身,还需调用子view的measure方法。so,measure是一个递归的过程,只有所有子View的大小知道了,父控件的大小才能确定。、measure 方法是一个final类型的方法,其内部会调用onMeasure方法, onMeasure中会设置view宽高的测量值。xml中对控件大小有三种设定,精确值,wrap_Content,match_Parent, 对于wrapContent和match_Parent,安卓采用AT_MOST即最大值模式;对于精确值采用EXACTLY即精确值模式。当自定义控件使用wrap_Content,其测量模式为ATMOST,在这种测量模式下,它的宽高为parentSize。所以当自绘控件使用wrap_Content时,等同于使用了match_Parent。那么如何解决呢?我的view就不能用wrap_Content了吗 ,当然可以,附上方案:重写onMeasure方法,判断测量模式是啥,如果是ATMOST,说明用户采用了wrapContent,这个时候设置你的宽高就行。 
/**
* 使view能支持wrapcontent
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST)
setMeasuredDimension(mWidth, heightMeasureSpec);
else if (heightSpecMode == MeasureSpec.AT_MOST)
setMeasuredDimension(widthMeasureSpec, mHeight);

}
上面的mHeight和mWidth就是wrap_Content时的测量大小。
刚才我一直再说测量大小而非最终大小,因为最终大小是在layout阶段确定的,但几乎所有情况下测量大小就是最终大小。有好事儿的问了,什么情况下不等于嘞?。。。这个我先
挖个坑,等谈layout的时候再讲。
测量完毕之后,就可以获得测量后的宽高值。
2:layout过程将确定在屏幕上显示的位置。layout方法会确定自身四个顶点的位置,确定了四个顶点,就确定了自身的位置。接着会调用onlayout方法确定子view的位置。so,与
测量过程相反,布局过程是从上而下的。
 回答上面好事的人的问题,啥时候测量大小不等于最终大小,就是我们强行找事的时候:布局结束后,就可以获得最终显示的宽高值。3: 绘制过程,绘制过程将把view绘制到屏幕上。步骤: 绘制背景(background.draw)----> 绘制自身(onDraw)----->绘制子视图(dispatchDraw)----->绘制装饰(onDrawScrollBars)绘制过程告诉我们,view会通过dispatchDraw方法将绘制任务一层一层的下发。在这里有个坑,view的ondraw方法中啥也没写,这意味着如果你xml中定义了padding,就得自己让他们有效。具体做法是在ondraw方法中获取padding,获取测量宽高或最终宽高相减,得到显示区域的宽高。@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//使view支持paddingfinal int paddingLeft = getPaddingLeft();final int paddingRight = getPaddingRight();final int paddingTop = getPaddingTop();final int paddingButtom = getPaddingBottom();this.__Left = paddingLeft;this.__Top = paddingTop;this.__Right = getWidth() - paddingRight;this.__Bottom = getHeight() - paddingButtom;this.mHeight = __Bottom - __Top;this.mWidth = __Right - __Left;}这样,view就支持了padding._left _right _top _buttom是padding之后的绘制区域边界,如果你的view还需要留白,自己去设置。还有view常用到的自定义属性:1:在attrs.xml中新建,name与自定义view相同
<declare-styleable name="MyView_ArcScale"><attr name="inCircleColor" format="reference" /><attr name="outArcColor" format="reference" /><attr name="testColor" format="reference" /><attr name="outArcWidth" format="dimension" /><attr name="testSize" format="dimension" /><attr name="degreeStart" format="integer" /></declare-styleable>
2:在布局文件中引用,需使用全路径
<com.example.administrator.view.mywidgit.myview.MyView_ArcScaleandroid:layout_width="wrap_content"android:layout_height="wrap_content"MyView_ArcScale:inCircleColor="@android:color/holo_purple"MyView_ArcScale:outArcColor="@android:color/holo_blue_light"  
3:在自绘控件中获取
final TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.MyView_ArcScale);this.inCricleColor = typedArray.getColor(R.styleable.MyView_ArcScale_inCircleColor, inCricleColor);this.outArcColor = typedArray.getColor(R.styleable.MyView_ArcScale_outArcColor, outArcColor);this.textColor = typedArray.getColor(R.styleable.MyView_ArcScale_testColor, text4000Color);this.outArcWidth = typedArray.getDimension(R.styleable.MyView_ArcScale_outArcWidth, outArcWidth);this.startDegree = typedArray.getInt(R.styleable.MyView_ArcScale_degreeStart, startDegree);this.testSize = typedArray.getDimension(R.styleable.MyView_ArcScale_testSize, testSize);typedArray.recycle();//回收资源  
这样,view支持padding,wrapconent,还支持自定义属性,应该能满足基本要求了。附上MyStandardView:public abstract class MyStandardView extends View {private int mHeight = 100;private int mWidth = 100;private int __Top;private int __Left;private int __Right;private int __Bottom;public MyStandardView(Context context) {this(context, null, 0);}public MyStandardView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MyStandardView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mHeight = getDefaultHeight();mWidth = getDefaultWidth();}/*** 绘制区域的上边界* @return*/protected final int get__Top() {return __Top;}/*** 绘制区域的左边界* @return*/protected final int get__Left() {return __Left;}/*** 绘制区域的右边界* @return*/protected final int get__Right() {return __Right;}/*** 绘制区域的下边界* @return*/protected final int get__Bottom() {return __Bottom;}/*** 获取wrapContent时的高度** @return*/protected abstract int getDefaultHeight();/*** 获取wrapContent时的宽度** @return*/protected abstract int getDefaultWidth();@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//使view支持paddingfinal int paddingLeft = getPaddingLeft();final int paddingRight = getPaddingRight();final int paddingTop = getPaddingTop();final int paddingButtom = getPaddingBottom();this.__Left = paddingLeft;this.__Top = paddingTop;this.__Right = getWidth() - paddingRight;this.__Bottom = getHeight() - paddingButtom;this.mHeight = __Bottom - __Top;this.mWidth = __Right - __Left;}/*** 获取layout过程之后控件的高度,即最终高度* 仅在layout之后调用有效** @return*/protected final int getFinalHeight() {return this.mHeight;}/*** 获取layout过程之后控件的宽度,即最终宽度* 仅在layout之后调用有效** @return*/protected final int getFinalWidth() {return this.mWidth;}/** * 使view能支持wrapcontent * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) setMeasuredDimension(mWidth, heightMeasureSpec); else if (heightSpecMode == MeasureSpec.AT_MOST) setMeasuredDimension(widthMeasureSpec, mHeight); }}

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息