zoukankan      html  css  js  c++  java
  • 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

      

     

  • 相关阅读:
    poj 3616 Milking Time
    poj 3176 Cow Bowling
    poj 2229 Sumsets
    poj 2385 Apple Catching
    poj 3280 Cheapest Palindrome
    hdu 1530 Maximum Clique
    hdu 1102 Constructing Roads
    codeforces 592B The Monster and the Squirrel
    CDOJ 1221 Ancient Go
    hdu 1151 Air Raid(二分图最小路径覆盖)
  • 原文地址:https://www.cnblogs.com/RGogoing/p/5767788.html
Copyright © 2011-2022 走看看