2024年安卓最全Android 每周一个小轮子之 学习仿网易云广场歌单的效果(1),2024年最新程序员去公司面试准备什么

总结

【Android 详细知识点思维脑图(技能树)】

image

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

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

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

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

有了这三个方法,我们就能真真正正的判断我们点击的地方是不是在一个View中:

/**

  • 用矩阵的方法,来定义一个点是否位于一个区域内

*/

private boolean isPointInView(View view, float[] points) {

// 像ViewGroup那样,先对齐一下Left和Top

points[0] -= view.getLeft();

points[1] -= view.getTop();

// 获取View所对应的矩阵

Matrix matrix = view.getMatrix();

// 如果矩阵有应用过变换

if (!matrix.isIdentity()) {

// 反转矩阵

matrix.invert(matrix);

// 映射坐标点

matrix.mapPoints(points);

}

//判断坐标点是否在view范围内

return points[0] >= 0 && points[1] >= 0 && points[0] < view.getWidth() && points[1] < view.getHeight();

}

学会了,学会了(抱拳)

2、关于层级交换的方法


关于层级的交换,其实就是把两个View从ViewGroup取出来,然后交换顺序,又放回去。

对应的是detachViewFromParent()attachViewToParent()方法

我们只需要知道,想要交换的两个View在ViewGroup的层级顺序是什么就行了。

其中 indexofChild(View v)可以知道它所处的层级,它越大,说明它越靠上,最大值就是 childCount-1,这个时候它是在最上层的 。

/**

  • 使用attachViewToParent和detachAllViewsFromParent来交换两个Index的层级

*/

public void exchangeOrder(int fromIndex, int toIndex) {

if (fromIndex == toIndex) {

return;

}

View fromChild = getChildAt(fromIndex);

View toChild = getChildAt(toIndex);

//分开这些View

detachViewFromParent(fromChild);

detachViewFromParent(toChild);

//交换两个View,本质就是交换它们的index

attachViewToParent(fromChild, toIndex > getChildCount() ? getChildCount() : toIndex, fromChild.getLayoutParams());

attachViewToParent(toChild, fromIndex > getChildCount() ? getChildCount() : fromIndex, toChild.getLayoutParams());

invalidate();

}

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

如果你进阶的路上缺乏方向,可以加入我们的圈子和安卓开发者们一起学习交流!

  • Android进阶学习全套手册

    img

  • Android对标阿里P7学习视频

    img

  • BATJ大厂Android高频面试题

    img

最后,借用我最喜欢的乔布斯语录,作为本文的结尾:

人这一辈子没法做太多的事情,所以每一件都要做得精彩绝伦。
你的时间有限,所以不要为别人而活。不要被教条所限,不要活在别人的观念里。不要让别人的意见左右自己内心的声音。
最重要的是,勇敢的去追随自己的心灵和直觉,只有自己的心灵和直觉才知道你自己的真实想法,其他一切都是次要。

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

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

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

ikkaLayoutParams) getChildAt(i).getLayoutParams();

lp.setFrom(lp.getTo());

如果你进阶的路上缺乏方向,可以加入我们的圈子和安卓开发者们一起学习交流!

  • Android进阶学习全套手册

    [外链图片转存中…(img-TaGyqHhV-1715733096274)]

  • Android对标阿里P7学习视频

    [外链图片转存中…(img-uQieoiVf-1715733096275)]

  • BATJ大厂Android高频面试题

    [外链图片转存中…(img-jNsBdDiL-1715733096275)]

最后,借用我最喜欢的乔布斯语录,作为本文的结尾:

人这一辈子没法做太多的事情,所以每一件都要做得精彩绝伦。
你的时间有限,所以不要为别人而活。不要被教条所限,不要活在别人的观念里。不要让别人的意见左右自己内心的声音。
最重要的是,勇敢的去追随自己的心灵和直觉,只有自己的心灵和直觉才知道你自己的真实想法,其他一切都是次要。

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值