本文只总结知识点 欢迎补充,欢迎纠正。谢谢!

#预备知识


Android控件框架

####1. View树状图

  • Android的View树结构总是以一个ViewGroup开始,包含多个View或ViewGroup
  • View是所有控件的父类
  • ViewGroup是继承自View的容器类抽象类

####2. AndroidUI界面架构图

  • 每个Activity都包含一个Window对象,通常为PhoneWindow
  • PhoneWindow将一个DecorView作为整个窗口的根View,DecorView作为窗口顶层视图封装了一些窗口操作的方法
  • DecorView将内容显示在PhoneWindow上,并通过WindowManagerService来进行接收,并通过Activity对象来回调对应的onClickListener。显示时,将屏幕分成两个部分,TitleView和ContentView。Content是一个id为content的FrameLayout,activity_main.xml就在其中。

坐标体系

View的坐标由它的四个顶点决定,分别对应View的四个属性

获得四个顶点的方式

  • left,getLeft() 左上角的横坐标
  • top,getTop()左上角的纵坐标
  • right,getRight() 右下角的横坐标
  • bottom,getBottom() 右下角的纵坐标

View测量

View测量主要依赖MeasureSpec 测量模式有三种

  • EXACTLY 精确模式
  1. 明确指定数值: layout_width=200dp,layout_height=200dp
  1. layout_width=match_parent,layout_height=match_parent
  • AT_MOST 最大模式
  1. layout_width=warp_content,layout_height=warp_content
  1. 空间大小会随着内容变大而变大,最大为父布局剩余空间
  • UNSPECIFIED

父容器不对View限制大小,要多大给多大,这种情况一般用于系统内部,表示一种测量状态,不用过多关注

#一、自定义View ##分类

  • 继承View,重写onDraw方法
  • 继承已有View(比如TextView
  • 继承ViewGroup实现特殊的Layout
  • 继承已有的ViewGroup(比如LinearLayout

##一般步骤 ###1. 在res/values/ 下建立一个attrs.xml文件,声明我们的自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>  <attr name="titleText" format="string" />  <attr name="titleTextColor" format="color" />  <attr name="titleTextSize" format="dimension" />  <declare-styleable name="CustomTitleView">  <attr name="titleText" />  <attr name="titleTextColor" />  <attr name="titleTextSize" />  </declare-styleable>
复制代码

###2. 继承View(或其他)重写构造方法

    public CustomView(Context context)  {  this(context, null);  }  /** * 获得我自定义的样式属性 *  * @param context * @param attrs * @param defStyle */  public CustomView(Context context, AttributeSet attrs)  {  super(context, attrs, defStyle);  /** * 获得我们所定义的自定义样式属性 */  TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, defStyle, 0);  int n = a.getIndexCount();  for (int i = 0; i < n; i++)  {  int attr = a.getIndex(i);  switch (attr)  {  case R.styleable.CustomView_titleText:  mTitleText = a.getString(attr);  break;  case R.styleable.CustomView_titleTextColor:  // 默认颜色设置为黑色  mTitleTextColor = a.getColor(attr, Color.BLACK);  break;  case R.styleable.CustomView_titleTextSize:  // 默认设置为16sp,TypeValue也可以把sp转化为px  mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(  TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));  break;  }  }  a.recycle();  }
复制代码

几个点

  • 单参数构造是直接new的时候会调用
  • 从xml中申明,并通过findViewById实例化会调用第两个参数的构造方法
  • 通过TypedArray解析自定义属性,完成时候记得回收
  • 解析自定义属性时get的类型与定义时的format对应

###3. 测量onMeasure 确定View大小 以自定义View实现文字绘制为例:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int wSpecMode=MeasureSpec.getMode(widthMeasureSpec);int wSpecSize=MeasureSpec.getSize(widthMeasureSpec);int hSpecMode=MeasureSpec.getMode(heightMeasureSpec);int hSpecSize=MeasureSpec.getSize(heightMeasureSpec);int width = 0;int height = 0;int textWidth=(int)mPaint.measureText(mText)+getPaddingLeft()+getPaddingRight();int textHeight=(int)(-mPaint.ascent() + mPaint.descent())+getPaddingTop()+getPaddingBottom();if(wSpecMode==AT_MOST&&hSpecMode==AT_MOST){width=textWidth;height=textHeight;}else if(wSpecMode==AT_MOST){width=textWidth;height=hSpecSize;}else if(hSpecMode==AT_MOST){width=wSpecSize;height=textHeight;}width=Math.min(width,wSpecSize);height=Math.min(height,hSpecSize);setMeasuredDimension(width,height);}
复制代码

几个点

  • 当直接继承ViewViewGroup重写onMeasure时,注意Viewwidthheightwarp_content时,需要特殊处理,否则默认为父布局剩余空间。

    Why? ** View自身的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams(也就是xml中设置的layout_width=warp_content,或代码中获取ViewLayoutParams设置宽高)共同决定。**

    • View设置宽高为具体数值时,无论父容器的MeasureSpec是什么,ViewMeasureSpec都是EXACTLY,宽高为LayoutParams中的大小。
    • View设置宽高为match_parent时,①.父布局的MeasureSpecEXACTLY时,ViewMeasureSpec也为EXACTLY,大小为父容器剩余空间;②.父布局的MeasureSpecAT_MOST时,ViewMeasureSpec也为AT_MOST,大小不会超过父容器剩余空间;
    • View设置宽高为warp_content时,无论父容器的MeasureSpec是什么,ViewMeasureSpec都是AT_MOST,并且大小不能超过父容器剩余空间 注:依据Android开发艺术探索
  • 如需支持Padding需要在测量时计算
  • 继承ViewGroup,如需支持Margin需要在测量时计算
  • View的生命周期与Activity不是同步,所以在ActivityonResume及之前的生命周期方法中获取View的宽高是不靠谱的

获取方法

  1. 重写onWindowFocusChanged在这个方法中获取
  2. view.post(runnable)
  3. ViewTreeObserver 4.view.measure(int widthMeasureSpec,int heightMeasureSpec) 不建议,因为这个方法要区分LayoutParams,在这不具体阐述
  • 计算类TextView的自定义布局的高度时,需知FontMetrics这个类:

FontMetrics有五个float类型值:

  • leading 留给文字音标符号的距离

  • ascentbaseline线到最高的字母顶点到距离,负值

  • topbaseline线到字母最高点的距离加上ascent|top|=|ascent|+|leading|

  • descentbaseline线到字母最低点到距离

  • bottomtop类似,系统为一些极少数符号留下的空间。topbottom总会比ascentdescent大一点的就是这些少到忽略的特殊符号

###4. 布局onLayout 确定View位置

 @Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);}
复制代码

几个点

  • 容器类自定义View布局时需处理Margin

###5. 绘制onDraw 想要绘制一个view,需要什么?

  • 保存像素的Bitmap
  • 管理绘制请求的Canvas
  • 绘画的原始基本元素,例如矩形,线,文字,Bitmap
  • 拥有颜色和风格信息的画笔

综合来说就是:画笔Paint,画布Canvas,画什么:text,bitmap,path...

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);//文字的x轴坐标float stringWidth = mPaint.measureText(text);float x = (getWidth() - stringWidth) / 2;//文字的y轴坐标Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();float y = getHeight() / 2 + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2;canvas.drawText(text, x, y, mPaint);
}
复制代码

几个点

  • 绘制区域关注Rect,RectF
  • 文字是从baseline开始绘制
  • 考虑padding
  • 尽量不要在onDraw中构造对象
  • 绘制时需要用到的一些类
  • 绘制文字:FontMetrics(文字度量)
  • 绘制图像:ColorMatrix(图像色彩),PorterDuffXfermore(两个图像间的混合显示模式), Shader (着色器), Matrix(图形处理)
  • 绘制路径:Path(路径),PathEffect(路径效果), Bezier (贝塞尔曲线), PathMeasure (辅助计算Path的计算器)
  • 继承ViewGroup处理滑动、拖动辅助类:ViewDragHelper(可以实现各种不同的的滑动、拖动)

###注意几点

  • 尽量不要在View中使用Handler
  • View中如果有线程或者动画,需要及时停止,否则有可能造成内存泄漏,在onDetachedFromWindow中处理
  • 处理好焦点传递
  • 处理滑动及滑动冲突

#二、View滑动 ##1. 触摸、滑动相关

  • MotionEvent 触摸事件
    @Overridepublic boolean onTouchEvent(MotionEvent event) {return super.onTouchEvent(event);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return super.onInterceptTouchEvent(ev);}
复制代码

用于报告(鼠标、笔、手指,轨迹球)运动事件 ACTION_DOWN(按下),ACTION_UP(抬起),ACTION_MOVE(移动),ACTION_CANCEL(取消)

  • TouchSlop 最小距离
ViewContfiguration.get(getConetxt()).getScaledTouchSlop()
复制代码

TouchSlop是系统识别最小的滑动距离,是一个常量值。当手指在屏幕滑动距离小于这个值时,系统不会将动作视为滑动。这个常量值的具体大小和设备也有关,不同的屏幕分辨率,可能会不一样 利用这个临界值,可以将一些不想要的手指操作给过滤掉

  • VelocityTracker速度追踪
public class ScrollerActivity extends AppCompatActivity {private VelocityTracker velocityTracker;private final String TAG = "ScrollerActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_scroller);}@Overridepublic boolean onTouchEvent(MotionEvent event) {//获取VelocityTrackervelocityTracker = VelocityTracker.obtain();velocityTracker.addMovement(event);//计算滑动速度velocityTracker.computeCurrentVelocity(1000);//计算速度float xVelocity = velocityTracker.getXVelocity();float yVelocity = velocityTracker.getYVelocity();Log.e(TAG,"&&&-->x = "+xVelocity+"---> y = "+yVelocity);return super.onTouchEvent(event);}@Overrideprotected void onDestroy() {super.onDestroy();if (null != velocityTracker){velocityTracker.clear();//重置velocityTracker.recycle();//回收内存}}
}
复制代码

用于追踪手指在滑动过程中的速度,包括水平速度和竖直方向的速度 滑动速度值的正负取决于是否与坐标系方向一致 滑动速度是相对一定时间的

  • GestureDetector手势监控
public class ScrollerActivity extends AppCompatActivity {private Toast toast;private GestureDetector mGestureDetector;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_scroller);initGestureDetector();}/*** 初始化 GestureDetector*/private void initGestureDetector() {mGestureDetector = new GestureDetector(ScrollerActivity.this,onGestureListener );//解决屏幕长按后无法拖动mGestureDetector.setIsLongpressEnabled(false);}private GestureDetector.OnGestureListener onGestureListener = new GestureDetector.OnGestureListener() {@Overridepublic boolean onDown(MotionEvent e) {//手指轻触屏幕的一瞬间,由一个ACTION_DOWN触发showToast("轻触一下");return true;}@Overridepublic void onShowPress(MotionEvent e) {//手指轻触屏幕,尚未松开或拖动,由一个ACTION_DOWN触发showToast("轻触未松开");}@Overridepublic boolean onSingleTapUp(MotionEvent e) {//手指离开屏幕,伴随一个ACTION_UP触发,单击行为showToast("单击");return true;}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//手指按下屏幕并拖动// 由一个由一个ACTION_DOWN,多个ACTION_MOVE触发,是拖动行为showToast("拖动");return false;}@Overridepublic void onLongPress(MotionEvent e) {//长按showToast("长按");}@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {//按下屏幕,快速滑动后松开,由一个由一个ACTION_DOWN,多个ACTION_MOVE,一个ACTION_UP触发showToast("快速滑动");return false;}};@Overridepublic boolean onTouchEvent(MotionEvent event) {boolean consume = mGestureDetector.onTouchEvent(event);return consume;}/*** Toast*/private void showToast(String str) {if (null == toast) {toast = Toast.makeText(ScrollerActivity.this, str, Toast.LENGTH_LONG);} else {toast.setText(str);}toast.show();}
}
复制代码

用于辅助检测单击、滑动、长按、双击 GestureDetector.setOnDoubleTapListener(onDoubleTapListener)可以实现双击 在OnGestureListener内onDown(),onSingleTapUp(),onScroll(),onFling()方法都有一个boolean类型的返回值,这个值表示是否消费事件

  • Scroller弹性滑动对象
public class ScrollerView extends LinearLayout {private Scroller mScroller;public ScrollerView(Context context, AttributeSet attrs) {super(context, attrs);initScroller();}/*** 初始化Scroller*/private void initScroller() {mScroller = new Scroller(getContext());}@Overridepublic void computeScroll() {super.computeScroll();if (mScroller.computeScrollOffset()) {//判断Scroller是否执行完毕scrollTo(mScroller.getCurrX(), mScroller.getCurrY());postInvalidate();}}public void smoothScrollTo(int destX, int destY) {//计算相对于左上角的偏移量final int deltaX = getScrollX() - destX;final int deltaY = getScrollY() - destY;//在1000ms内滑向destX destYmScroller.startScroll(0, 0, deltaX, deltaY, 1000);invalidate();}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return true;}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:smoothScrollTo((int) event.getX(), (int) event.getY());break;case MotionEvent.ACTION_UP://恢复左上角mScroller.startScroll(getScrollX(), getScrollY(), -getScrollX(), -getScrollY(), 1000);invalidate();break;}return true;}
}
复制代码

用于实现View的弹性滑动。Scroller本身无法实现弹性滑动,需要配合ViewcomputeScroll()方法

  • ViewDragHelper ``ViewGroup中拖动、滑动view的辅助类
public class DragView extends LinearLayout {private ViewDragHelper mViewDragHelper;public DragView(Context context, AttributeSet attrs) {super(context, attrs);initDragHelper();}private void initDragHelper() {mViewDragHelper = ViewDragHelper.create(DragView.this, 1.0f, mDragCallback);}/***  ViewDragHelper回调接口*/private ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() {@Overridepublic boolean tryCaptureView(View child, int pointerId) {//可以用来指定哪一个childView可以拖动return true;}@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {// 水平拖动return left;}@Overridepublic int clampViewPositionVertical(View child, int top, int dy) {//竖直拖动return top;}};@Overridepublic boolean onInterceptHoverEvent(MotionEvent event) {//拦截事件return mViewDragHelper.shouldInterceptTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {//消费事件//将触摸事件传递给`ViewDragHelper`,必不可少mViewDragHelper.processTouchEvent(event);return true;}
}
复制代码

##2. 滑动冲突 ###常见的滑动冲突场景

  • 外部滑动方向与内部滑动方向不一致
  • 外部滑动方向与内部滑动方向一致
  • 上面两种情况嵌套

###解决办法

  • 内部拦截
  • 外部拦截

#####1. 内部拦截

内部拦截法指的是父容器不拦截任何事件,所有的事件都传递给childView,根据需要,childView来选择是否消费,需要配合requestDisallowInterceptTouchEvent()方法。重写childViewdispatchTouchEvent()方法 在ACTION_DOWN中,使用parent.requestDisallowInterceptTouchEvent(true),让父容器不拦截ACTION_DOWN事件,ACTION_DOWN不受FLAG_DISALLOW_INTERCEPT标记位控制

伪代码

public boolean dispatchTouchEvent(MotionEvent event){int x = (int) event.getX();int y = (int) event.getY();switch(event.getAction()){case MotionEvent.ACTION_DOWN:parent.requestDisallowInterceptTouchEvent(true);break;case MotionEvent.ACTION_MOVE:int deltaX = x - mLastX;int deltaY = y - mLastY;if(父容器需要此类点击事件){parent.requestDisallowInterceptTopuchEvent(false);}break;case MotionEvent.ACTION_UP:break;break;}mLastX = x ;mLastY = y ;return super.dispatchTouchEvent(event);
}
复制代码

#####2. 外部拦截

点击事件都会先经过父容器的拦截处理,如果父容器需要处理此事就拦截,否则就不进行拦截。重写父容器的onInterceptTouchEvent()方法

  • 首先,在ACTION_DOWN中,父容器必须返回false,不拦截ACTION_DOWN事件。因为一旦拦截了ACTION_DOWN后续的ACTION_MOVEACTION_UP都会又父容器来处理,这样事件就无法传递给childView
  • 其次,在ACTION_MOVE中,可以根据需要来进行拦截,需要就返回true,否则就false
  • 最后,在ACTION_UP中,返回false(如果父容器在ACTION_UP中,返回了truechildView就不会再收到ACTION_UP事件,childViewonClick事件就不会触发。父容器比较特殊,一旦开始拦截某个事件,之后的序列事件都是交给父容器来处理,包括ACTION_UP,即使在ACTION_UP中返回falseACTION_UP还是由父容器处理)

伪代码

public boolean onInterceptTouchEvent(MotionEvent event){boolean intercepted = false;int x = (int) event.getX();int y = (int) event.getY();switch(event.getAction()){case MotionEvent.ACTION_DOWN:intercepted = false;break;case MotionEvent.Move:if(父容器需要当前点击事件){intercepted = true;}else{intercepted = false; }break;case MotionEvent.ACTION_UP:intercepted = false;break;}mLastXIntercept = x;mLastYIntercept = y;return intercepted;
}复制代码

#三、事件分发 ##1. 主要方法 先来看一张图

事件分发

    @Overridepublic boolean dispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);}
复制代码

返回结果表示是否拦截当前事件。返回true,拦截;false,不拦截 事件分发的第一步,当事件传递到当前View一定会调用。返回结果受此ViewonTouchEvent()方法和下级childViewdispachTouchEvent影响。虽然是事件分发第一步,但绝多数情况不推荐直接修改这个方法

事件拦截

    @Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return super.onInterceptTouchEvent(ev);}
复制代码

返回结果用来判断是否拦截某个事件。这个方法只存在于ViewGroup中 如果当前view拦截了某个事件,在同一个事件的序列中,此方法便不会被再次调用

事件消费

    @Overridepublic boolean onTouchEvent(MotionEvent event) {return super.onTouchEvent(event);}
复制代码

返回结果表示是否消费了事件。true,消费了,不用在审核了;false,不消费,给父容器处理

##2. 主要流程 首先来看一张图

  • 如果事件不被中断的话,整个流程呈U型
  • 传递顺序 Activity -> Window -> ViewGroup -> View
  • 消费顺序 Activity <- Window <- ViewGroup <- View
  • View设置的onTouchListener()优先级高于onTouchEvent()onClickListener()优先级比onToucnEvent()

Android自定义View,滑动,事件传递小结相关推荐

  1. Android自定义view之事件传递机制

    Android自定义view之事件传递机制 在上一篇文章<Android自定义view之measure.layout.draw三大流程>中,我们探讨了一下view的显示过程.不太熟悉的同学 ...

  2. 精通Android自定义View(十三)事件分发简述

    1 事件序列 (1)手指接触屏幕后会产生一系列事件,事件分为3种:ACTION_DOWN(手指刚刚接触屏幕).ACTION_MOVE(手指在屏幕移动).ACTION_UP(手指从屏幕松开) (2)一个 ...

  3. Android自定义View2--触摸事件传递机制

    转载文章 :https://juejin.im/post/6844904041487532045#heading-6 https://juejin.im/post/684490389410388378 ...

  4. Android 自定义View 滑动解锁

    自定义view来绘制一个类似滑动解锁的button,注释都在,效果如下 代码在下面,替换一下资源,可以直接使用,如果需要画的是圆角的话,需要把下面两行注释的DrawLine的注释打开,然后把两行dra ...

  5. Android自定义view之ViewPager指示器——1

    Android自定义view之ViewPager指示器--1 在上两篇文章<Android自定义view之measure.layout.draw三大流程>以及<Android自定义v ...

  6. Android自定义View注意事项

    Android自定义View系列 Android自定义View之Paint绘制文字和线 Android自定义View之图像的色彩处理 Android自定义View之Canvas Android自定义V ...

  7. 精通Android自定义View(十四)绘制水平向右加载的进度条

    1引言 1 精通Android自定义View(一)View的绘制流程简述 2 精通Android自定义View(二)View绘制三部曲 3 精通Android自定义View(三)View绘制三部曲综合 ...

  8. Android自定义View之Paint绘制文字和线

    Android自定义View系列 Android自定义View注意事项 Android自定义View之图像的色彩处理 Android自定义View之Canvas Android自定义View之轻松实现 ...

  9. android 自定义view滚动条,Android自定义View实现等级滑动条的实例

    Android自定义View实现等级滑动条的实例 实现效果图: 思路: 首先绘制直线,然后等分直线绘制点: 绘制点的时候把X值存到集合中. 然后绘制背景图片,以及图片上的数字. 点击事件down的时候 ...

  10. Android 自定义View(四)实现股票自选列表滑动效果

    一.前言 Android 开发过程中自定义 View 真的是无处不在,随随便便一个 UI 效果,都会用到自定义 View.前面三篇文章已经讲过自定义 View 的一些案例效果,相关类和 API,还有事 ...

最新文章

  1. 利用WSS搭建学生作业平台
  2. 用存储过程创建的分页
  3. ubuntu rar文件乱码
  4. 三天打鱼,两天晒网。
  5. linux下set和eval的使用小案例精彩解答
  6. 兼容性好的overflow CSS清除浮动一例
  7. JFinal 1.5 发布,JAVA极速WEB+ORM框架
  8. drop.delete.trauncat的区别
  9. google 浏览器默认打开控制台_前端开发调试:浏览器console方法总结
  10. dategridview代码选中行_使用IntelliJ IDEA进行Java代码调试的技巧
  11. springmvc注解详解
  12. 单文件浏览器_浏览器工作原理
  13. HDU 5468 Puzzled Elena (2015年上海赛区网络赛A题)
  14. 5S管理跟精益生产的关系是什么?如何使5S管理有效落地?
  15. [算法]代码运行时间增长数量级对比 线性级别N vs 线性对数级别 NlgN
  16. 5g局域网传输速度_4G5G和上网带宽与下载速度的换算方法
  17. python交易是什么意思_Py交易是什么意思
  18. linux环境下mysql主从数据库配置(maser-slave-replication)
  19. 读List源码之Vector,ArrayList,LinkedList
  20. 【Linux进程间通信】四、mmap共享存储映射

热门文章

  1. SAP PM 入门系列7 - 常用Function Modules
  2. 中国知名企业ERP失败案例深入剖析
  3. 科大讯飞刘庆峰发表对未来10年AI三大判断
  4. 布局自动驾驶L3级,探访北汽福田发动机生产基地!
  5. 深入剖析机器学习中的统计思想
  6. 一句话总结贝叶斯分类器
  7. 卷积神经网络(CNN)的原理
  8. 一文盘点10大移动端机器学习框架
  9. 数学之美——隐含马尔科夫模型
  10. SOR迭代求解线性方程组代码实现