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

//整个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;

}

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;

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

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

总结

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的14套腾讯、字节跳动、阿里、百度等2021最新面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。

2020面试真题解析
腾讯面试真题解析

阿里巴巴面试真题解析

字节跳动面试真题解析
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

本文已被CODING开源项目:《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的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值