正文大纲
- 参考效果
- 前置技能
- 实现思路
- 关键代码
- 思维拓展
正文
参考效果
上图 UI
美眉给的手机录屏,是蚂蚁财富app
某一个版本上的滑动切换效果.
我们需要开发的是 下面这一半 这个滑动切换的控件。
前置技能
经过对ViewPager
可能特效的研究,发现它自身就带有这种动画特效的可能性,不用我们去自定义控件。
但是上方的TabLayout
字体大小变化,指示器indicator的长度和位置变化,谷歌给的TabLayout
貌似没法弄,所以只能自己DIY
了.
要完成这个特效,两个技能必须就位:
-
android 视图动画
android体系中比较原始的一种动画类型。原理,是将view的绘制过程在指定区域,按照指定规则再进行一遍,但是原本view所携带的事件交互,则不受影响。由于无法真正地继承事件交互,所以被属性动画所取代。但是它仍然有自己的价值。在不涉及到交互,只考虑视觉效果的情况下,它的效率反而比属性动画更高。 -
数学建模思想
不要误会,这里说的数学建模是一种思维方式,把我们肉眼看到的现象,用数学公式的形式表达出来而已,并不是什么高深的操作。学过自定义控件并且 深入实践过的童鞋应该能够体会到,要想真正从0开始完成一个DIY
控件,会有大量的数学计算,而拥有好的数学思维能力,能够在自定义的时候如鱼得水。
实现思路
一 ,源码研究
要对ViewPager进行特效改造,那么首先我们要知道ViewPager是一个容器ViewGroup
,它内部的子View
是如何摆放的,虽然从视觉上我们能够感觉到 子view是横向摆放的,但是作为技术人,就要敢于追根究底,用源码说话。
进入源码,找到 onLayout
方法(以下是我提炼的关键代码
):
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
…
for (int i = 0; i < count; i++) {
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
int childLeft = 0;
int childTop = 0;
if (lp.isDecor) {
…
}
}
}
…
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
ItemInfo ii;
if (!lp.isDecor && (ii = infoForChild(child)) != null) {
int loff = (int) (childWidth * ii.offset);
int childLeft = paddingLeft + loff;
int childTop = paddingTop;
if (lp.needsMeasure) {
// This was added during layout and needs measurement.
// Do it now that we know what we’re working with.
lp.needsMeasure = false;
final int widthSpec = MeasureSpec.makeMeasureSpec(
(int) (childWidth * lp.widthFactor),
MeasureSpec.EXACTLY);
final int heightSpec = MeasureSpec.makeMeasureSpec(
(int) (height - paddingTop - paddingBottom),
MeasureSpec.EXACTLY);
child.measure(widthSpec, heightSpec);
}
if (DEBUG) {
Log.v(TAG, “Positioning #” + i + " " + child + " f=" + ii.object
- “:” + childLeft + “,” + childTop + " " + child.getMeasuredWidth()
- “x” + child.getMeasuredHeight());
}
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
}
上面代码中,对 count
进行了两轮循环,其中第一轮是针对 lp.isDecor
为 true
的,
意为:如果当前view是一个 decoration 装饰,并不是adapter提供的view 则返回 true
显然,我们要探讨的是 adapter提供的View 是如何摆放的,所以忽略这一块。
而在下面的循环中,可以看到
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
这个便是child的排布的核心代码,追溯这4个参数,可以得知:第 1,3 参数 表示 left ,right , 他们都和一个 int loff = (int) (childWidth * ii.offset);
挂钩,而 第2,4 参数表示 top,bottom , 则 并没有与 任何动态参数相挂钩。
因此可以断定,ViewPager
的子View
排布,只会存在X轴方向上的位置偏差,在Y方向上会保持上下平齐。
其实还可以继续追溯 int loff = (int) (childWidth * ii.offset);
看看 x轴方向上的位置偏差是如何造成的,但是目的已经达到,到有必要的时候再去追查。
确定是横向排布,那么左右滑动逻辑又是怎么样的呢?
找到 onTouchEvent()
方法, 并且在其中找到 ACTION_MOVE 逻辑分支:
case MotionEvent.ACTION_MOVE:
if (!mIsBeingDragged) {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
// A child has consumed some touch events and put us into an inconsistent
// state.
needsInvalidate = resetTouch();
break;
}
final float x = ev.getX(pointerIndex);
final float xDiff = Math.abs(x - mLastMotionX);
final float y = ev.getY(pointerIndex);
final float yDiff = Math.abs(y - mLastMotionY);
if (DEBUG) {
Log.v(TAG, “Moved x to " + x + “,” + y + " diff=” + xDiff + “,” + yDiff);
}
if (xDiff > mTouchSlop && xDiff > yDiff) {
if (DEBUG) Log.v(TAG, “Starting drag!”);
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollState(SCROLL_STATE_DRAGGING);
setScrollingCacheEnabled(true);
// Disallow Parent Intercept, just in case
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
// Not else! Note that mIsBeingDragged can be set above.
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(activePointerIndex);
needsInvalidate |= performDrag(x);
}
break;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
面试复习笔记:
这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960页Android开发笔记》
《1307页Android开发面试宝典》
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-nki4IBZy-1712770653174)]