文末
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
在一个视频通话界面中,放置一个发言方列表,这个列表支持横向滑动,称为小窗列表, 处于背景的窗口称之大窗,当用户想将小窗列表中的某一个 item 切换到大窗时,可以使用手指触摸想要切换的 item, 并向上方滑动,即可将选定的小窗切换至大窗位置,而且上滑需要支持垂直向上和斜向上的方向。
解决方案
原始解决方案是为 item view 设置 OnTouchListener
方法, 在其 onTouch()
方法中的 ACTION_MOVE
事件中判断 dy (Y 坐标偏移量) 是否大于某个阈值。
遇到的问题
遇到的问题是当在 item 斜向上滑动时,item view 收到的 ACTION_MOVE
事件的 dy 总是特别小,即使你确定已经滑动了很多时
-
该问题定位为 在横向滑动时,RecyclerView 与 item 发生了嵌套滑动冲突
-
怀疑是 RecyclerView 消费了部分滑动事件,导致 item view 收到的滑动距离特别小。
通过翻阅源码发现,RecyclerView 内部提供了 OnItemTouchListener
, 介绍如下:
/**
-
An OnItemTouchListener allows the application to intercept touch events in progress at the
-
view hierarchy level of the RecyclerView before those touch events are considered for
-
RecyclerView’s own scrolling behavior.
-
This can be useful for applications that wish to implement various forms of gestural
-
manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept
-
a touch interaction already in progress even if the RecyclerView is already handling that
-
gesture stream itself for the purposes of scrolling.
-
@see SimpleOnItemTouchListener
*/
public static interface OnItemTouchListener{
/**
-
Silently observe and/or take over touch events sent to the RecyclerView
-
before they are handled by either the RecyclerView itself or its child views.
-
The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run
-
in the order in which each listener was added, before any other touch processing
-
by the RecyclerView itself or child views occurs.
-
@param e MotionEvent describing the touch event. All coordinates are in
-
the RecyclerView's coordinate system.
-
@return true if this OnItemTouchListener wishes to begin intercepting touch events, false
-
to continue with the current behavior and continue observing future events in
-
the gesture.
*/
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
…
}
OnItemTouchListener
的作用主要有两个:
-
在 RecyclerView 对事件消费之前,给予开发者自定义事件分发算法的权利。
-
当 RecyclerView 已经在对事件消费过程中时,可以通过本类对 RecylerView 正在处理的事件序列进行拦截。
本文提到的问题看似可以解决,思路就是为 RecyclerView 添加 OnItemTouchListener
, 在其 onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
调用时判断,如果 Y 轴的偏移量大于某一阈值,表明当前用户想触发窗口置换操作,那么就在 onInterceptTouchEvent()
中返回 false, 我们期望 RecyclerView 完全不消费事件,使事件下沉到 RecyclerView 的 item view 中,那么 item 就可以正常获取到 MOVE 事件,部分代码如下:
/**
- 纵坐标偏移量阈值
*/
private final int Y_AXIS_MOVE_THRESHOLD = 15;
private int downY = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
downY = (int) e.getRawY();
} else if (e.getAction() == MotionEvent.ACTION_MOVE) {
int realtimeY = (int) e.getRawY();
int dy = Math.abs(downY - realtimeY);
if (dy > Y_AXIS_MOVE_THRESHOLD) {
return false;
}
}
return true;
}
但其实这样是无法实现需求的,因为如果按照我们目前的实现方案,是期望在 dy 大于阈值时,RecyclerView 可以完全对 MOVE 事件放手,将事件下沉到 item view 中去处理,根据事件分发规则,这就需要 RecyclerView 的 onInterceptTouchEvent()
return false,然后子 View 即 item view 的 onTouchEvent()
会被调用。进而实现窗口置换,下面我们来通过源码分析为什么这种方案不能实现。
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mLayoutFrozen) {
// When layout is frozen, RV does not intercept the motion event.
// A child view e.g. a button may still get the click.
return false;
}
if (dispatchOnItemTouchIntercept(e)) {
cancelTouch();
return true;
}
if (mLayout == null) {
return false;
}
final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
final boolean canScrollVertically = mLayout.canScrollVertically();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(e);
final int action = MotionEventCompat.getActionMasked(e);
final int actionIndex = MotionEventCompat.getActionIndex(e);
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mIgnoreMotionEventTillDown) {
mIgnoreMotionEventTillDown = false;
}
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
if (mScrollState == SCROLL_STATE_SETTLING) {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
// Clear the nested offsets
mNestedOffsets[0] = mNestedOffsets[1] = 0;
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis);
break;
case MotionEventCompat.ACTION_POINTER_DOWN:
mScrollPointerId = e.getPointerId(actionIndex);
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
break;
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id " +
mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false;
}
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
if (mScrollState != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
} break;
case MotionEventCompat.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.clear();
stopNestedScroll();
} break;
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
架构师筑基包括哪些内容
我花了将近半个月时间将:深入 Java 泛型.、注解深入浅出、并发编程.、数据传输与序列化、Java 虚拟机原理、反射与类加载、高效 IO、Kotlin项目实战等等Android架构师筑基必备技能整合成了一套系统知识笔记PDF,相信看完这份文档,你将会对这些Android架构师筑基必备技能有着更深入、更系统的理解。
由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容
注:资料与上面思维导图一起看会更容易学习哦!每个点每个细节分支,都有对应的目录内容与知识点!
这份资料就包含了所有Android初级架构师所需的所有知识!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
,在此只以截图展示部分内容**
注:资料与上面思维导图一起看会更容易学习哦!每个点每个细节分支,都有对应的目录内容与知识点!
[外链图片转存中…(img-JHOuvPVK-1715623858019)]
[外链图片转存中…(img-RAkAt4bo-1715623858019)]
这份资料就包含了所有Android初级架构师所需的所有知识!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!