android 水平滑动分页指示器_Android ViewPager指示器

本文详细介绍了如何实现一个Android水平滑动分页指示器,包括构建布局、自定义控件、绘制UI、设计分页页面、实现滑动跟随、处理tab联动、设置点击事件和高亮文字。通过自定义ViewPagerIndicator控件,结合Fragment和ViewPager,实现指示器与页面内容的联动和滑动效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在很多常见的app中都有使用指示器,别的不说,简书app顶部就是一个指示器啦,那么指示器这种效果该怎么做呢?步骤有以下几点:

1、构建布局。你的指示器应该长什么样子,在界面中的什么位置,它应该有多少个页面(即有多少个tab选项卡),你给它设计的滑动跟随的东西是三角形还是一杠?

2、自定义控件。虽然不用自定义控件貌似也能做出指示器的效果,但这样你就需要对多个控件单独控制,一个tab的内容就是一个TextView,还有外层的LinearLayout等,你应该怎样来做控制?滑动跟随的效果你打算给谁绘制?这些都是一个个现实的问题。但是自定义控件不同,你想怎么搞就怎么搞,想绘制什么就绘制什么,关键你可以把以上提到的多个控件放到自定义控件中,作为一个控件来直接控制,你说这样的操作爽不爽。

3、画UI,指示器我们采用自定义控件来做,因此指示器即tab部分就用自定义控件了,然后是指示器的主体(即ViewPager)我们直接使用v4包中的ViewPager控件即可。

4、设计分页页面。这里说的分页页面就是一个个的Fragment,指示器来回切换不同的界面内容并不是一直在不断的切换Activity,而是在一个Activity中不断地换页,就好比:一个Activity就是一本书,每个Fragment都是一页一页的纸,不管你怎么翻页,都还是在这本书中。Fragment数量对应tab的数量,Fragment呈现的内容由你自己定义,你可以和Activity一样创建布局文件,在里面放你想展示的内容。

走完以上4步ViewPager应该可以动了。

5、滑动跟随的标志。这个东西完全是自己绘制的东西,app中最常见的就是三角形和一杠,其他的就自己去画吧。这个涉及到自定义绘制的内容。其实弄明白了顺序也不是很难。首先在构造函数中初始化画笔,在合适的地方创建Path类对象即路径,然后通过mPath.moveTo(x,y)将路径起点移动到(x,y)处,然后通过mPath..lineTo(a,b)把线直接从点(x,y)连到(a,b)处,绘制完路径之后在重写的dispatchDraw方法中调整一下画布然后通过drawPath方法绘制一下路径即可。

6、滑动跟随。滑动ViewPager页面让滑动跟随标志同步运动起来?这里面的原理其实很简单,我们之前不是在画布上绘制了滑动跟随标志吗?你只要对画布做平移看起来的效果不就是标志移动了吗。在你翻页的同时平移画布就能实现通过运动了。

7、指示器tab联动。当我们的tab如果超出一个界面所能显示的个数的时候(即你有10个tab,但一次只能显示3个tab),我们都知道每个tab都是自定义控件中的子控件TextView,如果你定了10个,那么默认效果是10一次显示在一个界面中。这样并不是我们所理想的效果。这时候又要说到绘制的问题了,这里的每个TextView的宽度我们在自定义控件类中改参数,用屏幕宽度来除以3(假设你一次只想显示3个tab),这样就定下来每个tab的宽度占屏幕的1 / 3了,超出部分自然不会显示。

8、点击tab切换page页。这个很简单,只要通过循环把所有的TextView加上一个点击事件即可。然后在点击时间的回调函数中调用ViewPager的setCurrentItem(position),这样在你电机的同时才会切换至相应的pager页面,否则只是普通的点击事件。

9、tab文字高亮。很简单。在此之前我们应该先获得所有的TextView并把他们的字体颜色全部覆盖掉,免得等下影响效果。然后我们点击的那个TextView由于我们是通过循环来设置点击事件的,因此我们可以通过循环变量 i 来确定当前点击的tab的索引,只需要在每次循环中使用final int j = i,然后在点击回调中使用 j 来做逻辑即可。拿到 j 后不就可以拿到对应的TextView吗通过getChildAt(j),然后设置字体就完事了。

以上就是ViewPager的基本使用步骤,接下来上代码。

布局文件

android:id="@+id/indicator"

android:layout_width="match_parent"

android:layout_height="45dp"

android:background="#000"

android:orientation="horizontal"

Alex:visible_tab_count="4"

>

MainActivity.java文件

//这里我们使用的是FragmentPagerAdapter,是提供提供来专门给ViewPager使用的

pagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {

@Override

public Fragment getItem(int position) {

//arrayList中存放的是一个个的Fragment对象

return arrayList.get(position);

}

@Override

public int getCount() {

return arrayList.size();

}

自定义tab文件,同时把viewpager控制放在一起

public class ViewPagerIndicator extends LinearLayout {

private Paint mPaint;

private Path mPath;

private int mTriangleWidth;

private int mTriangleHeight;

private static final float RADIO = 1/6F;

//定义三角形底边的最大宽度

private final int DIMENSION_TRIANGLE_MAX = (int)(getScreenWidth() / 3 * RADIO);

private int mInitTranslationX; //偏移位置

private int mTranslationX; //移动时的偏移

private int mTabVisibleCount;

private static final int COUNT_DEFAULT_TAB = 4;

public ViewPagerIndicator(Context context) {

this(context,null);

}

public ViewPagerIndicator(Context context,AttributeSet attrs) {

super(context, attrs);

mPaint = new Paint();

mPaint.setAntiAlias(true);

mPaint.setStyle(Paint.Style.FILL);

mPaint.setColor(Color.WHITE);

mPaint.setPathEffect(new CornerPathEffect(5)); //给三角形的边设置圆角,使得三角形不至于太尖锐

TypedArray typeArray = context.obtainStyledAttributes(attrs,R.styleable.ViewPagerIndicator);

//获得先前在自定义属性文件中定义的visible_tab_count属性,如果找不到则自动使用默认值来设置默认显示的选项卡数量

mTabVisibleCount = typeArray.getInt(R.styleable.ViewPagerIndicator_visible_tab_count,COUNT_DEFAULT_TAB);

if(mTabVisibleCount < 0){

mTabVisibleCount = COUNT_DEFAULT_TAB;

}

typeArray.recycle();

}

@Override

protected void dispatchDraw(Canvas canvas) {

canvas.save();

canvas.translate(mInitTranslationX + mTranslationX,getHeight());

canvas.drawPath(mPath,mPaint);

canvas.restore();

super.dispatchDraw(canvas);

}

/**

* 布局加载完毕调用的回调函数

*/

@Override

protected void onFinishInflate() {

super.onFinishInflate();

int cCount = getChildCount(); //得到当前控件的子控件数

if(cCount == 0){

return ;

}

for(int i = 0;i

View view = getChildAt(i); //得到当前控件的子控件对象

LinearLayout.LayoutParams lp= (LayoutParams)view.getLayoutParams();

lp.weight = 0;

/**

* 页面允许一次显示 mTabVisibleCount个tab

*/

lp.width = getScreenWidth() / mTabVisibleCount;

}

//给每个tab设置点击切换page的效果

setOnItemClickEvent();

}

/**

*

* @return 获得屏幕宽度

*/

public int getScreenWidth(){

WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);

DisplayMetrics dm = new DisplayMetrics();

wm.getDefaultDisplay().getMetrics(dm);

return dm.widthPixels;

}

/**

* tab跟随滑动效果,当用户滑动ViewPager的page或者点击tab切换page的时候调用

* @param position 该参数表示tab的索引,从0开始

* @param offset 该参数表示从当前tab滑动到下一个tab的偏移,如果用户按住慢慢滑动,这个值将慢慢变化,它是一个百分比数值,

* 如果你滑到item与item正中间则该值为0.5

*/

public void scroll(int position, float offset){

/**

* tab 的宽度

*/

int tabWidth = getWidth() / mTabVisibleCount;

/**

* 下面的公式拆开来看

* tabWidth * position + tabWidth * offset

*/

mTranslationX = (int)((position + offset) * tabWidth);

/**

* tab联动部分,假设一次是能显示3个tab,加上下面代码后1、2、3 中 1 被隐藏后界面显示效果为2、3、4

*/

if(position >= mTabVisibleCount - 2 && getChildCount() > mTabVisibleCount && offset > 0){

if(mTabVisibleCount != 1){

this.scrollTo((position - (mTabVisibleCount - 2)) * tabWidth + (int)(tabWidth * offset),0);

}else{

this.scrollTo((int)(tabWidth * position + tabWidth * offset),0);

}

}

/**

* 调用以下方法重绘,即让他去触发dispatchDraw函数,从而,使得画布移动,也就是我们所看到的滑动跟随啦

*/

invalidate();

}

/**

* 控件宽高发生变化回调该方法,第一次初始化将会调用该函数

* @param w 控件当前宽度

* @param h 控件当前高度

* @param oldw 控件发生变化之前的宽度

* @param oldh 控件发生变化之前的高度

*/

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

/**

* 定义三角形的宽度

* 下面的min函数是为了控制让三角形的宽度不要超过规定的默认值而做的判断

*/

mTriangleWidth = (int)( w / mTabVisibleCount * RADIO);

mTriangleWidth = Math.min(mTriangleWidth,DIMENSION_TRIANGLE_MAX);

/**

* 起始偏移,我们把跟随标志(下面都将设其为三角形)设计在tab的底部中央,这个就是水平偏移

*/

mInitTranslationX = w / mTabVisibleCount / 2 - mTriangleWidth / 2;

initTriangle();

}

//初始化三角形

public void initTriangle(){

mPath = new Path();

/**

* 定下此次绘制路径的起点

*/

mPath.moveTo(0,0);

/**

* 本次绘制路径的终点,从前一个点到本次路径的终点这一段距离是直线

*/

mPath.lineTo(mTriangleWidth,0);

mPath.lineTo(mTriangleWidth / 2,-(mTriangleWidth / 2 - 2));

/**

* 让路径的起始点和最后一个终止点连接闭合绘制的图形

*/

mPath.close();

}

/**

* 清除掉tab上所有的字体高亮效果

*/

public void resetTextViewColor(){

for(int i = 0;i

View view = getChildAt(i);

if(view instanceof TextView){

((TextView)view).setTextColor(Color.WHITE);

}

}

}

//高亮当前选择的tab的文本

public void highLightText(int position){

resetTextViewColor();

View view = getChildAt(position);

if(view instanceof TextView){

((TextView)view).setTextColor(Color.RED);

}

}

private ViewPager mViewPager;

public void setViewPager(ViewPager viewPager, int position){

this.mViewPager = viewPager;

viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

@Override

public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

//偏移计算:tabWidth * positionOffset + position * tabWidth

/**

* tabWidth : 一个item的宽度,item是平均分的,因此从一个item到下一个item的距离为一个item的宽度

* positionOffset:偏移量,范围在0~1,当指示器偏移到item与item的正中间的时候为0.5

* position:位置,从0开始,表示在此前面还有position个item

* tabWidth * positionOffset:表示滑动的偏移量

* position * tabWidth:表示从当前选项卡准备移动到下一个选项卡除了滑动偏移部分的之前的固定值

* 因为我们滑动每次都是从第一个位置开始滑动的,因为我们只绘制了一个三角形

*/

scroll(position,positionOffset);

}

@Override

public void onPageSelected(int position) {

highLightText(position);

}

@Override

public void onPageScrollStateChanged(int state) {

}

});

viewPager.setCurrentItem(position);

highLightText(position);

}

/**

* 给每个tab设置点击事件

*/

public void setOnItemClickEvent(){

int cCount = getChildCount();

for(int i = 0;i

final int j = i;

getChildAt(i).setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

mViewPager.setCurrentItem(j);

}

});

}

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值