Android自定义控件总结

绘制

绘制基础

坐标系

每个view的坐标系原点为左上角那个点,水平方向为x轴,右正左负,竖直方向为y轴,下正上负。

canvas.draw

canvas.drawColor //绘制区域涂上颜色(设置底色/蒙层)

canvas.drawCircle(float centerX(圆心X坐标),float centerY(圆心Y坐标),float radius(圆的半径,单位像素),Paint paint)

canvas.drawBitmap

canvas.drawRect(float left,float top,float right,float bottom,Paint paint) //画矩形

canvas.drawRect(RecF rect,Paint paint)

canvas.drawRect(Rect rect,Paint paint)

canvas.drawPoint(float x(点X轴坐标),float y(点Y轴坐标),Paint paint)//画点

点的大小 ->paint.setStrokeWidth(width)

点的形状 ->paint.setStrokeCap(cap)

ROUND(圆形),BUTT(平头),SQUARE(方头)

canvas.drawPoints()//批量画点

canvas.drawOval(float left(左边界点),float top(上边界点),float right(右边界点),float bottom(下边界点),Paint paint) //画椭圆

canvas.drawLine(float startX(起点X轴坐标),float startY(起点Y轴坐标),float stopX(终点X轴坐标),float stopY(终点X轴坐标),Paint paint) (setStyle对直线没有影响)

canvas.drawLines(批量画线)

canvas.drawRoundRect(float left,float top,float right,float bottom,float rx(圆角的横向半径),float ry(圆角的纵向坐标),Paint paint)//画圆角矩形

canvas.drawRoundRect(RectF rect,float rx, float ry,Paint paint)

canvas.drawArc(float left, float top, float right, float bottom, float startAngle(起始角度,顺时针为正,逆时针为负), float sweepAngle(弧形划过角度), boolean useCenter(是否连接到圆心), Paint paint) //绘制弧形或扇形 根据弧形所在椭圆进行绘制

canvas.drawPath() //通过描述路径的方式来绘制图形

path.addXxx() —添加子图形

path.addCircle(x,y,radius,dir(路径方向:顺时针/逆时针))

path.xxxTo —画线

path.lineTo()

path.rLineTo()

path.close() —封闭当前图形

path.setFillType(Path.FillType ft) //设置填充模式

canvas.drawBitmap(Bitmap bitmap,float left,float top,Paint paint);//画bitmap

canvas.drawBitmap(Bitmap bitmap,Rect src,RectF dst,Paint paint)

canvas.drawBitmap(Bitmap bitmap,Rect src,Rect dst,Paint paint)

canvas.drawBitmap(Bitmap bitmap,Matrix matrix,Paint paint)

canvas.drawText(String text,float x(起点x坐标),float y(起点y坐标),Paint paint) //绘制文字

piant详解(画笔)

Paint.setStyle //设置绘制模式

FILL 填充模式(默认)

STROKE 画线模式

FILL_AND_STROKE 既画线又填充

Paint.setStrokeWidth //设置线条宽度 (仅在style:Stroke、FILL_AND_STROLE下有效)

Paint.setTextSize //设置文字大小

Paint.setAntiAlias //设置抗锯齿开关

Paint.setTextSize(float textSize)//设置文字大小

Paint.setStrokeJoin(Paint.Join join) //设置拐角的形状

MITER//尖角(默认)

BEVEL//平角

ROUND//圆角

Paint.setStokeMiter(float miter)//设置MITER型拐角的延长线的最大值

设置颜色

直接设置颜色

Paint.setColor(int color)

Paint.setARGB(int a,int r,int g,int b)

Paint.setShader(Shader shader) //设置shader

LinearGradient 线性渐变

RadialGradient 辐射渐变

SweepGradient 扫描渐变

BitmapShader 用bitmap的像素来作为图形或文字的填充

ComposeShader 混合着色器,多个shader混合使用

Paint.setColorFilter(ColorFilter colorFilter) //设置颜色过滤

Paint.setXfermode(Xfermode xfermode) //以要绘制的内容为源图像,以View中已有内容作为目标图像,选取一个PorterDuff.Mode作为绘制内容的颜色处理方案。

色彩优化

Paint.setDither(boolean dither) //设置抖动来优化色彩深度降低时的绘制效果

Paint.setFilterBitmap(boolean filter) //设置双线性过滤优化Bitmap放大绘制的效果

可以理解为 由马赛克变成模糊状态

Paint.setPathEffect(PathEffect effect)//使用PathEffect设置形状的轮廓效果

CornerPathEffect//把所有的拐角变成圆角

DiscretePathEffect//把线条进行随机的偏离

DashPathEffect//使用虚线

PathDashPathEffect//使用一个Path来绘制虚线

SumPathEffect//组合效果

ComposePathEffect//组合效果,组合有先后顺序

Paint.setShadowLayer(float radius,float dx,float dy,int shadowColor)//添加阴影

Paint.setMaskFilter(MaskFilter maskfilter)//在绘制层上方的附加效果

BlurMaskFilter //模糊效果

new BlurMaskFilter(float radius(模糊范围),BlurMaskFilter.Blur style(模糊类型))

EmbossMaskFilter//浮雕效果

new EmbossMaskFilter(float[] direction(光源的方向),float ambient(环境光强度),float specular(炫光系数),float blurRadius(光线范围))

获取绘制的Path

getFillPath(Path src,Path dst)//实际path

getTextPath(Stirng text,int start,int end,float x,float y,Path)/getTextPath(char[] text,int index,int count,float x,float y,Path path)//文字的path

canvas.drawText()//文字的绘制

drawTextOnPath()//沿一条Path来绘制文字

StaticLayout //绘制文字,支持换行

paint.setFakeBoldText(booleab fakeBoldText)//是否使用伪粗体

paint.setStrikeThruText()//是否加删除线

paint.setUnderLineText(boolean underlineText)//是否加下划线

paint.setTextSkewX(float skewX)//设置文字横向错切角度

paint.setTextScaleX(float scaleX)//设置文字横向放缩

paint.setLetterSpacing(float letterSpacing)//设置字符间距,默认为0

paint.setTextAlign(Paint.Align align)//LEFT、CENTER、RIGHT默认为LEFT

paint.setTextLocale(Locale locale)/paint.setTextLocales(LocaleList locales) //设置绘制所用的地域

paint.setHinting(int mode)//是否启用字体微调

测量文字尺寸类:

paint.getFontSpacing();//获取推荐的行距

paint.getFontMetrics();//获取point的FontMetrics

AndroidFontMetrics.png

baseline:基准线

ascent/descent:普通字符的顶部和底部范围

top/bottom:限制字型的顶部和底部

leading:行的额外间距,即上一行字的bottm与下一行字的top距离

paint.getTextBounds(String text(测量的文字),int start(文字的起始位置),int end(文字的结束位置),Rect bounds(文字显示范围的对象))//获取文字的显示范围

paint.measureText(String text)//测量文字占用的宽度

measureText()>getTextBounds()

paint.getTextWidths(String text,float[] widths)//获取字符串中每个字符的宽度,并把结果填入参数widths

paint.breakText(String text((要测量的文字),boolean measureForwards(测量的方向),float maxWidth(宽度上限(超出上限会截断文字)),float[] measuredWidth(用于接受数据))//测量完成后会把文字宽度赋给measureWidth[0]

paint.getRunAdvance(CharSequence text,int start(文字的起始坐标),int end(文字的结束坐标),int contextStart(上下文的起始坐标),int ContextEnd(上下文的结束坐标),boolean isRtl(文字的方向),int offset(字数的偏移))//计算某个字符处光标的x坐标

paint.getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance)//计算出文字中最接近这个位置的字符偏移量

paint.hasGlyph(String s)//检查指定的字符串是否是一个单独的字型

canvas对绘制的辅助—范围裁切和几何变换

范围裁剪

canvas.clipRect()//范围裁剪

canvas.clipPath()//根据范围裁剪

几何变换

二维变换

canvas.translate(float dx,float dy)//位移

canvas.rotate(float degrees,float px,float py)//旋转

canvas.scale(float sx(横向缩放倍数),float sy(纵向缩放倍数),float px,float py)//缩放

canvas.skew(float sx(x轴的错切系数),float sy(y轴的错切系数))//错切

使用Matrix来做常见变化

canvas.setMatrix(matrix)//用Matrix直接替换Canvas当前的变换矩阵

canvas.concat(matrix)//用Canvas当前的变换矩阵和Matrix相乘

使用Matrix来做常见变化

Camera.rotate*()//三维旋转

使用不同的绘制方法来绘制顺序

1、super.draw()//总调度方法

2、super.onDraw()

3、dispatchDraw()//绘制子View的方法

绘制顺序:

draw()总调度方法,view的绘制过程都发生在draw()方法里

1、背景(drawBackground()不能重写)-------android:background:/View.setBackgroundXxx()

2、主体(onDraw())

3、子View(dispatchDraw())

4、滑动边缘渐变和滑动条(onDrawForeground())-------android:scrollbarXxx/View.setXXXScrollBarXXX()

5、前景(onDrawForeground())-------android:foreground/View.setForeground()

属性动画 Property Animation

ViewPropertyAnimator

view.animate().translationX()//x轴偏移

ObjectAnimator

1、如果是自定义控件,需要添加setter、getter方法

2、ObjectAnimator.ofXXX()创建ObjectAnimator对象

3、用start()方法执行动画

通用方法

setDuration(int duration)//设置动画时长

setInterpolator(Interpolator interpolator)//设置插值器

设置监听器

ViewPropertyAnimator.setListener()/ObjectAnimator.addListener()

ViewPropertyAnimator.setUpdateListener()/ObjectAnimator.addUpdateListener()

ObjectAnimator.addPauseListener()

ViewPropertyAnimator.withStartAction/EndAction()

TypeEvaluator

ArgbEvaluator//颜色渐变动画

PropertyValuesHolder//同一个动画中改变多个属性

PropertyValuesHolders.ofKeyframe()//把同一个属性拆分

AnimatorSet//多个动画配合执行

硬件加速

targetSdkVersion>=14,硬件加速默认开启

view.setLayerType()

LAYER_TYPE_SOFTWARE:使用软件来绘制View Layer,绘制到Bitmap,并顺便关闭硬件加速

LAYER_TYPE_HARDWARE:使用GPU来绘制View Layer,绘制到OpenGL texture(如果硬件加速关闭,那么行为和LAYER_TYPE_SOFTWARE一致)

LAYER_TYPE_NONE:关闭View Layer

View Layer可以加速无invalidate()(例如动画)时的刷新效率,但对于需要调用invalidate()的刷新无法加速

硬件加速并不支持所有的绘制操作

硬件加速api支持.png
硬件加速画布缩放api支持.png

布局

布局的两个阶段

1、测量(measure)

View:View在onMeasuer中会计算自己的尺寸然后保存

ViewGroup:ViewGroup在onMeasure中会调用所有子View的measure让它们进行自我测量,并根据子View

计算出的期望尺寸来计算他们的事迹尺寸和位置然后保存。

2、布局(layout)

View:无子View所以onLayout不做任何处理

ViewGroup:ViewGroup在onLayout中会调用自己所有子View的layout方法,把他们的尺寸、位置传给他们, 让他们完成自我布局。

布局过程自定义的三种方式

MeasureSpec

MeasureSpec = mode + size :父类传递过来给当前View的一个建议值

MeasureSpec.getMode(int spec)//获取模式

MeasureSpec.getSize(int spec)//获取数值

限制分类:

UNSPECIFIED(不限制)

AT_MOST(限制上限)->wrap_content

EXACTLY(限制固定值)->match_parent/具体值

1、重写onMeasure来修改已有的View尺寸

(1)、重写onMeasure方法,调用super.onMeasure触发原有的自我测量。

(2)、在super.onMeasure下用getMeasureWidth与getMeasureHeigh获取之前测量的结果,使用自己的算法计算新结果。

(3)、调用setMeasureDimension保存新结果。

2、重写onMeasure来全新定制自定义View的尺寸

与1区别,保证计算的同时,保证结果满足父View给出的尺寸限制

(1)重写onMeasure,计算出View的尺寸

(2)使用resolve让子View的计算结果符合父View的限制,也可不使用该方法自己定义

3、重写onMeasure和onLayout来全新定制自定义ViewGroup的内部布局

两个注意点:

子控件间的margin值

1、重写generateLayoutParams()和generateDefaultLayoutParams()

2、获取margin值 MarginLayoutParams lp = (MarginLayoutParams )child.getLayoutParams()

子控件间的padding值

1、测量后直接getPaddingLeft、getPaddingTop、getPaddingRight、getPaddingBottom

重写onMeasure来计算内部布局

(1)调用每个子View的measure来计算子View的尺寸

结合layout_xxx和自己可用空间

(2)计算子View的位置并保存子View的尺寸和位置

(3)计算自己的尺寸并用setMeasureDimension保存

重写onLayout来摆放子View

(1)调用每个子View的layout,让他们保存自己的位置和尺寸

view工作原理

触摸反馈

触摸事件

1、ACTION_DOWN:手指刚接触屏幕,按下去的那一瞬间

2、ACTION_MOVE:手指在屏幕上移动

3、ACTION_UP:手指从屏幕上松开的瞬间

事件序列:从ACTION_DOWN -> ACTION_UP

ViewGroup:
DispatchTouchEvent
• return true:表示该View内部消化掉了所有事件
• return false:表示事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费
• return super.dispatchTouchEvent(ev):默认事件将分发给本层的事件拦截onInterceptTouchEvent方法 进行处理
OnInterceptTouchEvent
• return true:表示将事件进行拦截,并将拦截到的事件交由本层控件的onTouchEvent进行处理
• return false:表示不对事件进行拦截,事件得以成功分发到子View
• return super.onInterceptTouchEvent(ev):默认表示不拦截该事件,并将事件传递给下一层View的 dispatchTouchEvent
OnTouchEvent 默认false
• return true:表示onTouchEvent处理完事件后消费了此次事件
• return fasle:表示不响应事件,那么该事件将会不断向上层View的onTouchEvent方法传递,直到某个View的 onTouchEvent方法返回true
• return super.dispatchTouchEvent(ev):表示不响应事件,结果与return false一样
子View不存在分发:
• DispatchTouchEvent 事件分发
• OnTouchEvent 默认true

如下图为事件分发流程图:


事件分发.jpg

---------------------- 以上总结部分源自Hencoder教程 ------------------------------

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343