mPaint.setAntiAlias(true); //设置为抗锯齿
mPaint.setColor(mColor);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setPathEffect(new CornerPathEffect(3)); //设置三角形不那么尖锐
}
/**
-
绘制矩形
-
绘制VIew本身的内容,通过调用View.onDraw(canvas)函数实现,绘制自己的孩子通过dispatchDraw(canvas)实现
-
-
画完背景后,draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法,
-
dispatchDraw
-
()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制
-
,当它没有背景时直接调用的是dispatchDraw
-
()方法,而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了
-
dispatchDraw()方法的调用。因此要在ViewGroup上绘制东西的时候往往重写的是
-
dispatchDraw()方法而不是onDraw()方法,或者自定制一个Drawable,重写它的draw(Canvas c)和
-
getIntrinsicWidth(),getIntrinsicHeight()方法,然后设为背景
*/
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
/*
-
save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
-
restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
-
save和restore要配对使用(restore可以比save少,但不能多),如果restore调用次数比save多,会引发Error。
*/
canvas.save();
canvas.translate(mTranslationX, RECT_HEIGHT);
//绘制一个封闭的区域
canvas.drawPath(mPath, mPaint);
canvas.restore();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//矩形的宽 就是屏幕的宽度/可见个数
mRectWidth = w / mTabVisibleCount;
initRect();
}
/**
- 初始化矩形
*/
private void initRect() {
//矩形的位置在 子控件的底部
mPath = new Path();
mPath.moveTo(0, getHeight()); //将下一个轮廓的开始设置为点(x,y)。
//从最后一点添加一行到指定点(x,y)。如果没有对此轮廓进行moveTo()调用,则第一个点为自动设置为(0,0)。
mPath.lineTo(mRectWidth, getHeight());
mPath.lineTo(mRectWidth, getHeight() - RECT_HEIGHT - RECT_HEIGHT);
mPath.lineTo(0, getHeight() - RECT_HEIGHT - RECT_HEIGHT);
// 关闭当前轮廓,完成闭合
mPath.close();
}
/**
-
移动指示器
-
@param position 需要移动到的那个位置
-
@param offset 移动偏移量百分比
*/
public void scroll(int position, float offset) {
//每个tab的宽度
int tabWidth = getWidth() / mTabVisibleCount;
//指示器需要偏移的距离
mTranslationX = (int) (tabWidth * offset + tabWidth * position);
//当移动到倒数第二个时,就需要移动ViewPagerIndicator 当前这个ViewGroup了
if (position >= (mTabVisibleCount - 2) && offset > 0 && getChildCount() >
mTabVisibleCount) {
//如果mTabVisibleCount == 1时,mTabVisibleCount - 2是负数
if (mTabVisibleCount != 1) {
//如果是移动到所有页签的倒数第二个则不能再移动了
if (position != getChildCount() - 2) {
Log.d(TAG, “scroll: position----”+position);
this.scrollTo((int) ((position - (mTabVisibleCount - 2)) * tabWidth + tabWidth *
offset), 0);
//当移动到界面可见数的最后一个时,直接移动到0 不然会出现第一个显示不全的bug
if (position == mTabVisibleCount-2) {
Log.d(TAG, “scroll: 0”);
this.scrollTo(0,0);
}
}
} else {
this.scrollTo((int) (position * tabWidth + tabWidth * offset), 0);
}
}
//重绘
invalidate();
}
/**
-
当xml加载完成之后就会调用此方法,必须调用super()方法
-
我们需要在这做的事情是 将每个子控件的weight改为0,然后将默认的可以显示的个数的子控件显示出来
-
通过布局添加tab的时候,调用到此方法
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//获取子控件个数
int childCount = getChildCount();
if (childCount < 0) {
return;
}
//设置每个页签的宽度
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
LinearLayout.LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
layoutParams.weight = 0;
layoutParams.width = getScreenWidth() / mTabVisibleCount;
view.setLayoutParams(layoutParams);
}
//设置每个tab的点击事件
setTabItemClickEvent();
}
/**
-
获取屏幕宽度
-
@return
*/
private int getScreenWidth() {
//拿到WindowManager对象
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context
.WINDOW_SERVICE);
Display defaultDisplay = windowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
defaultDisplay.getMetrics(metrics);
return metrics.widthPixels;
}
/**
-
动态地设置可见tab的个数,必须大于0
-
必须在setTabItemTitles()之前调用此方法才能生效
-
@param mTabVisibleCount
*/
public void setmTabVisibleCount(int mTabVisibleCount) {
if (mTabVisibleCount > 0) {
this.mTabVisibleCount = mTabVisibleCount;
} else {
this.mTabVisibleCount = COUNT_DEFAULT_TAB; //默认值
}
}
/**
-
动态设置tab
-
@param titles 所有tab的标题
*/
public void setTabItemTitles(List titles) {
if (titles != null && titles.size() > 0) {
//用户是想设置Tab,则无视布局文件中写的TextView,全部移除
removeAllViews();
//生成标题
for (String title : titles) {
addView(generateTextView(title));
}
}
//设置每个tab的点击事件
setTabItemClickEvent();
}
/**
-
根据title动态地生成一个tab 其实就是一个TextView,用来显示tab的名称用的
-
@param title
-
@return
*/
private View generateTextView(String title) {
TextView textView = new TextView(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout
.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
if (mTabVisibleCount == 0) {
params.width = getScreenWidth() / COUNT_DEFAULT_TAB;
} else {
params.width = getScreenWidth() / mTabVisibleCount;
}
textView.setGravity(Gravity.CENTER);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
textView.setText(title);
textView.setTextColor(COLOR_TEXT_NORMAL);
textView.setLayoutParams(params);
return textView;
}
/**
-
设置关联的ViewPager
-
@param viewPager
-
@param position 设置当前位置
*/
public void setViewPager(ViewPager viewPager, int position) {
this.mViewPager = viewPager;
//页面改变事件 滑动
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int
positionOffsetPixels) {
//移动指示器
scroll(position, positionOffset);
//监听器 全部需要回调此方法 参考源码
if (mOnPageChangeListeners != null) {
for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
OnPageChangeListener listener = mOnPageChangeListeners.get(i);
if (listener != null) {
listener.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
}
}
}
@Override
public void onPageSelected(int position) {
if (mOnPageChangeListeners != null) {
for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
OnPageChangeListener listener = mOnPageChangeListeners.get(i);
if (listener != null) {
listener.onPageSelected(position);
}
}
}
//高亮当前选中的位置
highLightTextView(position);
}
@Override
public void onPageScrollStateChanged(int state) {
if (mOnPageChangeListeners != null) {
for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
OnPageChangeListener listener = mOnPageChangeListeners.get(i);
if (listener != null) {
listener.onPageScrollStateChanged(state);
}
}
}
}
});
//设置当前的tab是position位置
viewPager.setCurrentItem(position);
//高亮当前选中的位置
highLightTextView(position);
}
/**
-
需要暴露给开发者的ViewPager的滑动事件 接口 和ViewPager的监听器定义地一模一样
-
可能开发者也需要用到ViewPager的监听事件
*/
public interface OnPageChangeListener {
void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
void onPageSelected(int position);
void onPageScrollStateChanged(int state);
}
/**
-
给ViewPager添加监听器
-
官方已经弃用setOnPageChangeListener()了,取而代之的是
-
Use {@link #addOnPageChangeListener(OnPageChangeListener)}
-
and {@link #removeOnPageChangeListener(OnPageChangeListener)} instead.
-
@param listener
*/
public void addOnPageChangeListener(OnPageChangeListener listener) {
if (mOnPageChangeListeners == null) {
mOnPageChangeListeners = new ArrayList<>();
}
mOnPageChangeListeners.add(listener);
}
/**
-
Remove a listener that was previously added via
-
{@link #addOnPageChangeListener(OnPageChangeListener)}.
-
@param listener listener to remove
*/
public void removeOnPageChangeListener(OnPageChangeListener listener) {
if (mOnPageChangeListeners != null) {
mOnPageChangeListeners.remove(listener);
最后
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
- 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
- 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
知识脉络 + 诸多细节*,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
[外链图片转存中…(img-bEElqmyL-1714697433097)]
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
- 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
- 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!