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