}
测量完后就需要布局了。
我们要根据三条辅助线来确定。最左边的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;
View clickView = getChildAt(i);
if (isPointInView(clickView, points)) {
Log.d(TAG, “isPointInView:” + i);
if (indexOfChild(clickView) != 2) {
//如果点到1、0View,则将他们移到最前方
setSelection(clickView);
}
}
}
return;
}
initAnimator();
}
/**
- 也是做动画,只是它是做一次完整的动画,起始值
*/
private void setSelection(View clickView) {
写在最后
在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。
如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
加入我们吧!群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。




由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)

多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-B7UlHLox-1710958500362)]
[外链图片转存中…(img-S6F0th6Y-1710958500362)]
[外链图片转存中…(img-UaR1vg6a-1710958500363)]
[外链图片转存中…(img-5g7KQYd7-1710958500363)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-T86brRzJ-1710958500364)]

被折叠的 条评论
为什么被折叠?



