美文网首页
Android ViewPager指示器

Android ViewPager指示器

作者: 千里同学 | 来源:发表于2018-10-18 23:36 被阅读0次

在很多常见的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的基本使用步骤,接下来上代码。

布局文件

<!--自定义控件,最后一个属性是自定义属性,用来设置界面中同时显示的tab个数-->
<com.example.administrator.viewpagerdemo.ViewPagerIndicator
        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<cCount;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<getChildCount();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<cCount;i++){
            final int j = i;
            getChildAt(i).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mViewPager.setCurrentItem(j);
                }
            });
        }
    }
}

相关文章

网友评论

      本文标题:Android ViewPager指示器

      本文链接:https://www.haomeiwen.com/subject/hykezftx.html