最新Android开源:想送你一款小清新的加载等待 控件(2),2024年最新腾讯安卓开发面试流程

最后

我一直以来都有整理练习大厂面试题的习惯,有随时跳出舒服圈的准备,也许求职者已经很满意现在的工作,薪酬,觉得习惯而且安逸。

不过如果公司突然倒闭,或者部门被裁减,还能找到这样或者更好的工作吗?

我建议各位,多刷刷面试题,知道最新的技术,每三个月可以去面试一两家公司,因为你已经有不错的工作了,所以可以带着轻松的心态去面试,同时也可以增加面试的经验。

我可以将最近整理的一线互联网公司面试真题+解析分享给大家,大概花了三个月的时间整理2246页,帮助大家学习进步。

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!以下是部分内容截图:

部分目录截图

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

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

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

// 释放资源
typedArray.recycle();
}

// 此步骤结束

/**

  • 关注1:判断方块是否在内部
    */

private boolean isInsideTheRect(int pos, int lineCount) {
// 判断方块是否在第1行
if (pos < lineCount) {
return false;
// 是否在最后1行
} else if (pos > (lineCount * lineCount - 1 - lineCount)) {
return false;
// 是否在最后1行
} else if ((pos + 1) % lineCount == 0) {
return false;
// 是否在第1行
} else if (pos % lineCount == 0) {
return false;
}
// 若不在4边,则在内部
return true;
}
// 回到原处

步骤2:初始化方块对象 & 之间的关系

private void init() {
// 初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(blockColor);

// 初始化方块对象 & 关系 ->>关注1
initBlocks(initPosition);

}

/**

  • 关注1
  • 初始化方块对象、之间的关系
  • 参数说明:initPosition = 移动方块的初始位置
    */
    private void initBlocks(int initPosition) {

// 1. 创建总方块的数量(固定方块) = lineNumber * lineNumber
// lineNumber = 方块的行数
// fixedBlock = 固定方块 类 ->>关注2
mfixedBlocks = new fixedBlock[lineNumber * lineNumber];

// 2. 创建方块
for (int i = 0; i < mfixedBlocks.length; i++) {

// 创建固定方块 & 保存到数组中
mfixedBlocks[i] = new fixedBlock();

// 对固定方块对象里的变量进行赋值
mfixedBlocks[i].index = i;
// 对方块是否显示进行判断
// 若该方块的位置 = 移动方块的初始位置,则隐藏;否则显示
mfixedBlocks[i].isShow = initPosition == i ? false : true;
mfixedBlocks[i].rectF = new RectF();
}

// 3. 创建移动的方块(1个) ->>关注3
mMoveBlock = new MoveBlock();
mMoveBlock.rectF = new RectF();
mMoveBlock.isShow = false;

// 4. 关联外部方块的位置
// 因为外部的方块序号 ≠ 0、1、2…排列,通过 next变量(指定其下一个),一个接一个连接 外部方块 成圈
// ->>关注4
relate_OuterBlock(mfixedBlocks, isClock_Wise);

}
// 此步骤结束

/**

  • 关注2:固定方块 类(内部类)
    */
    private class fixedBlock {

// 存储方块的坐标位置参数
RectF rectF;

// 方块对应序号
int index;

// 标志位:判断是否需要绘制
boolean isShow;

// 指向下一个需要移动的位置
fixedBlock next;
// 外部的方块序号 ≠ 0、1、2…排列,通过 next变量(指定其下一个),一个接一个连接 外部方块 成圈

}
// 请回到原处

/**

  • 关注3
    *:移动方块类(内部类)
    */
    private class MoveBlock {
    // 存储方块的坐标位置参数
    RectF rectF;

// 方块对应序号
int index;

// 标志位:判断是否需要绘制
boolean isShow;

// 旋转中心坐标
// 移动时的旋转中心(X,Y)
float cx;
float cy;
}
// 请回到原处

/**

  • 关注4:将外部方块的位置关联起来
  • 算法思想: 按照第1行、最后1行、第1列 & 最后1列的顺序,分别让每个外部方块的next属性 == 下一个外部方块的位置,最终对整个外部方块的位置进行关联
  • 注:需要考虑移动方向变量isClockwise( 顺 Or 逆时针)
    */

private void relate_OuterBlock(fixedBlock[] fixedBlocks, boolean isClockwise) {
int lineCount = (int) Math.sqrt(fixedBlocks.length);

// 情况1:关联第1行
for (int i = 0; i < lineCount; i++) {
// 位于最左边
if (i % lineCount == 0) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i + 1];
// 位于最右边
} else if ((i + 1) % lineCount == 0) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + lineCount];
// 中间
} else {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + 1];
}
}
// 情况2:关联最后1行
for (int i = (lineCount - 1) * lineCount; i < lineCount * lineCount; i++) {
// 位于最左边
if (i % lineCount == 0) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
// 位于最右边
} else if ((i + 1) % lineCount == 0) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
// 中间
} else {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - 1];
}
}

// 情况3:关联第1列
for (int i = 1 * lineCount; i <= (lineCount - 1) * lineCount; i += lineCount) {
// 若是第1列最后1个
if (i == (lineCount - 1) * lineCount) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
continue;
}
fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i - lineCount];
}

// 情况4:关联最后1列
for (int i = 2 * lineCount - 1; i <= lineCount * lineCount - 1; i += lineCount) {
// 若是最后1列最后1个
if (i == lineCount * lineCount - 1) {
fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
continue;
}
fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i + lineCount];
}
}
// 请回到原处


步骤3:设置方块初始位置

// 该步骤写在onSizeChanged()
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// 调用时刻:onCreate之后onDraw之前调用;view的大小发生改变就会调用该方法
// 使用场景:用于屏幕的大小改变时,需要根据屏幕宽高来决定的其他变量可以在这里进行初始化操作
super.onSizeChanged(w, h, oldw, oldh);

int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();

// 1. 设置移动方块的旋转中心坐标
int cx = measuredWidth / 2;
int cy = measuredHeight / 2;

// 2. 设置固定方块的位置 ->>关注1
fixedBlockPosition(mfixedBlocks, cx, cy, blockInterval, half_BlockWidth);
// 3. 设置移动方块的位置 ->>关注2
MoveBlockPosition(mfixedBlocks, mMoveBlock, initPosition, isClock_Wise);
}

// 此步骤结束

/**

  • 关注1:设置 固定方块位置
    */
    private void fixedBlockPosition(fixedBlock[] fixedBlocks, int cx, int cy, float dividerWidth, float halfSquareWidth) {

// 1. 确定第1个方块的位置
// 分为2种情况:行数 = 偶 / 奇数时
// 主要是是数学知识,此处不作过多描述
float squareWidth = halfSquareWidth * 2;
int lineCount = (int) Math.sqrt(fixedBlocks.length);
float firstRectLeft = 0;
float firstRectTop = 0;

// 情况1:当行数 = 偶数时
if (lineCount % 2 == 0) {
int squareCountInAline = lineCount / 2;
int diviCountInAline = squareCountInAline - 1;
float firstRectLeftTopFromCenter = squareCountInAline * squareWidth

  • diviCountInAline * dividerWidth
  • dividerWidth / 2;
    firstRectLeft = cx - firstRectLeftTopFromCenter;
    firstRectTop = cy - firstRectLeftTopFromCenter;

// 情况2:当行数 = 奇数时
} else {
int squareCountInAline = lineCount / 2;
int diviCountInAline = squareCountInAline;
float firstRectLeftTopFromCenter = squareCountInAline * squareWidth

  • diviCountInAline * dividerWidth
  • halfSquareWidth;
    firstRectLeft = cx - firstRectLeftTopFromCenter;
    firstRectTop = cy - firstRectLeftTopFromCenter;
    firstRectLeft = cx - firstRectLeftTopFromCenter;
    firstRectTop = cy - firstRectLeftTopFromCenter;
    }

// 2. 确定剩下的方块位置
// 思想:把第一行方块位置往下移动即可
// 通过for循环确定:第一个for循环 = 行,第二个 = 列
for (int i = 0; i < lineCount; i++) {//行
for (int j = 0; j < lineCount; j++) {//列
if (i == 0) {
if (j == 0) {
fixedBlocks[0].rectF.set(firstRectLeft, firstRectTop,
firstRectLeft + squareWidth, firstRectTop + squareWidth);
} else {
int currIndex = i * lineCount + j;
fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - 1].rectF);
fixedBlocks[currIndex].rectF.offset(dividerWidth + squareWidth, 0);
}
} else {
int currIndex = i * lineCount + j;
fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - lineCount].rectF);
fixedBlocks[currIndex].rectF.offset(0, dividerWidth + squareWidth);
}
}
}
}

// 回到原处

/**

  • 关注2:设置移动方块的位置
    */
    private void MoveBlockPosition(fixedBlock[] fixedBlocks,
    MoveBlock moveBlock, int initPosition, boolean isClockwise) {

// 移动方块位置 = 设置初始的空出位置 的下一个位置(next)
// 下一个位置 通过 连接的外部方块位置确定
fixedBlock fixedBlock = fixedBlocks[initPosition];
moveBlock.rectF.set(fixedBlock.next.rectF);
}
// 回到原处


步骤4:绘制方块

// 此步骤写到onDraw()中
@Override
protected void onDraw(Canvas canvas) {

// 1. 绘制内部方块(固定的)
for (int i = 0; i < mfixedBlocks.length; i++) {
// 根据标志位判断是否需要绘制
if (mfixedBlocks[i].isShow) {
// 传入方块位置参数、圆角 & 画笔属性
canvas.drawRoundRect(mfixedBlocks[i].rectF, fixBlock_Angle, fixBlock_Angle, mPaint);
}
}
// 2. 绘制移动的方块
if (mMoveBlock.isShow) {
canvas.rotate(isClock_Wise ? mRotateDegree : -mRotateDegree, mMoveBlock.cx, mMoveBlock.cy);
canvas.drawRoundRect(mMoveBlock.rectF, moveBlock_Angle, moveBlock_Angle, mPaint);
}

}

步骤5:设置动画

实现该动画的步骤包括:设置平移动画、旋转动画 & 组合动画。

1.设置平移动画

private ValueAnimator createTranslateValueAnimator(fixedBlock currEmptyfixedBlock,
fixedBlock moveBlock) {
float startAnimValue = 0;
float endAnimValue = 0;
PropertyValuesHolder left = null;
PropertyValuesHolder top = null;

// 1. 设置移动速度
ValueAnimator valueAnimator = new ValueAnimator().setDuration(moveSpeed);

// 2. 设置移动方向
// 情况分为:4种,分别是移动方块向左、右移动 和 上、下移动
// 注:需考虑 旋转方向(isClock_Wise),即顺逆时针 ->>关注1
if (isNextRollLeftOrRight(currEmptyfixedBlock, moveBlock)) {

// 情况1:顺时针且在第一行 / 逆时针且在最后一行时,移动方块向右移动
if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index || !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {

startAnimValue = moveBlock.rectF.left;
endAnimValue = moveBlock.rectF.left + blockInterval;

// 情况2:顺时针且在最后一行 / 逆时针且在第一行,移动方块向左移动
} else if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
|| !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {

startAnimValue = moveBlock.rectF.left;
endAnimValue = moveBlock.rectF.left - blockInterval;
}

// 设置属性值
left = PropertyValuesHolder.ofFloat(“left”, startAnimValue, endAnimValue);
valueAnimator.setValues(left);

} else {
// 情况3:顺时针且在最左列 / 逆时针且在最右列,移动方块向上移动
if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
|| !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {

startAnimValue = moveBlock.rectF.top;
endAnimValue = moveBlock.rectF.top - blockInterval;

// 情况4:顺时针且在最右列 / 逆时针且在最左列,移动方块向下移动
} else if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index
|| !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {
startAnimValue = moveBlock.rectF.top;
endAnimValue = moveBlock.rectF.top + blockInterval;
}

// 设置属性值
top = PropertyValuesHolder.ofFloat(“top”, startAnimValue, endAnimValue);
valueAnimator.setValues(top);
}

// 3. 通过监听器更新属性值
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object left = animation.getAnimatedValue(“left”);
Object top = animation.getAnimatedValue(“top”);
if (left != null) {
mMoveBlock.rectF.offsetTo((Float) left, mMoveBlock.rectF.top);
}
if (top != null) {
mMoveBlock.rectF.offsetTo(mMoveBlock.rectF.left, (Float) top);
}
// 实时更新旋转中心 ->>关注2
setMoveBlockRotateCenter(mMoveBlock, isClock_Wise);

// 更新绘制
invalidate();
}
});
return valueAnimator;
}
// 此步骤分析完毕

/**

  • 关注1:判断移动方向
  • 即上下 or 左右
    */
    private boolean isNextRollLeftOrRight(fixedBlock currEmptyfixedBlock, fixedBlock rollSquare) {
    if (currEmptyfixedBlock.rectF.left - rollSquare.rectF.left == 0) {
    return false;
    } else {
    return true;
    }
    }
    // 回到原处

/**

  • 关注2:实时更新移动方块的旋转中心
  • 因为方块在平移旋转过程中,旋转中心也会跟着改变,因此需要改变MoveBlock的旋转中心(cx,cy)
    */

private void setMoveBlockRotateCenter(MoveBlock moveBlock, boolean isClockwise) {

// 情况1:以移动方块的左上角为旋转中心
if (moveBlock.index == 0) {
moveBlock.cx = moveBlock.rectF.right;
moveBlock.cy = moveBlock.rectF.bottom;

// 情况2:以移动方块的右下角为旋转中心
} else if (moveBlock.index == lineNumber * lineNumber - 1) {
moveBlock.cx = moveBlock.rectF.left;
moveBlock.cy = moveBlock.rectF.top;

// 情况3:以移动方块的左下角为旋转中心
} else if (moveBlock.index == lineNumber * (lineNumber - 1)) {
moveBlock.cx = moveBlock.rectF.right;
moveBlock.cy = moveBlock.rectF.top;

// 情况4:以移动方块的右上角为旋转中心
} else if (moveBlock.index == lineNumber - 1) {
moveBlock.cx = moveBlock.rectF.left;
moveBlock.cy = moveBlock.rectF.bottom;
}

//以下判断与旋转方向有关:即顺 or 逆顺时针

// 情况1:左边
else if (moveBlock.index % lineNumber == 0) {
moveBlock.cx = moveBlock.rectF.right;
moveBlock.cy = isClockwise ? moveBlock.rectF.top : moveBlock.rectF.bottom;

// 情况2:上边
} else if (moveBlock.index < lineNumber) {
moveBlock.cx = isClockwise ? moveBlock.rectF.right : moveBlock.rectF.left;
moveBlock.cy = moveBlock.rectF.bottom;

// 情况3:右边
} else if ((moveBlock.index + 1) % lineNumber == 0) {
moveBlock.cx = moveBlock.rectF.left;
moveBlock.cy = isClockwise ? moveBlock.rectF.bottom : moveBlock.rectF.top;

// 情况4:下边
} else if (moveBlock.index > (lineNumber - 1) * lineNumber) {
moveBlock.cx = isClockwise ? moveBlock.rectF.left : moveBlock.rectF.right;
moveBlock.cy = moveBlock.rectF.top;
}
}
// 回到原处

2. 设置旋转动画

private ValueAnimator createMoveValueAnimator() {

// 通过属性动画进行设置
ValueAnimator moveAnim = ValueAnimator.ofFloat(0, 90).setDuration(moveSpeed);

moveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object animatedValue = animation.getAnimatedValue();

// 赋值
mRotateDegree = (float) animatedValue;

// 更新视图
invalidate();
}
});
return moveAnim;
}
// 此步骤完毕

如何成为Android高级架构师!

架构师必须具备抽象思维和分析的能力,这是你进行系统分析和系统分解的基本素质。只有具备这样的能力,架构师才能看清系统的整体,掌控全局,这也是架构师大局观的形成基础。 你如何具备这种能力呢?一是来自于经验,二是来自于学习。

架构师不仅要具备在问题领域上的经验,也需要具备在软件工程领域内的经验。也就是说,架构师必须能够准确得理解需求,然后用软件工程的思想,把需求转化和分解成可用计算机语言实现的程度。经验的积累是需要一个时间过程的,这个过程谁也帮不了你,是需要你去经历的。

但是,如果你有意识地去培养,不断吸取前人的经验的话,还是可以缩短这个周期的。这也是我整理架构师进阶此系列的始动力之一。


成为Android架构师必备知识技能

对应导图的学习笔记(由阿里P8大牛手写,我负责整理成PDF笔记)

部分内容展示

《设计思想解读开源框架》

  • 目录
  • 热修复设计
  • 插件化框架设计

    《360°全方面性能优化》
  • 设计思想与代码质量优化
  • 程序性能优化

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

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

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

5368795471)]

  • 热修复设计
    [外链图片转存中…(img-Bpn1NpGF-1715368795471)]
  • 插件化框架设计
    [外链图片转存中…(img-Ir9U44yk-1715368795471)]
    《360°全方面性能优化》
    [外链图片转存中…(img-Z51oZw4G-1715368795472)]
  • 设计思想与代码质量优化
    [外链图片转存中…(img-bMuCvB0P-1715368795472)]
  • 程序性能优化
    [外链图片转存中…(img-awhjExod-1715368795472)]

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

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

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

  • 27
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值