不忘初心 砥砺前行, Tomorrow Is Another Day !
相关文章
本文概要:
- 触摸事件相关
- View工作流程相关
- 其他工具
- 常见问题
一. 触摸事件相关
1.1 ScrollTo和ScrollBy
适合对View内容的滑动,只对View的scrollX、scrollY有影响,对View的大小和位置没有影响.
对应源码
//增量滚动,增量是指在已有的滚动偏移量的增量
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
//绝对滚动
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
复制代码
源码中两个比较重要的参数:
- mScrollX : 水平方向滚动的偏移量(类似绝对坐标,可以理解为绝对偏移量)
- 计算方式: mScrollX = 0(初始位置) - 100(结束位置) = -100
- mScrollY : 竖直方向滚动的偏移量
题外话:网上部分博客说成是坐标,看了mScrollX注释"The offset, in pixels, by which the content of this view is scrolled horizontally." 用我的渣英语一看,特么不说的是"View的内容在水平方向滚动的偏移量"吗,所以个人觉得说偏移量更加严谨.如有不对欢迎拍砖,请指点.
因为内部调用invalidate导致重绘,不会走测量布局过程,所以才有上述结论.由于是对View的内容进行滑动,所以需注意滑动方向的问题.
对应源码
//由父元素调用dispatchDraw,然后调用三个参数的draw方法.
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
//...省略部分代码
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
computeScroll();
sx = mScrollX;
sy = mScrollY;
}
final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
int restoreTo = -1;
if (!drawingWithRenderNode || transformToApply != null) {
restoreTo = canvas.save();
}
if (offsetForScroll) {
//平移的是画布,这就解释了为什么传入的方向值要相反.
canvas.translate(mLeft - sx, mTop - sy);
}
//...省略部分代码
}
复制代码
1.2 layout 、offsetLeftAndRight与offsetTopAndBottom
对View的L、T、R、B属性有影响.
示例代码
private void scrollMethodOnLayout(int offsetX, int offsetY) {
//layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
}
复制代码
1.3 LayoutParams
对布局参数有影响
示例代码
private void scrollMethodOnLP(int offsetX, int offsetY) {
//在不清楚父View是什么类型时,可以使用ViewGroup.MarginLayoutParams
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin += offsetX;
layoutParams.topMargin += offsetY;
setLayoutParams(layoutParams);
}
复制代码
1.4 动画
- 视图动画适合没有交互性的View
- 属性动画适合有交互性的View,只对View具体属性值有影响
下一篇详细讲解动画相关.
1.5 Scroller
实现弹性滑动
使用步骤:
- 初始化Scroller
- startScroll开启滑动过程
- 在View中重写computeScroll方法
示例代码
//1. 第一步
mScroller = new Scroller(context);
//2. 第二步
private void scrollMethodOnScroller(int offsetX, int offsetY) {
mScroller.startScroll(((View) getParent()).getScrollX(), ((View) getParent()).getScrollY(),
offsetX, offsetY, 3000);
invalidate();
}
//3. 第三步
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
复制代码
原理:
- 首先通过startScroll保存相关的滑动坐标信息.
- 当我们调用invalidate进行重绘时,系统会回调computeScroll方法.
- 在computeScrollOffset方法里根据时间的流逝,去计算是否滑动完成.
- 未完成,则继续invalidate进行重绘.
具体流程可以看源码,比较简单这里就不单独分析了.
根据上面Scroller的原理可以总结出一个弹性滑动的核心思想,那就是在一个时间段里,将一次大的滑动分解成多次小的滑动,除了系统的提供的Scrooler,还可以使用动画与延迟策略去实现.
1.6 ViewConfiguration
View系统配置信息
参考:blog.youkuaiyun.com/lfdfhl/arti…
1.7 VelocityTracker
速度追踪,一般用于识别快速滑动
使用步骤:
- 初始化VelocityTracker
- 接管触摸事件,获取当前滑动速度
- 重置并回收.
参考:blog.youkuaiyun.com/lfdfhl/arti…
1.8 GestureDetector
手势识别
使用步骤:
- 初始化GestureDetector
- 接管触摸事件
- 处理手势识别回调
参考: blog.youkuaiyun.com/lfdfhl/arti…
1.9 ViewDragHelper
一般用于自定义ViewGroup中处理子View的拖动.
使用步骤:
- 初始化ViewDragHelper
- 接管触摸事件
- 处理回调
参考:
1.10 setTranslationX和setTranslationY
只是将View位置改变,不会触发View的重绘,这是与前面ScrollTo的最大区别.
- setTranslationX : 设置View在水平方向,相对于它的left位置偏移量.
- setTranslationY : 同理
1.11 requestDisallowInterceptTouchEvent
请求父元素不要拦截我的事件
二. View工作流程相关
2.1 inflate(布局解析器)
对应源码
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
//调用了三个参数的方法
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
}
复制代码
- 当root为空时,那么会解析xml布局,最后返回xml中的根节点.
- 当root不为空时,那么会解析xml布局并且添加到根节点root下,最后返回这个根节点root.
另外有一种特殊情况直接采用三个参数的方法时, 3. 当root不为空,attachToRoot为false时,这时不会将解析的xml布局添加到根节点root中,最后返回xml中的根节点.
这时root的作用,仅仅只是为了给xml布局中的根节点提供layoutParams的参数属性,否则layoutParams的参数属性会失效,因为其xml中的根节点压根不知道自己父容器是谁.具体用例比如在已经退出历史舞台的ListView中getView时,inflate防止item的布局参数失效.
如果想详细的了解inflate的源码实现细节,可以参考郭婶的博客,地址blog.youkuaiyun.com/guolin_blog…
2.2 onFinishInflate
当xml布局文件被解析完成时.
2.3 ViewTreeObserver
View树观察者,包括布局、绘制、触摸事件变化等.
示例代码
- 获取view的宽高
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
复制代码
2.4 requestLayout
触发测量和布局过程,不会触发重绘.
使用场景:
一般用于View的位置和大小改变时.
2.5 onSizeChange
View的大小发生改变时.
在View进行布局过程会被调用.在layout-setFrame-onSizeChange
使用场景:
一般用于初始化与View的大小相关成员变量.
2.6 setWillNotDraw
是否不绘制,默认是true
由于ViewGroup默认是不绘制自己的,除非设置了背景或者调用了setWillNotDraw设置为false.才会绘制自己
使用场景:
一般用于自定义ViewGroup并且想要实现它本身的绘制时,就可以设置一个背景或者直接调用setWillNotDraw(false)
2.7 invalidate与postInvalidate
触发View的重绘,但不会调用测量和布局过程.
两者区别:
- invalidate : 只能用于主线程
- postInvalidate : 可以用于子线程更新UI
- 内部原理最终还是通过handler发送message到主线程,然后调用invalidate.
2.8 onAttachFromWindow与onDetachFromWindow
- onAttachFromWindow : 当一个View绑定到Window上时的调用.
- 一般用于初始化一些任务等等
- onDetachFromWindow :同理.
三.其他工具
3.1 TypeValue
将普通数值转换成对应数据类型的数值
示例代码
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,666,getResources().getDisplayMetrics());
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,666,getResources().getDisplayMetrics());
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,666,getResources().getDisplayMetrics());
复制代码
3.2 DisplayMetrics
除了系统提供的TypeValue,还可以自己实现转换.
示例代码
public static int dp2Px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
public static int px2Dp(Context context, float px) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (px / scale + 0.5f);
}
public static int px2Sp(float pxValue, Context context) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
public static int sp2Px(float spValue, Context context) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (spValue * scale + 0.5f);
}
复制代码
四.常见问题
在前面几篇文章中有几个问题一直未进行解释,下面对此依次说明.
- 为什么需要针对wrap_content时进行处理,不然效果等同match_parent?
解答: 默认返回的是specSize(parentSize父元素可用空间)
解决方式: 示例代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//处理wrap_content,设置一个默认的宽或高
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, 200);
} else if (widthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, heightSize);
} else if (heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSize, 200);
}
}
复制代码
- getMeasureWidth和getWidth的区别?
解答: 产生时机不同,前者产生于measure过程,后者产生于layout过程
- onCreate/onStart/onResume中无法获取宽高?
解答: 通过了解ActivityThread过程可以得知,activity生命周期方法和view的measure过程不是同步的.
解决方式: 示例代码
- Activity/View#onWindowFocusChanged
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
Log.d(TAG, "onWindowFocusChanged, width= " + view.getMeasuredWidth() + " height= " + view.getMeasuredHeight());
}
}
复制代码
- view.post(runnable)与ViewTreeObserver
@Override
protected void onStart() {
super.onStart();
//*********
view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
//ViewTreeObserver已经说明,见本文2.3.
}
复制代码
- 手动view.measure(int widthMeasureSpec, int heightMeasureSpec) 需要根据view的layoutParams区分
//match_parent,无法得知measureSpec的parentSize
private void measureView() {
//wrap_content
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);
//具体数值
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "measureView, width= " + view.getMeasuredWidth() + " height= " + view.getMeasuredHeight());
}
复制代码
关于常用工具暂时先到这了,未来也会持续更新,用作实际开发中API文档使用.
由于本人技术有限,如有错误的地方,麻烦大家给我提出来,本人不胜感激,大家一起学习进步.
参考链接: