//整个View的高 取三个View的最大值
private int measureHeight(int heightMeasureSpec) {
int maxHeight = 0;
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
//如果是具体值就取具体值
maxHeight = heightSize;
} else {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
RikkaLayoutParams lp = (RikkaLayoutParams) child.getLayoutParams();
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
}
}
return maxHeight;
}
测量完后就需要布局了。
我们要根据三条辅助线来确定。最左边的View的辅助线,应该以左边为准,右边的以右边为准,中间的以中间为准。
由于在滑动时,View的位置也是要变的,也要不断的走onLayout
方法,所以辅助线也是跟着变动的,它是跟着滑动百分比
来计算的。
/**
-
根据基准线去布局子View
-
基准线有四条,子View分别在这四条线上
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
int baseLineX = calBaseLine(i);
int baseLineY = getHeight() / 2;
//滑动的过程也是layout的过程,所以在layout的时候也要更改其透明度和缩放度
View child = getChildAt(i);
RikkaLayoutParams lp = (RikkaLayoutParams) child.getLayoutParams();
child.setScaleX(lp.getScale());
child.setScaleY(lp.getScale());
child.setAlpha(lp.getAlpha());
int left = baseLineX - child.getMeasuredWidth() / 2;
int top = baseLineY - child.getMeasuredHeight() / 2;
int right = left + child.getMeasuredWidth();
int bottom = top + child.getMeasuredHeight();
child.layout(left + lp.leftMargin + getPaddingLeft(),
top + lp.topMargin + getPaddingTop(),
right + lp.rightMargin + getPaddingRight(),
bottom + lp.bottomMargin + getPaddingBottom());
}
}
/**
- 根据offsetPercent来计算基线位置,子View是根据基线来布局的
*/
private int calBaseLine(int index) {
float baseline = 0;
//最左边的baseline
float baselineLeft = getWidth() / 4;
//最中间的baseline
float baselineCenter = getWidth() / 2;
//最右边的baseline
float baselineRight = getWidth() - baselineLeft;
RikkaLayoutParams lp = (RikkaLayoutParams) getChildAt(index).getLayoutParams();
//根据lp的from 和 to来确定基线位置
switch (lp.getFrom()) {
case 0:
if (lp.getTo() == 1) {
baseline = baselineLeft + (baselineRight - baselineLeft) * -offsetPercent;
} else if (lp.getTo() == 2) {
baseline = baselineLeft + (baselineCenter - baselineLeft) * offsetPercent;
} else {
baseline = baselineLeft;
}
break;
case 1:
if (lp.getTo() == 0) {
baseline = baselineRight - (baselineRight - baselineLeft) * offsetPercent;
} else if (lp.getTo() == 2) {
baseline = baselineRight + (baselineRight - baselineCenter) * offsetPercent;
} else {
baseline = baselineRight;
}
break;
case 2:
if (lp.getTo() == 1) {
baseline = baselineCenter + (baselineRight - baselineCenter) * offsetPercent;
} else if (lp.getTo() == 0) {
baseline = baselineCenter + (baselineCenter - baselineLeft) * offsetPercent;
} else {
baseline = baselineCenter;
}
break;
}
return (int) baseline;
}
我们需要在onInterceptTouchEvent
里判断一下我们是否需要使用到onTouchEvent
,所以我们需要时时刻刻的去获取点击的位置,并记录偏移量,来判断是否是滑动状态,如果是的话,我们需要处理子View的移动了。
/**
- 如果是滑动,则调用onTouchEvent,如果只是单击,可以切换View
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
isDraged = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mDownX = x;
mDownY = y;
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
//如果滑动超出规定的距离,则可以滑动View
int offsetX = (int) (x - mLastX);
int offsetY = (int) (y - mLastY);
if (Math.abs(offsetX) > MIN_SLOP_DISTANCE && Math.abs(offsetY) > MIN_SLOP_DISTANCE) {
mLastX = x;
mLastY = y;
isDraged = true;
}
case MotionEvent.ACTION_UP:
isDraged = false;
break;
}
return isDraged;
}
/**
- onTouchEvent就是确定是要滑动了,根据滑动距离,做子View的位移动画
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
//通过总位移量除以View长来得到百分比
int offsetX = (int) (x - mLastX);
totalOffsetX += offsetX;
moveItem();
break;
case MotionEvent.ACTION_UP:
isDraged = false;
break;
}
mLastX = x;
mLastY = y;
//能走到onTouchEvent就肯定是返回true的
return true;
}
而子View就是根据 总位移量totalOffsetX来计算百分比的:
/**
- 通过百分比的正负值来确定每个View要去到哪里、设置透明度和缩放、交换View的层级
*/
private void moveItem() {
offsetPercent = totalOffsetX / getWidth();
setViewFromAndTo();
changeViewLevel();
changeAlphaAndScale();
requestLayout();
}
/**
-
根据百分比的正负值,来设置View的from和to
-
如果是负则说明手指正在往左边滑动,则 0->1,1->2,2->0,反之亦然
*/
private void setViewFromAndTo() {
//如果滑动距离超出了屏幕的宽度,则超出的部分要更新
if (Math.abs(offsetPercent) >= 1) {
//在每次完整的滑完一次后,需要重置isReordered,不然当一次滑动很长距离时,会产生问题
isReordered = false;
for (int i = 0; i < getChildCount(); i++) {
RikkaLayoutParams lp = (RikkaLayoutParams) getChildAt(i).getLayoutParams();
lp.setFrom(lp.getTo());
}
totalOffsetX %= getWidth();
offsetPercent %= 1f;
} else {
//否则就要判断from和to
for (int i = 0; i < getChildCount(); i++) {
RikkaLayoutParams lp = (RikkaLayoutParams) getChildAt(i).getLayoutParams();
switch (lp.getFrom()) {
case 0:
lp.setTo(offsetPercent > 0 ? 2 : 1);
break;
case 1:
lp.setTo(offsetPercent > 0 ? 0 : 2);
break;
case 2:
lp.setTo(offsetPercent > 0 ? 1 : 0);
break;
}
}
}
}
/**
-
当滑动进度超出了0.5则需要交换层级,2是最上层,0和1在下层,交换的时候交换1,2就行了
-
isReordered判断有没有交换过层级,每次onInterceptTouchEvent的时候都要重置
-
因为可能会交换了还要交换回来
*/
private void changeViewLevel() {
Log.d(TAG, "offsetPercent : " + offsetPercent);
if (Math.abs(offsetPercent) >= 0.5f) {
if (!isReordered) {
exchangeOrder(1, 2);
isReordered = true;
}
} else {
if (isReordered) {
//如果没有超出0.5f,但是又交换过层级,说明滑到一半后又往回滑了,需要交换回来
exchangeOrder(1, 2);
isReordered = false;
}
}
}
/**
- 改变正在移动的View的Scale和透明度
*/
private void changeAlphaAndScale() {
for (int i = 0; i < getChildCount(); i++) {
RikkaLayoutParams lp = (RikkaLayoutParams) getChildAt(i).getLayoutParams();
switch (lp.getFrom()) {
case 0:
if (lp.getTo() == 2) {
lp.setAlpha(MIN_ALPHA + (1f - MIN_ALPHA) * offsetPercent);
lp.setScale(MIN_SCALE + (1f - MIN_SCALE) * offsetPercent);
} else if (lp.getTo() == 1) {
//将View和低层的View交换
exchangeOrder(indexOfChild(getChildAt(i)), 0);
}
break;
case 1:
if (lp.getTo() == 0) {
exchangeOrder(indexOfChild(getChildAt(i)), 0);
} else if (lp.getTo() == 2) {
lp.setAlpha(MIN_ALPHA + (1f - MIN_ALPHA) * Math.abs(offsetPercent));
lp.setScale(MIN_SCALE + (1f - MIN_SCALE) * Math.abs(offsetPercent));
}
break;
case 2:
lp.setAlpha(1f - (1f - MIN_ALPHA) * Math.abs(offsetPercent));
lp.setScale(1f - (1f - MIN_SCALE) * Math.abs(offsetPercent));
}
}
}
他们是一样的,都是从我们最后手指离开时的偏移量,到某一个值(比如说0、getWidth、-getWidth)
能走完一个流程。
所以我们需要在 ACTION_UP
的时候多做一个动画的方法
在这里我们就会用到一开始的,判断手指点击的地方是不是在一个View中了。
/**
- 每次抬起手指的时候需要判断当前要不要做动画
*/
private void handleActionUp(int x, int y) {
if (Math.abs(x - mDownX) < MIN_SLOP_DISTANCE && Math.abs(y - mLastY) < MIN_SLOP_DISTANCE) {
for (int i = getChildCount() - 1; i >= 0; i–) {
//确定是单击,首先要判断是点击的是哪一个View,因为传入的points会改变,所以每次都要重新定义
float[] points = new float[2];
points[0] = x;
points[1] = y;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
总结
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的14套腾讯、字节跳动、阿里、百度等2021最新面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
图片转存中…(img-q5a4e39u-1712101544904)]
[外链图片转存中…(img-BEWQGGpx-1712101544904)]
[外链图片转存中…(img-Aa1X8UWk-1712101544905)]
[外链图片转存中…(img-YUL2hyqf-1712101544905)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算