自定义View系列(一)基本概念
2016-05-05 17:15
363 查看
一直对于自定义view没有进行深入研究过,最近抽空开始整理整理自定义view相关的知识,以便可以有个系统的深入的认识,也希望能够给初学者一点启示。
本篇主要介绍自定义view中一些相关API以及相关知识点。
常用API如下:
Canvas ,画布类。
常用API如下:
API还是挺多的,但也比较清晰。
1. 编写自定义属性
2. 在构造函数中获取自定义属性
3. 在
4. 重写
1. 编写自定义属性
2. 在构造函数中获取自定义属性
3. 在
4. 重写
5. 如有需要重写
invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。鉴于此,如果在子线程中想刷新界面,需要配合handler来调用invalidate的刷新,这样就比较繁琐。所以,在子线程中调用postInvalidate比较方便。postInvalidate里面会封装一个Message后利用Handler提交到MessageQueue中,由UI线程的Looper从MessageQueue中一一取出进行处理,处理完之后才会进行下一次处理。
invalidate和requestLayout的区别在于,invalidate只刷新draw相关的部分(dispatchDraw,draw,onDraw),requestLayout会刷新measure(measure,onMeasure),layout(layout,onLayout),draw(dispatchDraw,draw,onDraw)相关部分,不仅可以刷新view,还能要求父布局对其重新布局。
当一个view调用requestLayout的时候,会给当前的View设置一个FORCE_LAYOUT标记。由此向ViewParent请求布局。这样从这个view开始向上,最终到达ViewRootImpl。然后ViewRootImpl由上往下根据标记位重新布局。
当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent(父类的视图)重新调用他的onMeasure onLayout来重新设置自己位置。特别是当view的layoutparameter发生改变,并且它的值还没能应用到view上时,这时候适合调用这个方法。
设置监听
本篇主要介绍自定义view中一些相关API以及相关知识点。
初识Canvas和Paint
Paint,画笔类。常用API如下:
//设置画笔的颜色 mPaint.setColor(int color); //设置填充风格,Paint.Style.FILL(仅填充),Paint.Style.STROKE(仅描边),Paint.Style.FILL_AND_STROKE(同时描边和填充) mPaint.setStyle(); //设置描边宽度 mPaint.setStrokeWidth() //设置字体大小 mPaint.setTextSize(); //重置画笔 mPaint.reset(); //设置滤镜 mPaint.setColorFilter(); //设置混合模式 mPaint.setXfermode();
Canvas ,画布类。
常用API如下:
//绘制矩形 canvas.drawRect(); //绘制圆 canvas.drawCircle(); //给画布填充颜色 canvas.drawColor(); //绘制扇形,弧 canvas.drawArc(); //绘制椭圆 canvas.drawOval(); //绘制圆角矩形 canvas.drawRoundRect(); //绘制线条 canvas.drawLine(); //绘制bitmap canvas.drawBitmap(); //绘制点 canvas.drawPoint(); //绘制路径 canvas.drawPath(); //画文字 canvas.drawText(); //保存画布状态 canvas.save(); //旋转画布 canvas.rotate(); //平移画布 canvas.translate(); //裁切画布 canvas.clipRect() //恢复画布状态 canvas.restore();
API还是挺多的,但也比较清晰。
自定义View流程
流程一般如下,但由于实际需求不同,并不是每个步骤都需要重写。1. 编写自定义属性
2. 在构造函数中获取自定义属性
3. 在
onMeasure中测量宽高,如有需要务必考虑支持padding属性和wrap_content,margin不用管,它是由父布局控制的。
4. 重写
onDraw来进行绘制,以达到我们所需要的效果。
自定义ViewGroup流程
流程一般如下,但由于实际需求不同,并不是每个步骤都需要重写。1. 编写自定义属性
2. 在构造函数中获取自定义属性
3. 在
onMeasure中测量宽高,并测量子view宽高。
4. 重写
onLayout来对子View进行布局。
5. 如有需要重写
onDraw来进行添加效果,绝大多数并不需要。
invalidate,postInvalidate和requestLayout之间的区别
invalidate和postInvalidate的区别在于,invalidate只能在UI线程中调用,而postInvalidate可以在线程中调用。invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。鉴于此,如果在子线程中想刷新界面,需要配合handler来调用invalidate的刷新,这样就比较繁琐。所以,在子线程中调用postInvalidate比较方便。postInvalidate里面会封装一个Message后利用Handler提交到MessageQueue中,由UI线程的Looper从MessageQueue中一一取出进行处理,处理完之后才会进行下一次处理。
public void postInvalidate() { postInvalidateDelayed(0); } public void postInvalidateDelayed(long delayMilliseconds) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); } } public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds);//这里使用了Handler进行处理 }
invalidate和requestLayout的区别在于,invalidate只刷新draw相关的部分(dispatchDraw,draw,onDraw),requestLayout会刷新measure(measure,onMeasure),layout(layout,onLayout),draw(dispatchDraw,draw,onDraw)相关部分,不仅可以刷新view,还能要求父布局对其重新布局。
当一个view调用requestLayout的时候,会给当前的View设置一个FORCE_LAYOUT标记。由此向ViewParent请求布局。这样从这个view开始向上,最终到达ViewRootImpl。然后ViewRootImpl由上往下根据标记位重新布局。
当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent(父类的视图)重新调用他的onMeasure onLayout来重新设置自己位置。特别是当view的layoutparameter发生改变,并且它的值还没能应用到view上时,这时候适合调用这个方法。
ViewConfiguration
//根据设备dpi获取一个最小滑动距离,即大于这个值才判定为滑动 int touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); //获取达到长按所需要的时间,小于这个时间算单击 int longPressTimeout=ViewConfiguration.getLongPressTimeout(); //获取系统默认的双击间隔时间的最大时间,大于这个时间不算做双击,算两次单击 int doubleTapTimeout=ViewConfiguration.getDoubleTapTimeout(); //获取触摸的边缘最大距离,小于这个距离算作触摸边缘 int edgeSlop=ViewConfiguration.get(getContext()).getScaledEdgeSlop() //获取惯性滑动速度的最小值(1000ms) int minimumFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity(); //获取惯性滑动速度的最大值(1000ms) int maximumFlingVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
VelocityTracker (速度追踪器)
/** * 给事件添加速度追踪器 */ private void acquireVelocityTracker(MotionEvent event) { if(null == mVelocityTracker) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } /** * 释放速度追踪器 */ private void releaseVelocityTracker() { if(null != mVelocityTracker) { mVelocityTracker.clear(); mVelocityTracker.recycle(); mVelocityTracker = null; } } /** * 计算在x,y方向上滑过的速度 */ private int[] computeVelocityTracker() { int[] in=new int[2]; // 计算按照当前速度1000ms内,可以滑过的像素。 mVelocityTracker.computeCurrentVelocity(1000); // 获取在1000ms内X方向所滑动像素值 int xVelocity = (int) mVelocityTracker.getXVelocity(); // 获取在1000ms内Y方向所滑动像素值 int yVelocity = (int) mVelocityTracker.getYVelocity(); in[0]=xVelocity; in[1]=yVelocity; return in; }
GestureDetector
初始化监听手势GestureDetector mGestureDetector=new GestureDetector(MainActivity.this, new GestureDetector.OnGestureListener() { //滑动标准:MotionEvent.ACTION_MOVE的滑动距离大于设备的最小滑动距离就会判定为滑动,参照ViewConfiguration @Override public boolean onDown(MotionEvent e) { //触发条件为MotionEvent执行 MotionEvent.ACTION_DOWN,任何时候都会执行 Log.d(TAG,"--onDown"+e.getAction()); return false; } @Override public void onShowPress(MotionEvent e) { //触发条件为MotionEvent执行MotionEvent.ACTION_DOWN,但未达到滑动标准或MotionEvent.ACTION_UP Log.d(TAG,"--onShowPress"+e.getAction()); } @Override public boolean onSingleTapUp(MotionEvent e) { //触发条件为MotionEvent执行MotionEvent.ACTION_UP,,且按下的时间小于长按时间标准且未达到滑动标准,则执行这个,注意onSingleTapUp和onLongPress只执行一个 Log.d(TAG,"--onSingleTapUp"+e.getAction()); return false; } @Override public void onLongPress(MotionEvent e) { //触发条件为MotionEvent执行MotionEvent.ACTION_DOWN,且按下的时间达到了长按时间标准,参照ViewConfiguration,注意onSingleTapUp和onLongPress只执行一个 Log.d(TAG,"--onLongPress"+e.getAction()); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { //触发条件为MotionEvent执行 MotionEvent.ACTION_DOWN,且在达到长按标准前达到滑动标准,如果达到长按标准只执行onLongPress,如果达到滑动标准则不会在执行onLongPress //e1表示MotionEvent.ACTION_DOWN时的事件 //e2表示MotionEvent.ACTION_MOVE时的事件 //distanceX表示两次X轴滑动的距离差 //distanceY表示两次Y轴滑动的距离差 Log.d(TAG,"--onScroll"+e1.getAction()+"/"+e2.getAction()+" "+distanceX+"/"+distanceY); return false; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { //触发条件为MotionEvent执行 MotionEvent.ACTION_DOWN,且在达到长按时间标准前达到滑动标准后又达到了惯性滑动标准,参照ViewConfiguration //执行onFling之前一定会执行onScroll,因为只有达到滑动标准后才会进行计算速度 //e1表示MotionEvent.ACTION_DOWN时的事件 //e2表示MotionEvent.ACTION_UP时的事件 //velocityX表示X轴滑动的速度(1000ms) //velocityX表示Y轴滑动的速度(1000ms) Log.d(TAG,"--onFling"+e1.getAction()+"/"+e2.getAction()+" "+velocityX+"/"+velocityY); return false; } });
设置监听
@Override public boolean onTouchEvent(MotionEvent event) { return mGestureDetector.onTouchEvent(event); } view.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { return mGestureDetector.onTouchEvent(event); } });
相关文章推荐
- Android中的Audio播放:控制Audio输出通道切换
- java、orcale、javascript、el表达式的四舍五入大全
- runtime 获取设备上所有App的bundle id
- Android实现国际化
- Android ListView 中的CheckBox点击乱系
- String类的常见功能和使用
- Extjs combox获取显示值和ID值
- 断言(ASSERT)的用法
- ntfs共享权限有哪些
- 双系统下格式化系统分区
- mybatis 之 generator插件使用 集成到了eclipse 自动生成实体,example,mapper
- .ftl文件 是什么文件
- Centos 7 系统安装
- 判断整数序列是不是二元查找树的后序遍历结果
- PHP中操作MYSQL数据库常用函数
- Bootstrap学习速查表(四) 栅格系统
- Swift 网络请求数据与解析
- adb命令
- 使用yum命令报错File "/usr/bin/yum", line 30 except KeyboardInterrupt, e:
- 算法复习——风骚的快速排序