Android 每周一个小轮子之 学习仿网易云广场歌单的效果

}

5、关于布局


测量完后就需要布局了。

我们要根据三条辅助线来确定。最左边的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;

}

6、关于滑动、子View的移动


我们需要在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));

}

}

}

7、关于抬起手指时的过渡动画和单击的动画


他们是一样的,都是从我们最后手指离开时的偏移量,到某一个值(比如说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移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

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

多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-B7UlHLox-1710958500362)]
[外链图片转存中…(img-S6F0th6Y-1710958500362)]
[外链图片转存中…(img-UaR1vg6a-1710958500363)]
[外链图片转存中…(img-5g7KQYd7-1710958500363)]

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值