Create Android's most practical ViewPager indicator control, androidviewpager
Why do I say it is the most practical ViewPager indicator control?
It has the following features:
1. Implemented through custom View, and the code is easy to understand;
2. Easy to use;
3. high versatility. This control can be used in most places involving ViewPager indicators;
4. Two types of indicator effects are implemented: traditional indicator and popular indicator (see for details)
I. First look
For traditional indicators:
Effects of pop-up indicators
Ii. Analysis
If you just want to implement this function, I believe everyone can implement it, and I will not discuss it here. Here I want to make it a control. In other words, it can be used directly in the future without modifying the code.
Controls, you can't do without Custom views. I also mentioned an article about custom views in the previous section. What do you need to know about Android custom views, but I think it is still very useful. If you are interested, you can read it and it is helpful to understand this article. No, I ran the question! Looking back at the two images, the entire View requires only two images. The only difficulty is how to calculate the position of the image. Since it is a general-purpose easy-to-use control, then you can no longer change the status of the indicator in the OnPagerChangerListener of ViewPager. Therefore, you have to pass ViewPager to this control. Here, the analysis is almost done;
Iii. coding implementation functions
For example, if you want to take a bite, you have to create a class first, and then let him inherit the View. The previous steps are similar to those of my previous blog, so it is not cumbersome to directly use the code.
Public class IndicatorView extends View implements ViewPager. onPageChangeListener {// indicator icon, which is a drawable, which contains two states: // selected and flying selected private Drawable mIndicator; // indicator Icon size, based on the width and height of the icon, select the larger private int mIndicatorSize; // The width of the entire indicator control is private int mWidth; /* the width of the padding icon plus space at home */private int mContextWidth; // The number of indicator icons, that is, the number of items in the current ViwPager private int mCount; /* interval between each indicator */private int mMargin;/* current view Is used to determine the current indicator selection */private int mSelectItem;/* the indicator is based on the offset of ViewPager sliding */private float mOffset; /* Whether the indicator is refreshed in real time */private boolean mSmooth;/* because the pageChangeListener of ViewPager is occupied, You need to define a * for other calls **/private ViewPager. onPageChangeListener mPageChangeListener; public IndicatorView (Context context) {this (context, null);} public IndicatorView (Context context, AttributeSet attrs) {this (c Ontext, attrs, 0);} public IndicatorView (Context context, AttributeSet attrs, int defStyleAttr) {super (context, attrs, defStyleAttr ); // use TypedArray to obtain the custom attribute TypedArray typedArray = getResources (). obtainAttributes (attrs, R. styleable. indicatorView); // get the number of Custom Attributes int N = typedArray. getIndexCount (); for (int I = 0; I <N; I ++) {int attr = typedArray. getIndex (I); switch (attr) {case R. styleable. in DicatorView_indicator_icon: // you can use custom attributes to obtain the metric mIndicator = typedArray. getDrawable (attr); break; case R. styleable. indicatorView_indicator_margin: float defaultMargin = (int) TypedValue. applyDimension (TypedValue. COMPLEX_UNIT_DIP, 5, getResources (). getDisplayMetrics (); mMargin = (int) typedArray. getDimension (attr, defaultMargin); break; case R. styleable. indicatorView_indicator_smooth: mSmooth = typedAr Ray. getBoolean (attr, false); break ;}// after use, remember to recycle typedArray. recycle (); initIndicator ();} private void initIndicator () {// obtain the indicator size value. In general, it's a square, and that's also the case. Your artist shook his hand and cut out a rectangle. // you don't have to worry about it. Here mIndicatorSize = Math. max (mIndicator. getIntrinsicWidth (), mIndicator. getIntrinsicHeight ();/* set the border of the indicator */mIndicator. setBounds (0, 0, mIndicator. getIntrinsicWidth (), mIndicator. getIntrinsicWidth ());}}
Note that the member variable Drawable mIndicator is a drawable file defined in the drawable folder. It contains the selected and selected two images.
Next is the measurement work.
/*** Measure the View size. This method is described in my previous blog. * @ param widthMeasureSpec * @ param heightMeasureSpec */@ Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension (measureWidth (widthMeasureSpec), measureHeight (heightMeasureSpec);}/*** measure the width, calculate the width of the current View * @ param widthMeasureSpec * @ return */private int measureWidth (int widthMeasureSpec) {int mode = MeasureSpec. getMode (widthMeasureSpec); int size = MeasureSpec. getSize (widthMeasureSpec); int width; int desired = getPaddingLeft () + getPaddingRight () + mIndicatorSize * mCount + mMargin * (mCount-1); mContextWidth = desired; if (mode = MeasureSpec. EXACTLY) {width = Math. max (desired, size);} else {if (mode = MeasureSpec. AT_MOST) {width = Math. min (desired, size) ;}else {width = desired ;}} mWidth = width; return width ;}private int measureHeight (int heightMeasureSpec) {int mode = MeasureSpec. getMode (heightMeasureSpec); int size = MeasureSpec. getSize (heightMeasureSpec); int height; if (mode = MeasureSpec. EXACTLY) {height = size;} else {int desired = getPaddingTop () + getPaddingBottom () + mIndicatorSize; if (mode = MeasureSpec. AT_MOST) {height = Math. min (desired, size) ;}else {height = desired ;}} return height ;}
After the measurement is complete, the View is drawn. Here we will focus on the onDraw () method. let's first talk about the general process,
First, draw all the selected indicators. Here, draw Drawable, so you need to use some methods in the Canvas to translate the Canvas, so that it can draw all Drawable in sequence, A special note here is Canvas. restore () method. This method is called to return to the original position and status after the painting is complete, but it must work with Canvas. save. Canvas. save () is to record the status of the current Canvas. So here, I think the name of this method should be changed to record (). Is it more in line with our understanding? This is purely a personal opinion. It is good to understand it. How to name it does not affect our work. The following is the onDraw () code, which is very detailed.
/*** Draw indicator * @ param canvas */@ Override protected void onDraw (Canvas canvas) {/** first, you must save the current status of the canvas, if the location Row Method * waits for the restore () to expire, and the canvas does not know the status to which it is restored *, the save and restore operations appear in pairs, which makes it easy to understand. **/Canvas. save ();/** here we start with calculating the position to be drawn. * if you do not understand it well, please follow what I said and pick up the paper and pen nearby, draw it on the paper and then * you can see it clearly. **/int left = mWidth/2-mContextWidth/2 + getPaddingLeft (); canvas. translate (left, getPaddingTop (); for (int I = 0; I <mCount; I ++) {/** here also needs to be explained, * Because drawable is a selector file *, we need to set its status, that is, state *, to obtain the corresponding image. * Obtain the unselected image. **/mIndicator. setState (EMPTY_STATE_SET);/* draw drawable */mIndicator. draw (canvas);/* draw an indicator and move it to the right once */canvas. translate (mIndicatorSize + mMargin, 0);}/** restore not all settings of the canvas. * According to google, that is, matrix/clip * can only be restored to the position where the last save method is called. **/Canvas. restore ();/* calculate the drawing position again */float leftDraw = (mIndicatorSize + mMargin) * (mSelectItem + mOffset);/** the calculation is complete and comes again, translation: Why do we need to translate twice? * For better understanding. **/Canvas. translate (left, getPaddingTop (); canvas. translate (leftDraw, 0);/** set the Drawable status to selected. * then, the obtained Drawable is the selected image. **/MIndicator. setState (SELECTED_STATE_SET);/* drawing again */mIndicator. draw (canvas );}
Now our controls are actually not implemented in one step, that is, when and where to update the View. It is analyzed at the beginning. This View needs to be passed in to ViewPager, and what is the purpose of passing in ViewPager, there are actually three,
1. Obtain the number of items of ViewPager to determine the number of indicators;
2. Get the item selected by the current ViewPager, which is also the item selected by the indicator;
3. Obtain OnPagerChangeListener to control when the View needs to be refreshed;
/*** This ViewPager must have set the Adapter first, * and the Adapter needs all the data. In the future, it cannot * modify data * @ param viewPager */public void setViewPager (ViewPager viewPager) {if (viewPager = null) {return;} PagerAdapter pagerAdapter = viewPager. getAdapter (); if (pagerAdapter = null) {throw new RuntimeException ("please refer to the instructions");} mCount = pagerAdapter. getCount (); viewPager. setOnPageChangeListener (this); mSelectItem = viewPager. getCurrentItem (); Invalidate ();} public void setOnPageChangeListener (ViewPager. onPageChangeListener mPageChangeListener) {this. mPageChangeListener = mPageChangeListener;} @ Override public void onPageScrolled (int position, float positionOffset, int positionOffsetPixels) {Log. v ("zgy", "======" + position + ", === offset" + positionOffset); if (mSmooth) {mSelectItem = position; mOffset = positionOffset; invalida Te ();} if (mPageChangeListener! = Null) {mPageChangeListener. Locate (position, positionOffset, positionOffsetPixels) ;}@override public void onPageSelected (int position) {mSelectItem = position; invalidate (); if (mPageChangeListener! = Null) {mPageChangeListener. onPageSelected (position) ;}@ Override public void onPageScrollStateChanged (int state) {if (mPageChangeListener! = Null) {mPageChangeListener. onPageScrollStateChanged (state );}}
This position also has a point to mention, that is, when mSmooth is true, it needs to be refreshed in real time, so it needs to be in onPageScrolled (int position, float positionOffset, int positionOffsetPixels) call invalidate () and save the offset to calculate the position of the indicator.
Well, the above is the entire implementation process of the indicator control;
Since it is a control, let's take a look at how xml references
<com.gyzhong.viewpagerindicator.IndicatorView android:id="@+id/id_indicator" android:layout_centerHorizontal="true" android:layout_alignParentBottom="true" android:layout_marginBottom="20dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="5dp" zgy:indicator_icon="@drawable/indicator_selector" zgy:indicator_margin="5dp"/>
Let's take a look at the reference in the code.
mIndicatorView = (IndicatorView) findViewById(R.id.id_indicator) ; mIndicatorView.setViewPager(mViewPager);
The code is concise and clear.
Iv. Summary
In general, it is not very difficult, the amount of code is very small, the main knowledge points used, 1. Custom Attributes, 2. How to measure the View, 2. How to use some methods in the Cavans; finally, if you find it useful, please check it out. Thank you!
Source code: Stamp me