轻松实现分页指示器 ViewPagerIndicator Android自定义控件

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(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

img

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!

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

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

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

知识脉络 + 诸多细节*,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

[外链图片转存中…(img-bEElqmyL-1714697433097)]

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!

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

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

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

  • 8
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
分页器是一个常见的 UI 控件,用于展示数据的分页情况,并提供翻页操作。在 Vue 中,我们可以通过自定义组件的方式来实现分页器。下面是一个简单的分页器组件示例: ```vue <template> <div class="pagination"> <button :disabled="currentPage === 1" @click="prevPage">上一页</button> <span class="page-num">{{ currentPage }} / {{ totalPages }}</span> <button :disabled="currentPage === totalPages" @click="nextPage">下一页</button> </div> </template> <script> export default { props: { currentPage: { type: Number, required: true }, totalPages: { type: Number, required: true } }, methods: { prevPage() { if (this.currentPage > 1) { this.$emit('page-change', this.currentPage - 1); } }, nextPage() { if (this.currentPage < this.totalPages) { this.$emit('page-change', this.currentPage + 1); } } } } </script> <style scoped> .pagination { display: flex; justify-content: center; align-items: center; margin-top: 20px; } button { margin: 0 10px; padding: 5px 10px; border-radius: 5px; border: none; background-color: #007aff; color: #fff; cursor: pointer; } button:disabled { opacity: 0.5; cursor: not-allowed; } .page-num { font-size: 16px; font-weight: bold; margin: 0 10px; } </style> ``` 在这个示例中,我们定义了两个 props:currentPage 和 totalPages,分别表示当前页和总页数。组件中包含上一页、下一页按钮和页码信息。通过点击按钮来触发 page-change 事件,从而更新父组件中的 currentPage 值,实现翻页操作。 使用该组件时,只需在父组件中传入 currentPage 和 totalPages 值,并监听 page-change 事件即可: ```vue <template> <div> <div v-for="item in items">{{ item }}</div> <pagination :current-page="currentPage" :total-pages="totalPages" @page-change="handlePageChange" /> </div> </template> <script> import Pagination from './Pagination.vue'; export default { components: { Pagination }, data() { return { items: ['item1', 'item2', 'item3', 'item4', 'item5', 'item6', 'item7', 'item8', 'item9', 'item10'], currentPage: 1, totalPages: 2 } }, methods: { handlePageChange(page) { this.currentPage = page; } } } </script> ``` 这里只是一个简单的分页器示例,实际开发中还可以根据需求进行扩展,例如添加页码输入框、每页显示条数控制等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值