最后
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
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);
}
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;
}
测量完后就需要布局了。
我们要根据三条辅助线来确定。最左边的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;
}
我们需要在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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!