目录

一、自定义View

一、自定义View主要分为四种

二:自定义View的注意事项

二、View的绘制流程

三、onMeasure

(1)onMeasure方法简介

(2)MeasureSpec简介

(3)getMeasuredWidth()与getWidth()区别

(4)获取子控件margin方法

三、onLayout

四、View的位置参数

五、View的滑动

六、自定义view滑动冲突

1.外部拦截法

七、Paint常用方法

八、Canvas

1、常用的基本方法

2、 drawPath绘制路径

(1)直线路径

(2)矩形路径

(3)画弧线

(4)其他路径

九、贝塞尔曲线

(1)//二阶贝赛尔

(2)实现手指轨迹

(3)实现水波纹效果

十、补充

一、Bitmap创建方式:

二、用bitmap创建canvas

三、获取点击事件发生的x和y坐标

四、TouchSlop

五、VelocityTracker

六、GestureDetector:

七、Scroller:弹性滑动对象,用于实现View的弹性滑动;

八、在Activity中获取宽高

九、onDraw、dispatchDraw区别

十、getLocationOnScreen

十一、Save、restore、saveLayer区别

十二、硬件加速

其他、色彩矩阵


一、自定义View

一、自定义View主要分为四种

(1)继承View重写onDraw方法

这种方法主要用于实现一些不规则的效果,即这种效果不方便通过布局的组合方式来达到,往往需要静态或者动态地显示一些不规则图片,显然这需要通过绘制的方式来实现,即重写onDraw方法。采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。

(2)继承特定的View(比如TextView)

一般用于扩展已有的View功能,比如TextView,这种方法不需要自己支持wrap_content和padding等;

(3)继承ViewGroup派生特殊的Layout

这种方法主要用于实现自定义布局,即除了Linerlayout、RelativeLayout、FrameLayout这几种系统布局之外,我们需要重新定义一种新布局,当某种效果看起来像多种View组合在一起的时候,可以采用这种方法来实现。采用这种方式稍微复杂一些,需要合适处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局过程;

(4)继承特定的ViewGroup(比如LinerLayout)

采用这种方法不需要自己处理ViewGroup的测量和布局这两个过程,方法三能实现的效果,本方法也能实现,区别在于方法3更趋向于View的底层;

二:自定义View的注意事项

 2.1:让View支持wrap_content
    直接继承View或ViewGroup的控件,如果不在onMeasrue中对wrap_content做特殊处理,那么当外界在布局中使用wrap_content时就无法达到预期效果;
  2.2:让View支持padding
     直接继承View的控件,如果不在draw方法中处理padding,那么padding属性时无效的;另外,直接继承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素margin对其造成的影响,不然将导致padding和子元素的margin失效;
  2.3:尽量不要在View中使用Handler,View提供了post方法
  2.4:View中如果有线程或动画,当View不可见时需要及时停止
    当View中有线程或者动画时,当View不可见时,需要及时停止动画和线程,否则会造成内存泄露。
    当包含此View的Activity退出或者当前View被remove不可见时,View的onDetachedFromWindow方法会被调用,所以可以在这个方法中进行动画和线程的停止;
    与onDetachedFromWindow对应的方法时onAttachedToWindow;
  2.5:View带有滑动嵌套情形时,需要处理好滑动冲突

二、View的绘制流程

  • 在Activity的attach()方法中创建window对象。Window的具体实现类是PhoneWinow。
  • 然后在Activity的onCreate方法中调用setContentView方法,设置我们的布局文件。setCOntentView的具体实现是AppCompatDelegateImpl。
  • 接着会调用AppCompatDelegateImpl的setContentView方法。在这个方法中,会创建SubDecorView。怎么创建的呢?解析activity的theme,根据主题设置title、actionBar、加载一个默认的布局文件生成decorView。
  • 创建完SubDecorView之后,然后调用Window.setContentView(subDecor);把SubDecorView传递给Window。
  • 在AppCompatDelegateImpl的setContentView方法中,创建完SubDecorView之后,然后通过findViewById(R.id.content)获取到SubDecorView的子view contentParent。这是ViewGroup。然后调用LayoutInflater的inflate方法,把我们的布局文件添加到了contentParent中。也就意味着把我们的布局文件添加到了SubDecorView中。
  • 在ActivityThread的handleResumeActivity方法中,调用完Activity的onResume方法之后,通过Activity获取到Activity的Window,然后通过window获取DecorView。然后获取Activity的WindowManager,然后调用WindowManager的addView方法。
  • 在调用WindowManager的addView方法之前会设置decorView为inVisible,防止闪屏。当调用完addView方法之后,设置decorView为Visibile。
decor.setVisibility(View.INVISIBLE);
  • 在WindowManager是个接口,具体实现类是WindowManagerImpl。在WindowManagerImpl的addView方法中,将addView转发给了WindowManagerGlobal来实现。
  • 在WindowManagerGlobal的addView方法中,创建了ViewRootImpl,并且将DecorView传递给了ViewRootImpl中。然后调用ViewRootImpl的setView方法。
  • 在ViewRootImpl的setView方法中,会调用requestLayout方法,requestLayout方法会调用checkThread方法判断是否在主线程更新ui,然后调用scheduleTraversals方法。
  • 在scheduleTraversals注册了Choreographer,来监听Vsync垂直同步信号,当垂直同步信号过来的时候,会执行mTraversalRunnable。
  • 在TraversalRunnable的run方法中调用了doTraversal方法。在doTraversal方法中调用了performTraversals。在performTraversals方法,依次调用performMeasure、performLayout和performDraw方法,开始view的测量布局绘制三大流程。进而到了ViewGroup中。ViewGroup会遍历它的子view,进而调用子View的measure、layout和draw方法。

view会经历measure、layout和draw三个阶段。

我们从ViewGroup的addView方法开始说起。在ViewGroup的addView方法中会调用View的requestLayout方法。之后会ViewParent的requestLayout方法。ViewParent的具体实现类是ViewRootImpl,requestLayout接下来调用scheduleTraversals->mTraversalRunnable的doTraversal()->performTraversals()。在performTraversals会依次调用performMeasure、performLayout、performDraw三个方法。

简单说:View的绘制是主要是三个方法:measure(测量),layout(布局),draw(绘制)。

performMeasure : 在Measure过程中,ViewGroup一般是先遍历测量子View的大小,然后再确定自身的大小。测量完毕之后调用setMeasuredDimension()设定View的宽高信息,measure完成以后,可以通过getMeasuredWidth和getMeasureHeight方法来获取到View测量后的宽高。

performLayout : View的布局主要通过是上下左右四个点来确定的。ViewGroup先在layout()中确定自己的布局,然后在onLayout()方法中再调用子View的layout()方法,让子View布局。Layout完成以后,可以通过getTop/Bottom/Left/Right拿到View的四个顶点位置,并可以通过getWidth和getHeight方法来拿到View的最终宽高。

performDraw

onDraw:绘制本身View内容。在View和ViewGroup中是空实现, 自定义View时需要进行复写该方法。

dispatchDraw()用于子控件的绘制,ViewGroup已经实现了该方法。view不需要实现。

onDraw()先于dispatchDraw()执行。

三、onMeasure

(1)onMeasure方法简介

1、onMeasure是告诉当前控件的父控件,当前控件要占用的大小,以便让它的父控件给它预留空间。所以首先,我们需要先计算出整个ViewGroup所要占据的大小,然后通过setMeasuredDimension()函数通知ViewGroup的父控件以预留位置。ViewGroup需要遍历所有子view,获取子view的宽高和margin,最终来计算ViewGroup最终的宽高。获取子view的宽高,要利用measureChildren(widthMeasureSpec, heightMeasureSpec),让每个子控件先测量自己,只有测量过自己之后,再调用子控件的getMeasuredWidth()才会有值。Measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到ViewGroup测量后的宽/高。但Measure过程后得到的宽高可能不准确,建议在layout过程中的onLayout去获取最终的宽高。

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {/*** 步骤一:获取ViewGroup的测量模式和测量大小*/val widthMode = MeasureSpec.getMode(widthMeasureSpec)val measureWidth = MeasureSpec.getSize(widthMeasureSpec)val heightMode = MeasureSpec.getMode(heightMeasureSpec)val measureHeight = MeasureSpec.getSize(heightMeasureSpec)//遍历所有子viewfor (i in 0 until childCount) {//1、获取子viewval childView = getChildAt(i)//2、让子视图进行自我测量measureChild(childView, widthMeasureSpec, heightMeasureSpec)//3、获取子view的宽高var childWidth = childView.measuredWidthvar childHeight = childView.measuredHeight//4、获取子view的margin,计算当前子view的宽高val childParams = childView.layoutParams//这里我们要重写generateLayoutParams方法。if (childParams is MarginLayoutParams) {childWidth += childParams.leftMargin + childParams.rightMarginchildHeight += childParams.topMargin + childParams.bottomMargin}}//根据子view的宽高计算当前ViewGroup的宽高//最后设置ViewGroup的宽高/*** 7、最后通过setMeasuredDimension把ViewGroup的宽高设置到系统中* 当测量模式是MeasureSpec.EXACTILY的时候,我们就不需要计算viewgroup的大小了。*/val ViewGroupWidth = if (widthMode == MeasureSpec.EXACTLY) {measureWidth} else {//计算得到的宽width}val ViewGroupHeight = if (heightMode == MeasureSpec.EXACTLY) {measureHeight} else {//计算得到的高height}setMeasuredDimension(ViewGroupWidth, ViewGroupHeight)
}

(2)MeasureSpec简介

onMeasure会传进来两个参数,这两个参数的意义是父类传递过来给当前view大小的一个建议值。即建议当前view的宽高设置为(widthMeasureSpec,heightMeasureSpec)。MeasureSpec是int类型的数字,转化成二进制是32位的。MeasureSpec=mode(测量模式)+size。前两位表示测量模式,后30位表示大小。

测量模式有3种:

EXACTILY:确切的大小,比如match_parent或具体的数值。

AT_MOST:子元素最大的size,比如wrap_content。

UNSPECIFIED:父容器不对View有任何限制,一般用于系统内部,表示一种测量状态。

当测量模式是MeasureSpec.EXACTILY的时候,我们就不需要计算viewgroup的大小了。如果ViewGroup的大小为wrap_content,我们需要进行size计算。最后调用setMeasuredDimension设置大小。

(3)getMeasuredWidth()与getWidth()区别

getMeasuredWidth在measure过程结束后就可以获取到了,getMeasuredWidth方法中的值是通过setMeasuredDimension来设置的。

getWidth要在layout过程结束才能获取到。getWidth()方法中的值则是通过layout(left,top,right,bottom)方法设置的。

(4)获取子控件margin方法

获取子控件margin方法可以分为三个步骤

(1)重写generateLayoutParams方法。

override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {return MarginLayoutParams(context,attrs)
}override fun generateLayoutParams(p: LayoutParams?): LayoutParams {return MarginLayoutParams(p)
}override fun generateDefaultLayoutParams(): LayoutParams {return MarginLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT)
}

Why要重写getLayoutParams方法呢:

当我们getLayoutParams方法时,默认会获取到new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

这样我们是获取不到margin值的,如果需要获取margin相关参数,就只能重写generateLayoutParams方法。我们看一下MarginLayoutParams的构造方法。

public MarginLayoutParams(Context c, AttributeSet attrs) {super();TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);int margin = a.getDimensionPixelSize(com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);//省略代码 可以看出和自定义view属性是一样的,获取到view的margin属性对应的数据。}

这样,就可以在onMeasure和onLayout方法中,获取到子view的margin了​。

MarginLayoutParams lp =(MarginLayoutParams) child.getLayoutParams();
int childHeight = child.getMeasuredHeight()+lp.topMargin+lp.bottomMargin;

三、onLayout

onLayout是对所有子控件进行布局。Layout过程决定了子View的四个顶点坐标和实际View的宽高,完成以后可以通过getTop、getBottom、getLeft、getRight来拿到View的四个顶点的位置,并可以通过getWidth和getHeight方法来获取到View的最终宽高。

布局主要代码:

/*** 布局所有子控件*/
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//1、遍历所以子view,给每个子view布局
for (i in 0 until childCount) {//2、获取当前子viewval childView = getChildAt(i)
int childHeight = childView.getMeasuredHeight();
int childWidth = childView.getMeasuredWidth();
child.layout(left, top, left+childWidth, top + childHeight);
}
}

四、View的位置参数

在Android中有两种坐标系,分别为Android坐标系和View坐标系。

(1)Android坐标系:将屏幕左上角的顶点作为Android坐标系的原点,这个原点向右是X轴正方向,向下是Y轴正方向。

在触控事件中,通过getRawX()和getRawY()方法获得的坐标就是Android坐标系的坐标;

(2)View坐标系:是View到其父控件的坐标;

width=getRight-getLeft;【获取View自身的宽】
height=getBottom-getTop;【获取View自身的高】
通过getTop、getLeft、getBottom、getRight方法可以获得View到其父控件的距离;

五、View的滑动

(1)View的滑动主要有三种实现方式

1、通过View本身提供的scrollTo/ScrollBy方法,使用scrollTo/ScrollBy来实现View的滑动,只能将View的内容进行移动,并不能将View本身进行移动。

2、通过动画给View施加平移效果来实现滑动,可以实现复杂的动画效果。

3、通过改变View的LayoutParams使得View重新布局从而实现滑动。操作稍微复杂,适用于有交互的View。

比如我们想把一个Button向右平移100px,只需要将这个Button的LayoutParams的marginLeft参数增加100px即可。

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) mButton.getLayoutParams();
layoutParams.width += 100;
layoutParams.leftMargin += 100;
mButton.requestLayout();
//或者也可以使用  mButton.setLayoutParams(layoutParams);

应用场景:

如果我们实现一个View跟随手滑动的效果:

实现思路:重写onTouchEvent方法,并处理ACTION_MOVE事件。

可以采用改变布局方式的方式实现,也可以使用动画实现(但是因为动画的性质,3.0以下版本无法实现新位置的点击事件)。

六、自定义view滑动冲突

1.外部拦截法

点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截。如果不需要就不拦截。这样就可以解决滑动冲突的问题。

ACTION_DOWN:父容器必须返回false,如果ACTION_DOWN返回true,那么后续的MOVE和UP事件都会直接交由父容器处理。就没办法传递给子View了。

ACTION_MOVE:根据需要决定是否拦截事件。

ACTION_UP:这里也必须返回false。如果返回true,就会导致子元素无法获取UP事件。

怎样判断拦截事件呢?可以根据水平和竖直的滑动距离来判断。

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {boolean intercept = false;int x = (int) ev.getX();//获取点击事件距离控件左边的距离int y = (int) ev.getY();switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:intercept = false;break;case MotionEvent.ACTION_MOVE:if (父容器需要拦截事件) {intercept = true;} else {intercept = false;}break;case MotionEvent.ACTION_UP:intercept = false;break;default:break;}mLastXIntercept=x;mLastYIntercept=y;return intercept;}

七、Paint常用方法

paint.setAntiAlias(true); //抗锯齿功能,如果使用会使绘图速度变慢
paint.setColor(Color.RED); //设置画笔颜色
paint.setStyle(Style.FILL); //设置填充样式。Paint.Style.FILL:填充内部 Paint.Style.FILL_AND_STROKE :填充内部和描边 Paint.Style.STROKE :仅描边
paint.setStrokeWidth(30); //设置画笔宽度
paint.setShadowLayer(10, 15, 15, Color.GREEN); //设置阴影
paint.setTextAlign(Align.CENTER); //设置文字对齐方式,取值:align.CENTER、align.LEFT或align.RIGHT
paint.setTextSize(12); //设置文字大小
paint.setFakeBoldText(true); //设置是否为粗体文字
paint.setUnderlineText(true); //设置下划线
paint.setTextSkewX((float) -0.25); //设置字体水平倾斜度,普通斜体字是-0.25
reset //重置画笔
setAlpha //设置画笔透明度
setStrokeCap //设置线冒模式 (ROUND圆形、SQUARE方形、BUTT无线帽、)
setStrokeJoin //设置线段连接处样式,(MITER锐角、 ROUND圆、 BEVEL直线)
setPathEffect //设置路径样式
CornerPathEffect圆形拐角效果
DashPathEffect 虚线效果
setSaturation //设置饱和度
setColorFilter //ColorMatrixColorFilter:色彩矩阵颜色过滤
LightingColorFilter:光照颜色过滤器,可以简单的完成色彩过滤和色彩增强功能

八、Canvas

1、常用的基本方法

drawPath、drawLine、drawLines(不是多个点连成线,而是绘制多条直线)、drawPoint、drawPoints、drawRect、drawRoundRect、drawCircle、drawOval(椭圆)、drawArc(弧包括是否是闭合的弧)、void drawText (String text, float x, float y, Paint paint)

void drawPosText (String text, float[] pos, Paint paint)指定每个文字的位置

drawTextOnPath:沿路径绘制文字

2、 drawPath绘制路径

(1)直线路径

moveTo、 lineTo、close(首尾相连)

(2)矩形路径

  • void addRect (float left, float top, float right, float bottom, Path.Direction dir):添加矩形路径
  • Path.Direction.CCW:指创建逆时针方向的矩形路径;
  • Path.Direction.CW:指创建顺时针方向的矩形路径。方向的作用:文字是可以依据路径方向排版的,那文字的行走方向就是依据路径的生成方向;

    (3)画弧线

弧是椭圆的一部分,而椭圆是根据矩形来生成的,所以弧当然也是根据矩形来生成的;

void drawArc (RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

参数:
       RectF oval:生成椭圆的矩形
float startAngle:弧开始的角度,以X轴正方向为0度,顺时针方向是变大为正.
float sweepAngle:弧持续的角度
boolean useCenter:是否有弧的两边,True,表示有两边,False,只有一条弧

  • (4)其他路径

  • 圆角矩形:addRoundRect(可以设置每个圆角大小)

  • 圆形路径:addCircle
  • 椭圆路径:addOval
  • 弧形路径:addArc
  • 贝塞尔曲线:quadTo

    九、贝塞尔曲线

贝赛尔曲线的控制点我们可以借助PhtotoShop的钢笔工具来找;

(1)//二阶贝赛尔

publicvoidquadTo(floatx1,floaty1,floatx2,floaty2)

publicvoidrQuadTo(floatdx1,floatdy1,floatdx2,floatdy2)

quadTo:(x1,y1)是贝塞尔曲线的控制点,(x2,y2)是贝塞尔曲线的终点。整条线的起始点是通过Path.moveTo(x,y)来指定的,而如果我们连续调用quadTo(),前一个quadTo()的终点,就是下一个quadTo()函数的起点;如果初始没有调用Path.moveTo(x,y)来指定起始点,则默认以控件左上角(0,0)为起始点;

rQuadTo:是相对位置路径。

(2)实现手指轨迹

原理:在onTouchEvent事件中,获取down事件的(x,y)坐标,记为初始值。在move事件不断获取(x,y)坐标,然后通过贝塞尔曲线,不断的连接上次和本次的两个坐标形成曲线,不断调用invalidate方法。会回调onDraw方法,画出当前的path。canvas.drawPath()

/*** author : Naruto* desc   :* 1、要实现手指轨迹其实是非常简单的,我们只需要在自定义中拦截OnTouchEvent,然后根据手指的移动轨迹来绘制Path即可。* 2、使用Path.lineTo()就能实现把各个点连接起来* 3、line是直线,不够平滑。所以我们可以使用贝塞尔曲线来实现平滑过渡。* version:*/
class FingerTrackView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {private var mPath = Path()private var mPreX = 0fprivate var mPreY = 0foverride fun onTouchEvent(event: MotionEvent?): Boolean {when (event?.action) {/*** return true表示当前控件已经消费了down动作,之后的ACTION_MOVE、ACTION_UP动作也会继续传递到当前控件中;* 如果return false,那么后序的ACTION_MOVE、ACTION_UP动作就不会再传到这个控件来了。*/MotionEvent.ACTION_DOWN -> {mPreX = event.xmPreY = event.ymPath.moveTo(mPreX, mPreY)return true}MotionEvent.ACTION_MOVE -> {val mEndx = (mPreX + event.x) / 2val mEndY = (mPreY + event.y) / 2mPath.quadTo(mPreX, mPreY, mEndx, mEndY)mPreX = mEndxmPreY = mEndYinvalidate()}}return super.onTouchEvent(event)}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)val paint = Paint()paint.setColor(Color.GREEN)paint.isAntiAlias = truepaint.strokeWidth = 2fpaint.style = Paint.Style.STROKEcanvas?.drawPath(mPath, paint)}fun reset() {mPath.reset()/*** Invalidate()一定要在UI线程执行,如果不是在UI线程就会报错。* postInvalidate(),可以在任何线程中执行.利用handler来通知主线程刷新。*/invalidate()}
}

(3)实现水波纹效果

1、使用贝塞尔曲线画出一条波浪线;

2、for循环画出多条水波纹撑满整个屏幕的宽度;(注意:波浪线的初始点要比屏幕多一个波浪线的波长出来,不然动画就会暂停。)

3、画出闭合的曲线。

4、使用动画不断的改变初始点的坐标,如果坐标的改变刚好符合一条水波纹的宽度,就会实现水波纹波动的效果。

/*** author : Naruto* desc   : 水波纹* version:*/
class WaveView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {private var mPaint = Paint()private var mPath = Path()private var mItemWaveLength = 1000private var mWaveHeight = 100fprivate var dx = 0fprivate var dy = 0finit {mPaint.color = Color.GREENmPaint.style = Paint.Style.FILL_AND_STROKEmPaint.strokeWidth = 4f}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)mPath.reset()val halfItemWaveLength = mItemWaveLength / 2mPath.moveTo(-mItemWaveLength.toFloat() + dx, 300f + dy)for (i in -mItemWaveLength..(width + mItemWaveLength) step mItemWaveLength) {mPath.rQuadTo(halfItemWaveLength / 2.toFloat(), -mWaveHeight, halfItemWaveLength.toFloat(), 0f)mPath.rQuadTo(halfItemWaveLength / 2.toFloat(), mWaveHeight, halfItemWaveLength.toFloat(), 0f)}mPath.lineTo(width.toFloat(), height.toFloat())mPath.lineTo(0f, height.toFloat())mPath.close()canvas?.drawPath(mPath, mPaint)}fun startAnim() {val animatorX = ValueAnimator.ofFloat(0f, mItemWaveLength.toFloat())//无限循环animatorX.repeatCount = ValueAnimator.INFINITEanimatorX.duration = 2000//设置LinerInterpolator保持匀速动画,不会有暂停的效果animatorX.interpolator = LinearInterpolator()animatorX.addUpdateListener {dx = it.animatedValue as FloatpostInvalidate()}animatorX.start()}
}

十、补充

一、Bitmap创建方式:

常用的两种:

//方法一:新建一个空白bitmap
Bitmap bmp = Bitmap.createBitmap(width ,height Bitmap.Config.ARGB_8888);
//方法二:从图片中加载
Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.wave_bg,null);

二、用bitmap创建canvas

如果我们用bitmap构造了一个canvas,那这个canvas上绘制的图像也都会保存在这个bitmap上,而不是画在View上,如果想画在View上就必须使用OnDraw(Canvas canvas)函数中传进来的canvas画一遍bitmap才能画到view上。

代码:

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);Bitmap mBmp = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);Canvas mBmpCanvas = new Canvas(mBmp);mPaint.setTextSize(100);mBmpCanvas.drawText("bitmap 自定义text", 0, 100, mPaint);//要用onDraw传进来的canvas才能画到当前view上canvas.drawBitmap(mBmp, 0, 0, mPaint);
}

三、获取点击事件发生的x和y坐标

MotoinEvent通过MotionEvent对象,我们可以得到点击事件发生的x和y坐标。

getRawX获取相对屏幕左上角的x坐标,getX相对于当前View左上角的坐标。

通过MotionEvent对象,我们可以获取手指接触屏幕的操作。ACTION_DOWN、ACTION_MOVE、ACTION_UP。

四、TouchSlop

TouchSlop是系统认为的滑动最小距离;通俗的说,如果滑动距离小于TouchSlop,系统就不认为这是滑动;

TouchSlop和设备有关,获取代码:

int mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

五、VelocityTracker

VelocityTracker:速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。

手指逆着坐标系的正方向滑动,所产生的速度为负值,顺着正反向滑动,所产生的速度为正值。

4.1:在View的onTouchEvent方法中追踪当前事件的速度:
VelocityTracker   mVelocityTracker = VelocityTracker.obtain();mVelocityTracker.addMovement(event);
4.2:当我们想获取当前速度是,采用如下方法:mVelocityTracker.computeCurrentVelocity(1000);//设置时间间隔,单位msint velocityY = (int) mVelocityTracker.getYVelocity();int velocityX= (int) mVelocityTracker.getXVelocity();
4.3:当我们不需要的时候,需要clear方法重置并回收内存;
mVelocityTracker.clear();
mVelocityTracker.recycle();

六、GestureDetector:

GestureDetector:手势监测,用于辅助检测用户的单击、滑动、长按、双击等行为;

1.创建一个GestureDetector对象,可以实现OnGestureListener或者setOnDoubleTapListener(监听双击行为);

GestureDetector   mGestureDetector = new GestureDetector(new GestureDetector.OnGestureListener() {
});
mGestureDetector.setIsLongpressEnabled(false);//解决长按屏幕后无法拖动的现象;

2.接着在View的onTouchEvent方法中,添加如下实现:

boolean consum = mGestureDetector.onTouchEvent(event);
return  consum;

做完上面两步,我们可以有选择地实现OnGestureListener和OnDoubleTapListener中的方法。

onSingleTapUp(单击)、onFling(快速滑动)、onScroll(拖动)、onLongPress(长按)、onDoubleTap(双击)。

建议:如果只是监听滑动相关的,建议在onTouchEvent中实现。如果要监听双击这种行为,那么就使用OnGestureListener。

七、Scroller:弹性滑动对象,用于实现View的弹性滑动;

当使用View的scrollTo/scrollBy方法进行滑动的时候,这个过程是瞬间完成的。这个没有过渡效果的滑动体验很差。

Scroller就可以实现过渡滑动的效果。Scroller本身是不能实现View的滑动的,它需要与View的computeScroll方法结合才能实现弹性滑动的效果。

如何使用Scroller呢?它的典型代码是固定的。

 Scroller scroller = new Scroller(getContext());private void smoothScrollTo(int destX, int dextY) {int scrollX = getScrollX();int delta = destX - scrollX;//1000ms滑向dextXscroller.startScroll(scrollX,0,delta,0,1000);invalidate();}@Overridepublic void computeScroll() {if (mScroller.computeScrollOffset()) {scrollTo(mScroller.getCurrX(), mScroller.getCurrY());postInvalidate();}}

原理:smoothScrollTo方法中调用invalidate方法,invalidate会导致View重绘,调用draw方法。draw方法中调用computeScroll方法,computeScroll是个空实现,需要我们重写。computeScroll会去向scroller获取当前的scrollx和scrolly,调用scrollTo来实现滑动。接着调用postinvalidate进行第二次重绘。如此反复,直到整个滑动过程结束。

八、在Activity中获取宽高

有以下三种方法:

view.post、ViewTreeObserver、onWindowFocusChanged

4.1:view.post

Activity获取View的宽高,在onCreate、onResume等方法中获取到的都是0,因为View的测量过程并不是和Activity的生命周期同步执行的。

view.post投递一个Runnable,等looper调用此Runnable的时候,view已经初始化好了。

   view.post(new Runnable() {@Overridepublic void run() {int width = view.getMeasuredWidth();int height = view.getMeasuredHeight(); }});

4.2:ViewTreeObserver使addOnGlobalLayoutListene

ViewTreeObserver使addOnGlobalLayoutListene

接口, 当view树的状态发生改变或者View树内部的view的可见性发生改变时,onGlobalLayout都会被调用, 需要注意的是,onGlobalLayout方法可能被调用多次, 代码如下:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {view.getViewTreeObserver().removeOnGlobalLayoutListener(this);int width = view.getMeasuredWidth();int height = view.getMeasuredHeight();}});

4.3:onWindowFocusChanged

onWindowFocusChanged这个方法的含义是View已经初始化完毕了, 宽高已经准备好了, 需要注意的就是这个方法可能会调用多次, 在Activity onResume和onPause的时候都会调用, 也会有多次调用的情况。

 @Overridepublic void onWindowFocusChanged(boolean hasWindowFocus) {super.onWindowFocusChanged(hasWindowFocus);if(hasWindowFocus){int width = view.getMeasuredWidth();int height = view.getMeasuredHeight();}

九、onDraw、dispatchDraw区别

onDraw()的意思是绘制视图自身,dispatchDraw()是绘制子视图。

无论是View还是ViewGroup对它们俩的调用顺序都是onDraw()->dispatchDraw()。
但在ViewGroup中,当它有背景的时候就会调用onDraw()方法,否则就会跳过onDraw()直接调用dispatchDraw();所以如果要在ViewGroup中绘图时,往往是重写dispatchDraw()方法。
在View中,onDraw()和dispatchDraw()都会被调用的,所以我们无论把绘图代码放在onDraw()或者dispatchDraw()中都是可以得到效果的,但是由于dispatchDraw()的含义是绘制子控件,所以原则来上讲,在绘制View控件时,我们是重新onDraw()函数
所以结论来了:
在绘制View控件时,需要重写onDraw()函数,在绘制ViewGroup时,需要重写dispatchDraw()函数。

十、getLocationOnScreen

public void getLocationOnScreen(int[] location)

该函数的功能是获取当前控件所在屏幕的位置,传进去一个location的数组,在执行以后会把left,top值赋给location[0]和location[1]

十一、Save、restore、saveLayer区别

Save():每次调用Save()函数,都会把当前的画布的状态进行保存,然后放入特定的栈中;

restore():每当调用Restore()函数,就会把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布,并在这个画布上做画。

saveLayer(图层)会创建一个全新透明的bitmap,大小与指定保存的区域一致,其后的绘图操作都放在这个bitmap上进行。在绘制结束后,会直接盖在上一层的Bitmap上显示。

十二、硬件加速

GPU英文全称Graphic Processing Unit,中文翻译为“图形处理器”。与CPU不同,GPU是专门为处理图形任务而产生的芯片。在GPU加速时,实际是使用OpenGL的函数来完成绘制的。使用GPU加速,提高了Android系统显示和刷新的速度;

硬件加速缺点:

1、 兼容性问题:由于是将绘制函数转换成OpenGL命令来绘制,定然会存在OpenGL并不能完全支持原始绘制函数的问题,所以这就会造成在打开GPU加速时,效果会失效的问题。

2、内存消耗问题:由于需要OpenGL的指令,所以需要把系统中的OpenGL相关的包加载到内存中来,所以单纯OpenGL API调用就会占用8MB,而实际上会占用更多内存;

3、电量消耗问题:多使用了一个部件,当然会更耗电……

API 11——API 13虽然是支持硬件加速的,但是默认是关闭的。

API 14之后,硬件加速是默认开启的。

硬件加速分全局(Application)、Activity、Window、View 四个层级

其他、色彩矩阵

色彩矩阵可以实现图片滤镜等效果。本文只是简单介绍。

a11、a22、a33、a44在色彩矩阵对角线上的分别代表R、G、B、A的几个值

假如:原始矩阵

ColorMatrix colorMatrix = new ColorMatrix(new float[]{1.0f, 0, 0, 0, 0,0, 1.0f, 0, 0, 0,0, 0, 1.0f, 0, 0,0, 0, 0, 1.0f, 0,
});

1、矩阵加法

最后一列,对应的颜色上 面进行加减。

注意:第二行第五列。A25

比如:在绿色值上添加增量50,即增大绿色的饱和度。就是给a25赋值为50.

ColorMatrix colorMatrix = new ColorMatrix(new float[]{1.0f, 0, 0, 0, 0,0, 1.0f, 0, 0, 50,0, 0, 1.0f, 0, 0,0, 0, 0, 1.0f, 0,
});

2、色彩的缩放运算

在当前数值上,进行乘法运算。比如方法1.2倍。

ColorMatrix colorMatrix = new ColorMatrix(new float[]{1.2f, 0, 0, 0, 0,0, 1.2f, 0, 0, 50,0, 0, 1.2f, 0, 0,0, 0, 0, 1.2f, 0,
});

View基础知识总结相关推荐

  1. view基础知识介绍(一)

    view基础知识介绍 view是一种界面层的控件的一种抽象 分为view和viewGroup viewGroup继承自view 也就是说view本身可以是单个控件 也可以是一个控件组 例如:一个vie ...

  2. View的事件体系(上)(View基础知识,滑动,弹性滑动)

    View不是四大组件之一,但重要性堪比四大组件,本篇博文主要讲解View的事件体系,包括View的基础知识,滑动,弹性滑动,事件分发机制,滑动冲突的种类与解决方案. 一 View的基础知识 (1).V ...

  3. 第1章 ADAMS/View 基础知识

    1.1 概述 1.1.1 计算机辅助工程(CAE) 广义CAE包括计算机辅助设计(CAD),计算机辅助创新(CAI),计算机辅助分析(CAA),计算机辅助优化(CAO),计算机辅助制造(CAM).狭义 ...

  4. 网络测速全解析之一:自定义View基础知识(八)

    一.事件分发机制详解: 大佬名言:所有的源码都是为了适应具体的应用场景而写的,只要能够理解运用场景,理解源码也就十分简单了. 核心问题是:正确理解在实际场景中事件分发机制的作用. 常见事件 事件 简介 ...

  5. 安卓基础知识之View篇(四):View 事件滑动冲突解决方案

    安卓基础知识系列旨在简明扼要地提供面试或工作中常用的基础知识,让对安卓还不太熟悉的小伙伴更快地入门.同时自己在工作中,也没法完全记住所有的基础细节,写这样的系列文章,可以让自己形成一个更完备的知识体系 ...

  6. Android自定义view之基础知识

    Android自定义view之基础知识 虽然Android已经自带了很多实用的view和layout,加以调教能实现很美观的界面,但是有一些情况下,需要实现特殊的界面效果,比如我们比较熟悉的各种播放器 ...

  7. Android View(一)——View的基础知识

    目录 一.View的基础知识 1.什么是View 2.View的位置参数 3.MotionEvent 4. TouchSlop 5. VelocityTracker 6. GestureDetecto ...

  8. PHP内核介绍及扩展开发指南—基础知识

    一. 基础知识 本章简要介绍一些Zend引擎的内部机制,这些知识和Extensions密切相关,同时也可以帮助我们写出更加高效的PHP代码. 1.1 PHP变量的存储 1.1.1 zval结构 Zen ...

  9. 超详细的Java面试题总结(四 )之JavaWeb基础知识总结

    系列文章请查看: 超详细的Java面试题总结(一)之Java基础知识篇 超详细的Java面试题总结(二)之Java基础知识篇 超详细的Java面试题总结(三)之Java集合篇常见问题 超详细的Java ...

最新文章

  1. CakePHP中出现persistent is not writable等Warning的解决方法
  2. python能不能爬数据库_python如何爬数据库
  3. 2011年最新使用CSS3实现各种独特悬浮效果的教程
  4. 装了Ubuntu后将默认启动项修改为windows
  5. 电脑技巧:C盘爆满该如何清理,实用的清理方案,小白必备
  6. PHP函数库06:PHP统计字符串里单词出现次数
  7. 命令父窗口变颜色_【编程】第五期:Python Tkinter图形化教程03布局之父窗口、pack和LabelFrame...
  8. Linux学习笔记三:安装VMWare Tools共享文件夹
  9. 工程图样中粗实线的用途_电气工程图的一般特点、设计规范
  10. 鸿蒙系统30个G,鸿蒙系统升级,为何固定大小有5.9G,也有3点几G呢?
  11. 百度C语言面试题2017,百度C语言面试题
  12. 那些买了来客推商城V3多用户uni-app商城源码的客户体验怎么样?
  13. android 恢复出厂设置流程分析,android恢复出厂设置流程概括
  14. Excel拆分字符判断是否有汉字
  15. 头歌Python,7号的,作业,
  16. 全球手机芯片产业格局未定,LTE和中国是最大变数
  17. PTA 选择结构 7-1 能买手机吗?
  18. Markdown教程【从0到1这一篇就够了】
  19. Unix网络编程学习笔记之第11章 名字与地址转换
  20. 可能是最全面的MySQL8.0与MySQL5.7差异分析

热门文章

  1. API Testing 12 - API测试工具
  2. 单词学习-Unit1Text2-2(15年8月21日,第31天)
  3. java 数组定义方法_java中定义数组的方法有哪些
  4. Python函数day12
  5. 数据测量与相似性分析
  6. WhatsApp流量获取方案
  7. Linux下如何查看所使用的Eclipse版本号
  8. 行业案例 | 解谜 AR 数字孪生,数据价值“看”得见
  9. 热点数据的发现、处理、更新
  10. DOL HDR 先长后短曝光的原因猜测