mScrollRange -= getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
}
private List getNonGoneChildren() {
List children = new ArrayList<>();
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
children.add(child);
}
}
return children;
}
onMeasured的逻辑很简单,遍历测量子vew即可。
onLayout是把子view从上到下排列,就像一个垂直的LinearLayout一样。getNonGoneChildren()方法过滤掉隐藏的子view,隐藏的子view不参与布局。
上面的mScrollRange变量是布局自身可滑动的范围,它等于所有子view的高度减去布局自身的内容显示高度。在后面,它将用于计算布局的滑动偏移和边距限制。
拦截滑动事件前面说过ConsecutiveScrollerLayout会拦截它的可滑动的子view的滑动事件,由自己来处理所有的滑动。下面是它拦截事件的实现。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
// 需要拦截事件
if (isIntercept(ev)) {
return true;
}
}
return super.onInterceptTouchEvent(ev);
}
如果是滑动事件(ACTION_MOVE),判断是否需要拦截事件,拦截则直接返回true,让事件交由ConsecutiveScrollerLayout的onTouchEvent方法处理。判断是否需要拦截的关键是isIntercept(ev)方法。
private boolean isIntercept(MotionEvent ev) {
// 根据触摸点获取当前触摸的子view
View target = getTouchTarget((int) ev.getRawX(), (int) ev.getRawY());
if (target != null) {
// 判断子view是否允许父布局拦截事件
ViewGroup.LayoutParams lp = target.getLayoutParams();
if (lp instanceof LayoutParams) {
if (!((LayoutParams) lp).isConsecutive) {
return false;
}
}
// 判断子view是否可以垂直滑动
if (ScrollUtils.canScrollVertically(target)) {
return true;
}
}
return false;
}
public class ScrollUtils {
static boolean canScrollVertically(View view) {
return canScrollVertically(view, 1) || canScrollVertically(view, -1);
}
static boolean canScrollVertically(View view, int direction) {
return view.canScrollVertically(direction);
}
}
判断是否需要拦截事件,主要是通过判断触摸的子view是否可以垂直滑动,如果可以垂直滑动,就拦截事件,让事件由ConsecutiveScrollerLayout自己处理。如果不是,就不拦截,一般不能滑动的view不会消费滑动事件,所以事件最终会由ConsecutiveScrollerLayout所消费。之所以不直接拦截,是为了能让子view尽可能的获得事件处理和分发给下面的view的机会。
这里有一个isConsecutive的LayoutParams属性,它是ConsecutiveScrollerLayout.LayoutParams的自定义属性,用于表示一个子view是否允许ConsecutiveScrollerLayout拦截它的滑动事件,默认为true。如果把它设置为false,父布局将不会拦截这个子view的事件,而是完全交由子view处理。这使得子view有了自己处理滑动事件的机会和分发事件的主动权。
这对于实现一些需要实现局部区域内滑动的特殊需求十分有用。我在GitHub中提供的demo和使用介绍中对isConsecutive有详细的说明,在这就不做过多介绍了。
滑动处理把事件拦截后,就要在onTouchEvent方法中处理滑动事件。
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点
mTouchY = (int) ev.getY();
// 追踪滑动速度
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
break;
case MotionEvent.ACTION_MOVE:
if (mTouchY == 0) {
mTouchY = (int) ev.getY();
return true;
}
int y = (int) ev.getY();
int dy = y - mTouchY;
mTouchY = y;
// 滑动布局
scrollBy(0, -dy);
// 追踪滑动速度
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mTouchY = 0;
if (mVelocityTracker != null) {
// 处理惯性滑动
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int yVelocity = (int) mVelocityTracker.getYVelocity();
recycleVelocityTracker();
fling(-yVelocity);
}
break;
}
return true;
}
// 惯性滑动
private void fling(int velocityY) {
if (Math.abs(velocityY) > mMinimumVelocity) {
mScroller.fling(0, mOwnScrollY,
1, velocityY,
0, 0,
Integer.MIN_VALUE, Integer.MAX_VALUE);
invalidate();
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
int curY = mScroller.getCurrY();
// 滑动布局
dispatchScroll(curY);
invalidate();
}
}
onTouchEvent方法的逻辑非常简单,就是根据手指的滑动距离通过view的scrollBy方法滑动布局内容,同时通过VelocityTracker追踪手指的滑动速度,使用Scroller配合computeScroll()方法实现惯性滑动。
滑动距离的分发在处理惯性滑动是时候,我们调用了dispatchScroll()方法,这个方法是整个ConsecutiveScrollerLayout的核心,它决定了应该由谁来消费这次滑动,应该滑动那个布局。
其实ConsecutiveScrollerLayout的scrollBy()和scrollTo()方法最终都是调用它来处理滑动的分发的。
这里有个mOwnScrollY属性,是用于记录ConsecutiveScrollerLayout的整体滑动距离的,相当于View的mScrollY属性。
dispatchScroll()方法把滑动分成向上和向下两部分处理。让我们先看向上滑动部分的处理。
private void scrollUp(int offset) {
int scrollOffset = 0; // 消费的滑动记录
int remainder = offset; // 未消费的滑动距离
do {
scrollOffset = 0;
// 是否滑动到底部
if (!isScrollBottom()) {
// 找到当前显示的第一个View
View firstVisibleView = findFirstVisibleView();
if (firstVisibleView != null) {
awakenScrollBars();
// 获取View滑动到自身底部的偏移量
int bottomOffset = ScrollUtils.getScrollBottomOffset(firstVisibleView);
if (bottomOffset > 0) {
// 如果bottomOffset大于0,表示这个view还没有滑动到自身的底部,那么就由这个view来消费这次的滑动距离。
int childOldScrollY = ScrollUtils.computeVerticalScrollOffset(firstVisibleView);
// 计算需要滑动的距离
scrollOffset = Math.min(remainder, bottomOffset);
// 滑动子view
scrollChild(firstVisibleView, scrollOffset);
// 计算真正的滑动距离
scrollOffset = ScrollUtils.computeVerticalScrollOffset(firstVisibleView) - childOldScrollY;
} else {
// 如果子view已经滑动到自身的底部,就由父布局消费滑动距离,直到把这个子view滑出屏幕
int selfOldScrollY = getScrollY();
// 计算需要滑动的距离
scrollOffset = Math.min(remainder,
firstVisibleView.getBottom() - getPaddingTop() - getScrollY());
// 滑动父布局
scrollSelf(getScrollY() + scrollOffset);
// 计算真正的滑动距离
scrollOffset = getScrollY() - selfOldScrollY;
}
// 计算消费的滑动距离,如果还没有消费完,就继续循环消费。
mOwnScrollY += scrollOffset;
remainder = remainder - scrollOffset;
}
}
} while (scrollOffset > 0 && remainder > 0);
简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。
面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。
另外,描述问题一定要慢!不要一下子讲一大堆,慢显得你沉稳、自信,而且你还有时间反应思路接下来怎么讲更好。现在开发过多依赖ide,所以会有个弊端,当我们在面试讲解很容易不知道某个方法怎么读,这是一个硬伤…所以一定要对常见的关键性的类名、方法名、关键字读准,有些面试官不耐烦会说“你到底说的是哪个?”这时我们会容易乱了阵脚。正确的发音+沉稳的描述+好听的嗓音决对是一个加分项!
最重要的是心态!心态!心态!重要事情说三遍!面试时间很短,在短时间内对方要摸清你的底子还是比较不现实的,所以,有时也是看眼缘,这还是个看脸的时代。
希望大家都能找到合适自己满意的工作!
如果需要PDF版本可以在GitHub中自行领取!
或者点击这里自行下载,直达领取链接
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-c7siyZSt-1646484882168)]
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-YxfmTA2T-1646484882170)]



