不忘初心 砥砺前行, Tomorrow Is Another Day !html
本文概要:git
适合对View内容的滑动,只对View的scrollX、scrollY有影响,对View的大小和位置没有影响.github
对应源码canvas
//增量滚动,增量是指在已有的滚动偏移量的增量
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();
}
}
}
复制代码
源码中两个比较重要的参数:bash
题外话:网上部分博客说成是坐标,看了mScrollX注释"The offset, in pixels, by which the content of this view is scrolled horizontally." 用个人渣英语一看,特么不说的是"View的内容在水平方向滚动的偏移量"吗,因此我的以为说偏移量更加严谨.若有不对欢迎拍砖,请指点.app
由于内部调用invalidate致使重绘,不会走测量布局过程,因此才有上述结论.因为是对View的内容进行滑动,因此需注意滑动方向的问题.ide
对应源码工具
//由父元素调用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);
}
//...省略部分代码
}
复制代码
对View的L、T、R、B属性有影响.布局
示例代码post
private void scrollMethodOnLayout(int offsetX, int offsetY) {
//layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
}
复制代码
对布局参数有影响
示例代码
private void scrollMethodOnLP(int offsetX, int offsetY) {
//在不清楚父View是什么类型时,可使用ViewGroup.MarginLayoutParams
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin += offsetX;
layoutParams.topMargin += offsetY;
setLayoutParams(layoutParams);
}
复制代码
- 视图动画适合没有交互性的View
- 属性动画适合有交互性的View,只对View具体属性值有影响
下一篇详细讲解动画相关.
实现弹性滑动
使用步骤:
示例代码
//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();
}
}
复制代码
原理:
具体流程能够看源码,比较简单这里就不单独分析了.
根据上面Scroller的原理能够总结出一个弹性滑动的核心思想,那就是在一个时间段里,将一次大的滑动分解成屡次小的滑动,除了系统的提供的Scrooler,还可使用动画与延迟策略去实现.
View系统配置信息
速度追踪,通常用于识别快速滑动
使用步骤:
手势识别
使用步骤:
参考: blog.csdn.net/lfdfhl/arti…
通常用于自定义ViewGroup中处理子View的拖动.
使用步骤:
参考:
只是将View位置改变,不会触发View的重绘,这是与前面ScrollTo的最大区别.
请求父元素不要拦截个人事件
对应源码
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) {
}
复制代码
另外有一种特殊状况直接采用三个参数的方法时, 3. 当root不为空,attachToRoot为false时,这时不会将解析的xml布局添加到根节点root中,最后返回xml中的根节点.
这时root的做用,仅仅只是为了给xml布局中的根节点提供layoutParams的参数属性,不然layoutParams的参数属性会失效,由于其xml中的根节点压根不知道本身父容器是谁.具体用例好比在已经退出历史舞台的ListView中getView时,inflate防止item的布局参数失效.
若是想详细的了解inflate的源码实现细节,能够参考郭婶的博客,地址blog.csdn.net/guolin_blog…
当xml布局文件被解析完成时.
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();
}
});
复制代码
触发测量和布局过程,不会触发重绘.
使用场景:
通常用于View的位置和大小改变时.
View的大小发生改变时.
在View进行布局过程会被调用.在layout-setFrame-onSizeChange
使用场景:
通常用于初始化与View的大小相关成员变量.
是否不绘制,默认是true
因为ViewGroup默认是不绘制本身的,除非设置了背景或者调用了setWillNotDraw设置为false.才会绘制本身
使用场景:
通常用于自定义ViewGroup而且想要实现它自己的绘制时,就能够设置一个背景或者直接调用setWillNotDraw(false)
触发View的重绘,但不会调用测量和布局过程.
二者区别:
将普通数值转换成对应数据类型的数值
示例代码
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());
复制代码
除了系统提供的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);
}
复制代码
在前面几篇文章中有几个问题一直未进行解释,下面对此依次说明.
解答: 默认返回的是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);
}
}
复制代码
解答: 产生时机不一样,前者产生于measure过程,后者产生于layout过程
解答: 经过了解ActivityThread过程能够得知,activity生命周期方法和view的measure过程不是同步的.
解决方式: 示例代码
@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());
}
}
复制代码
@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.
}
复制代码
//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文档使用.
因为本人技术有限,若有错误的地方,麻烦你们给我提出来,本人不胜感激,你们一块儿学习进步.
参考连接: