• Android之实现ViewPagerIndicator


    PS:最近一直忙于学习任务,一直没有时间去写博客.今天周六,终于有时间了.

    学习任务:

    1.打造一个自己的ViewPagerIndicator

      最近被安排了一大堆的学习任务,感觉老板还是很好的,让我们在业余时间多提升自己的个人能力,就拿这个ViewPagerIndicator来说吧,当初自己没有什么好的实现方案,现在也就学了一发,看了一下Google上的实现方案,针对的情况比较的多,我这里就针对一种情况来说.大家想更深入的研究可以去Github上搜索一下Google工程师的实现方式,效果都很棒,有兴趣的可以全部研究一下.我看了一下鸿洋的实现方式,不过他做的是三角形的.我做的是线条状的,因此做了一些修改.


      原理还是比较简单的,下面是一个ViewPager,上面整体是一个ViewPagerIndicator.然后在最下面画一个线条当做指示符就可以了,当ViewPager在滑动的时候,我们只需要知道偏移的距离,然后重新绘制这条指示符,调用invalidate()函数重新绘制视图就可以了。

     首先说一下如何画才是关键,画出这个指示符其实就是画出一个实心的矩形,我们需要知道矩形的长度和宽度,以及在什么位置进行绘制,一般高度我们自己是可以人为指定的,我们需要多高就可以指定成多少单位个dp,最后转换成px就可以了,但是宽度呢?宽度其实 = 屏幕宽度 / 显示的ViewPager的数量,获取屏幕的宽度还是比较简单的,因为我们已经在xml文件中指定了这个ViewPagerIndicator的宽度,我们只需要测量一下就能够拿到他的具体宽度.

     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         mTop = getMeasuredHeight();
         int width = getMeasuredWidth();
         int height = mTop + DensityUtils.dip2px(context, mHeight);
         mWidth = width / mTabVisibleCount;
         setMeasuredDimension(width, height);
     }

      这样我们就能够获取到整个ViewPagerIndicator的高度和宽度,那么矩形指示符的 mTop(顶部起始位置) =  getMeasureHeight(); 因为指示符也是需要高度的,因此我们需要重新设置整个ViewPagerIndicator的高度,因此这里需要调用setMeasuredDimension(width,height)重新设置整个ViewPagerIndicator的高度和宽度。有了这个思路,我们就能够绘制出这个矩形的指示符。那么还有一个难题,就是如何设置显示的数量.总不能在自定义的时候写死吧.这样不是非常的灵活,并且在实际项目开发的时候,不同的页面,需要的指示符的数量也是不相同的.因此这里我们需要使用一种灵活的方式去设置指示符的显示数量.

     灵活的设置指示符的显示数量需要自定义视图属性,需要在attrs文件中进行声明.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <!--属性的名字以及对应的类型-->
        <attr name="item_count" format="integer"></attr>
         
        <!--声明自定义属性,与上面形成关联关系-->
        <declare-styleable name="ViewPagerIndicator">
            <attr name="item_count" />
        </declare-styleable>
    
    </resources>

      那么这东西如何使用呢?首先我们需要在xml文件中进行设置.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        <!--这里需要引入命名空间-->
        xmlns:rzx="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <!--修改显示数量
           rzx:item_count="我们想设置的数值",这里我设置了5个
           前面这个名字是命名空间,可以随便设置.  
         -->
       <com.example.totem.myviewpagerindicator.view.ViewPagerIndicator
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            rzx:item_count="4">
            
        </com.example.totem.myviewpagerindicator.view.ViewPagerIndicator>
        
    </LinearLayout>

      因为我们是自定义控件实现的,因此我们需要在我们自定义的ViewPagerIndicator中去获取我们声明的显示数量.然后去指定指示符的显示宽度.这样就可以根据自己定义的显示数量去指定指示符的宽度了.

    public ViewPagerIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        /**
         * 存放自定义视图属性的数组容器,需要在attrs文件夹中定义属性
         * 属性支持:
         * reference string color dimension boolean integer float fraction enum flag
         * */
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ViewPagerIndicator);
       /**
         * 获取数组容器中属性数量
         * */
        mTabVisibleCount = typedArray.getInt(R.styleable.ViewPagerIndicator_item_count, COUNT_DEFAULT_TAB);
        if (mTabVisibleCount <= 0) {
            mTabVisibleCount = COUNT_DEFAULT_TAB;
        }
        /**
         * 调用recycle()函数
         * */
        typedArray.recycle();
    }

      获取过程就需要使用TypedArray去进行获取,它是存放自定义视图的一个数组容器,支持的类型在上面已经声明了,我就不进行啰嗦了.obtainStyledAttributes()这个方法顾名思义,获取Styled中声明的属性,那么从哪里获取呢?从我们的attrs文件夹中获取,那么获取那个值呢?获取的就是我们声明的ViewPagerIndicator名字的属性值.因为我们已经指定了显示的数量,因此他就可以获取到我们指定的数值,如果获取不到,那么就会有一个默认值.就这么简单,这里recycle()函数是为了再次利用typedArray。不调用也没有什么问题.

     那么拿到了显示数量,以及整体宽度,我们就可以去绘制这个矩形指示符了.

    public Rect(int left, int top, int right, int bottom) {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }

      绘制矩形需要传递四个参数,top我们已经获取到了.起初的ViewPagerIndicator的高度位置.bottom就是最终的ViewPagerIndicator的高度,left的获取其实是比较重要的,其实他的位置也很容易获取.left = (position(当前位置) + offset(偏移量)) * mWidth(指示符的宽度). left1(第一个位置的指示符位置) = (0 + 0) * mWidth。偏移的过程中,offset是始终改变的.那么这个left也是随之变动的.然后不断的重新绘制.

    /**
        * 指示符滚动
        */
    public void scroll(int position, float offset) {
        mLeft = (int) ((position + offset) * mWidth);
        /**
         * 重新绘制视图
         * */
        invalidate();
    }

      那么谁给我们时刻传递这个偏移量是个关键.偏移是一个过程,因此如果希望指示符是移动过去的,需要时刻获取当前的偏移量,这个偏移量就得交给ViewPager了.我们需要把我们的ViewPagerIndicator与ViewPager进行绑定,这样就能能够时刻获取到偏移量了.这取决于ViewPager的方法.

     /**
       * 设置关联的ViewPager
       */
    public void setViewPager(ViewPager mViewPager, int pos) {
        this.mViewPager = mViewPager;
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                scroll(position, positionOffset);
            }
    
            @Override
            public void onPageSelected(int position) {
                resetTextViewColor();
                highLightTextView(position);
            }
    
            @Override
            public void onPageScrollStateChanged(int state) {
    
            }
        });
        mViewPager.setCurrentItem(pos);
        highLightTextView(pos);
    }

      ViewPager的滑动监听能够帮助我们时刻获取到偏移量.这里我们没有必要再对外暴露接口什么的了.如果还想做其他的操作,我们只需要封装方法.让其执行就可以了..剩下的就是一些琐碎的东西,比如说,改变字体的颜色,以及设置相关的点击事件等等.

    /**
         * 高亮文本
         */
        protected void highLightTextView(int position) {
            View view = getChildAt(position);
            if (view instanceof TextView) {
                ((TextView) view).setTextColor(COLOR_SELECT);
            }
        }
    
        /**
         * 重置文本颜色
         */
        private void resetTextViewColor() {
            for (int i = 0; i < getChildCount(); i++) {
                View view = getChildAt(i);
                if (view instanceof TextView) {
                    ((TextView) view).setTextColor(COLOR_NORMAL);
                }
            }
        }
    
        /**
         * 设置点击事件
         */
        public void setItemClickEvent() {
            int cCount = getChildCount();
            for (int i = 0; i < cCount; i++) {
                final int j = i;
                View view = getChildAt(i);
                view.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mViewPager.setCurrentItem(j);
                    }
                });
            }
        }

      核心的地方基本都说完了.小细节问题就是在设置高度的时候别忘了dp和px之间的转换,否则画出来的线条实际上是有高度偏差的.MainActivity就只需要做一些初始化操作,为ViewPager添加相关的Fragment就行了.

    package com.example.totem.myviewpagerindicator.activity;
    
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.support.v4.app.FragmentActivity;
    import android.support.v4.app.FragmentPagerAdapter;
    import android.support.v4.view.ViewPager;
    import android.view.Window;
    
    import com.example.totem.myviewpagerindicator.R;
    import com.example.totem.myviewpagerindicator.fragment.FinallyFragment;
    import com.example.totem.myviewpagerindicator.fragment.FirstFragment;
    import com.example.totem.myviewpagerindicator.fragment.FouthFragment;
    import com.example.totem.myviewpagerindicator.fragment.SecondFragment;
    import com.example.totem.myviewpagerindicator.fragment.ThirdFragment;
    import com.example.totem.myviewpagerindicator.view.ViewPagerIndicator;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    public class MainActivity extends FragmentActivity {
    
        private List<Fragment> mTabContents = new ArrayList<>();
        private FragmentPagerAdapter mAdapter;
        private ViewPager mViewPager;
        private List<String> mDatas = Arrays.asList("1", "2", "3", "4", "5");
        private ViewPagerIndicator mIndicator;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(R.layout.activity_main);
            initView();
            initData();
            //设置Tab上的标题
            mIndicator.setTabItemTitles(mDatas);
            mViewPager.setAdapter(mAdapter);
            //设置关联的ViewPager
            mIndicator.setViewPager(mViewPager, 0);
        }
    
        private void initView() {
            mViewPager = (ViewPager) findViewById(R.id.viewPager);
            mIndicator = (ViewPagerIndicator) findViewById(R.id.Indicator);
        }
    
        private void initData() {
    
            FirstFragment firstFragment = new FirstFragment();
            SecondFragment secondFragment = new SecondFragment();
            ThirdFragment thirdFragment = new ThirdFragment();
            FouthFragment fouthFragment = new FouthFragment();
            FinallyFragment finallyFragment = new FinallyFragment();
    
            mTabContents.add(firstFragment);
            mTabContents.add(secondFragment);
            mTabContents.add(thirdFragment);
            mTabContents.add(fouthFragment);
            mTabContents.add(finallyFragment);
    
            mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
    
    
                @Override
                public int getCount() {
                    return mTabContents.size();
                }
    
                @Override
                public Fragment getItem(int position) {
                    return mTabContents.get(position);
                }
            };
        }
    }

      最后放上一个源代码分享:http://pan.baidu.com/s/1i56onAX

      

     

  • 相关阅读:
    就这样吧
    搞了个1.0版本,名字和预定的一样改成OIFaQ了
    算了,总结一下教训
    我刚经历了人生中第一次版本回滚,目前感觉良好,请党和人民放心
    这有点爽的
    我决定出1.0的时候改名叫OIFaQ
    换个SSD把D盘弄飞了
    算是交代一下这三天的空白
    构建之法读书笔记03
    构建之法读书笔记02
  • 原文地址:https://www.cnblogs.com/RGogoing/p/5767788.html
Copyright © 2020-2023  润新知