2024年安卓最全Android 每周一个小轮子之 学习仿网易云广场歌单的效果(2),字节跳动面试链接

最后

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

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

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

3、关于LayoutParams


LayoutParams是用来协助Viewgoup的,它可以给子View定义一些属性。而且也可以支持margin。

我们要给子View定义 :

  • 从哪里来(from)

  • 到哪里去(to)

  • 透明度(alpha)

  • 缩放值(scale)

所以我们要这样子重写:

/**

  • 这里要自己写一个ViewGroup的LayoutParams来记录 scale、alpha、from、to

*/

class RikkaLayoutParams extends MarginLayoutParams {

float scale = 0f;

float alpha = 0f;

int from;

int to;

…(getter and setter)

public RikkaLayoutParams(Context c, AttributeSet attrs) {

super(c, attrs);

}

public RikkaLayoutParams(int width, int height) {

super(width, height);

}

public RikkaLayoutParams(LayoutParams source) {

super(source);

}

}

/**

  • 要支持margin,所以要重写generate方法

*/

@Override

public LayoutParams generateLayoutParams(AttributeSet attrs) {

return new RikkaLayoutParams(mContext, attrs);

}

@Override

protected LayoutParams generateLayoutParams(LayoutParams p) {

return new RikkaLayoutParams§;

}

@Override

protected LayoutParams generateDefaultLayoutParams() {

return new RikkaLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

}

4、关于测量


ViewGroup的宽度要么是写死的值,要么是三个子View之和

高度要么是写死的值,要么是三个View里面,最大的那一个:

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//先测量子View

measureChildren(widthMeasureSpec, heightMeasureSpec);

//因为这个时候已经测量完子View了,所以通过子View来计算整个View的宽高

int width = measureWidth(widthMeasureSpec);

int height = measureHeight(heightMeasureSpec);

//根据获取的宽高拿去用

setMeasuredDimension(width, height);

}

//整个View的宽度是三个子View的和

private int measureWidth(int widthMeasureSpec) {

int totalWidth = 0;

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

if (widthMode == MeasureSpec.EXACTLY) {

totalWidth = widthSize;

} else {

for (int i = 0; i < getChildCount(); i++) {

RikkaLayoutParams lp = (RikkaLayoutParams) getChildAt(i).getLayoutParams();

totalWidth += getChildAt(i).getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

}

}

return totalWidth;

}

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

}

最后

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

欢迎大家一起交流讨论啊~

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

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

kaLayoutParams 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);

}

最后

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

[外链图片转存中…(img-qstjCal9-1715733130604)]

欢迎大家一起交流讨论啊~

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

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值