1. Overview
Today to bring you a viewpagerindicator production, I believe that when you do tabindicator, most people have used tabpageindicator, and many well-known apps have used this open source indicator. Have you ever wondered how you can implement such an indicator yourself and how complicated the code will be?
Today, I will lead you to achieve such an indicator from scratch, of course, do not prepare exactly the same, do not innovate like, and then see the title, with MIUI related, so we are ready to do a feature and tabpageindicator consistent, but the appearance and miui the same as the tab ~ ~
First imitation miui relatively simple, we look at the effect of the picture:
However, MIUI in the number of all the tab in the basic maintenance of two to four, but, we may be more columns, assuming that we have more than 10 tab to do, the total can not be divided into 10 of the screen, which I 5.3 eyesight I can not accept ~ ~ So we need to do similar tabpageindicator characteristics, show a few, and then the rest, you can tab following Viewpager sliding linkage, the effect diagram is this:
Rub, look more for a while, is the linkage, the tab above is also to support the click of the ~
In fact, learn this, if you compromise with the following is underlined, simply modify a few lines of code on the O ~ whether you believe or not, anyway, I am a letter.
2, before the implementation of the analysis
For such an indicator, we first analyze how to make it.
Content area We basically do not have to consider,Viewpager+fragmentpageradapter can.
It's mainly the tab area at the top:
First, although it's a custom control, we just need to use a combination of the lines:
Selection of controls: outer layout I am ready to use LinearLayout
, set a direction level on the line, the internal title, the default, I decided to use TextView
.
Custom attributes: Because our visual tab properties should be customizable by the user, we publish a custom attribute, set by the user, each TextView
width that is ScreenWidth
/ mVisibleTab
.
Triangle TabIndicator
Drawing: Whether it is to draw a triangle or underscore indicator, we must be in the tab in the outer layout of the drawing, then we initialize the drawing of a triangle, in the end, dispatchDraw
according to the position of the triangle, directly drawn.
Position of the triangle indicator: the y-coordinate of the position is easier to compute, not to repeat here. It's mainly the x coordinates, because the x coordinates follow the ViewPager
move, so how do we get the distance to move? There's a PageChangeListener
method inside onPageScrolled
, this method callback positionOffset
and positionOffsetPixels
, we can follow this to control X's position.
LinearLayout
Linkage, the current tab if it is moved to the last of the Visible tab, we are still according to onPageScrolled
provide positionOffset
, let us Linearlayout
carry out scrollXTo
~ ~
Well, the analysis of all of the analysis ~ ~ Here need to explain, custom control sometimes combined with the effect of the implementation of the control is also a great bang ~ ~ since there is suitable, why do they go from scratch ~ ~
3, the use of the way
Before writing the code, or the first paste the use of the way, so that we first have a sense of understanding, and then based on this understanding, to explore the code in the implementation process ~ ~
1. layout file
<linearlayout xmlns:android= "http://schemas.android.com/apk/res/android" xmlns:tools= "http:// Schemas.android.com/tools "xmlns:zhy=" Http://schemas.android.com/apk/res/com.example.demo_zhy_mms_miui "Android: Layout_width= "Match_parent" android:layout_height= "match_parent" android:background= "#ffffffff" android:orient ation= "Vertical" > <com.example.demo_zhy_mms_miui. Viewpagerindicator android:id= "@+id/id_indicator" android:layout_width= "Match_parent" Android:layout_ height= "45DP" android:background= "@drawable/title_bar_bg_one_row" android:orientation= "horizontal" en Y:item_count= "3" > </com.example.demo_zhy_mms_miui. viewpagerindicator> <android.support.v4.view.viewpager android:id= "@+id/id_vp" Android:layout_wi Dth= "Match_parent" android:layout_height= "0DP" android:layout_weight= "1" > </android.support.v4.vi ew. Viewpager> </LinearLayout>
First we declare in the layout file, one is ViewPagerIndicator
, one is ours ViewPager
.
2, Mainactivity
Package COM.EXAMPLE.DEMO_ZHY_MMS_MIUI;
Import java.util.ArrayList;
Import Java.util.Arrays;
Import java.util.List;
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; public class Mainactivity extends Fragmentactivity {private list<fragment> mtabcontents = new ARRAYLIST<FRAGM
Ent> ();
Private Fragmentpageradapter Madapter;
Private Viewpager Mviewpager; Private list<string> Mdatas = arrays.aslist ("SMS 1", "SMS 2", "SMS 3", "SMS 4",//"SMS 5", "SMS 6", "SMS 7", "SMS 8", "SMS")
9 ");
Private list<string> Mdatas = arrays.aslist ("SMS", "collection", "recommended");
Private Viewpagerindicator Mindicator;
@Override protected void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate);
Requestwindowfeature (Window.feature_no_title); SetcontentvieW (r.layout.vp_indicator);
Initview ();
Initdatas ();
Set the caption on the tab mindicator.settabitemtitles (Mdatas);
Mviewpager.setadapter (Madapter);
Sets the associated Viewpager Mindicator.setviewpager (mviewpager,0); private void Initdatas () {for (String Data:mdatas) {Vpsimplefragment fragment =
Vpsimplefragment.newinstance (data);
Mtabcontents.add (fragment);
} madapter = new Fragmentpageradapter (Getsupportfragmentmanager ()) {@Override
public int GetCount () {return mtabcontents.size (); @Override public Fragment getitem (int position) {Retur
n Mtabcontents.get (position);
}
};
private void Initview () {Mviewpager = (Viewpager) Findviewbyid (R.ID.ID_VP); Mindicator = (viewpagerindicator) findViewbyid (R.id.id_indicator); }
}
About our ViewPagerIndicator
use, just two lines:
Set the caption on the tab
Mindicator.settabitemtitles (mdatas);
Sets the associated Viewpager
Mindicator.setviewpager (mviewpager,0);
Other code is the initialization of ViewPager
God horse ~ ~ Visible, our control is written after the use of extremely simple ~ ~
All right, everybody, look, there's a property in the layout file that sets the number of visible tabs: zhy:item_count=”3″
;
For example: When item_count=3
, and given TabTitle
the List<String>
size is also 3, is the effect of the effect of Figure 1 ~ ~ ~
When item_count=4
, and the given TabTitle
List<String>
size is greater than 4, is the effect of the effect of Figure 2 ~ ~ ~
In fact, we also support to write directly in the layout of our tab, you can not use mIndicator.setTabItemTitles(mDatas);
instead, you can define a few in the layout TextView
, fixed good text, style what the ~ ~ In fact, other controls we are supporting ~ ~ ~
Paste the Fragment Code ~
Package COM.EXAMPLE.DEMO_ZHY_MMS_MIUI;
Import Android.os.Bundle;
Import android.support.v4.app.Fragment;
Import android.view.Gravity;
Import Android.view.LayoutInflater;
Import Android.view.View;
Import Android.view.ViewGroup;
Import Android.widget.TextView;
public class Vpsimplefragment extends Fragment {public static final String bundle_title = "TITLE";
Private String Mtitle = "DefaultValue";
@Override public View Oncreateview (layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) {
Bundle arguments = getarguments ();
if (arguments!= null) {Mtitle = arguments.getstring (Bundle_title);
} TextView TV = new TextView (getactivity ());
Tv.settext (Mtitle);
Tv.setgravity (Gravity.center);
return TV;
public static vpsimplefragment newinstance (String title) {Bundle Bundle = new Bundle ();
Bundle.putstring (Bundle_title, TITLE); Vpsimplefragment fragment = new VpsimPlefragment ();
Fragment.setarguments (bundle);
return fragment; }
}
Well, read the use of the way, there is a little bit of excitement wood ~ ~
4, the realization of custom Viewpagerindicator
1, custom properties
In fact, can be extracted for custom properties a lot of ha ~ here we have written one, is the number of tab. You can completely put the indicator color, text color God horse Customizable properties all out ~ ~
Our control name is called:ViewPagerIndicator
So here's values/attr.xml
what we write:
<?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>
The definition is good, must use, how to use? Where do you use it? Let's not say it. The use of the above has been posted on the layout file ~ ~ Remember the namespace of the custom attribute to pay attention to ha ~ ~ ~
First look at what, certainly what member variables, and constructs inside what to do ~
2. Construction methods and member variables
public class Viewpagerindicator extends LinearLayout {/** * draw a triangle brush/private Paint mpaint;
/** * Path forms a triangle * * private path MPath;
/** * The width of the triangle * * private int mtrianglewidth;
/** * The height of the triangle * * private int mtriangleheight;
/** * The width of the triangle is a single tab 1/6 * * private static final float Radio_triangel = 1.0F/6;
/** * Triangle Maximum width/private final int dimension_triangel_width = (int) (Getscreenwidth ()/3 * Radio_triangel);
/** * Initial, the triangle indicator of the offset/private int minittranslationx;
/** * The offset of the finger sliding * * private float Mtranslationx;
/** * Default TAB number * * private static final int count_default_tab = 4;
/** * TAB Number * * Private int mtabvisiblecount = Count_default_tab;
/** * tab on the content/private list<string> mtabtitles;
/** * with the binding viewpager * * public viewpager Mviewpager; /** * Title Normal color * * private static final int color_Text_normal = 0X77FFFFFF;
/** * The color when the title is selected * * private static final int color_text_highlightcolor = 0xFFFFFFFF;
Public Viewpagerindicator {This (context, NULL);
Public Viewpagerindicator (context, AttributeSet attrs) {Super (context, attrs);
Gets the custom attribute, the number of tabs TypedArray a = Context.obtainstyledattributes (Attrs, r.styleable.viewpagerindicator);
Mtabvisiblecount = A.getint (R.styleable.viewpagerindicator_item_count, Count_default_tab);
if (Mtabvisiblecount < 0) Mtabvisiblecount = count_default_tab;
A.recycle ();
Initializes the brush mpaint = new Paint ();
Mpaint.setantialias (TRUE);
Mpaint.setcolor (Color.parsecolor ("#ffffffff"));
Mpaint.setstyle (Style.fill);
Mpaint.setpatheffect (New Cornerpatheffect (3)); }
}
It seems that a lot of member variables, in fact, mainly in a few categories:
The first 6 are related to the drawing of that triangle, and the brush determines the style (color, etc.) of the triangle, and path is used to construct the triangle (in fact, the closing of 3 lines), and then the width of the triangles.
The next two: all with Translation
, must be related to the position of the triangle ~
The rest is the tab content, the number of God horse ~ ~
Look at our construction method inside: Get the custom attribute, that is, the number of the Visible tab, initialize our brushes, here set setPathEffect
, is to draw the connection of the line, a bit rounded ~ ~
3, Onfinishinflate and onsizechanged
Some of our initialization work will be done in these two methods ~ ~ Size-related, will be set in the onSizeChanged
callback
/**
* Set some of the necessary properties of view in the layout; if Settabtitles is set, view in layout is invalid
*
/@Override protected void onfinishinflate ()
{
log.e ("TAG", "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;
Lp.width = Getscreenwidth ()/mtabvisiblecount;
VIEW.SETLAYOUTPARAMS (LP);
}
Set Click event
setitemclickevent ();
}
This is actually to get in the layout file directly to write a good tab ~ ~ If you write well here, you do not need to call mIndicator.setTabItemTitles(mDatas);
the ~ ~
You can download the code at the end of the article, mIndicator.setTabItemTitles(mDatas);
this line of code comments to test ~ ~ But note that the definition of the Tab
number of ViewPager
pages and the best consistent.
The code is simple to get ChildView
, and then the display resets a width for the getScreenWidth() / mTabVisibleCount;
next set of click events.
/**
* Set Click event
/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);}}
);
}
This is even simpler.mViewPager.setCurrentItem(j);
Look below onsizechanged.
/**
* Initialize the width of the triangle *
/@Override protected void onsizechanged (int w, int h, int oldw, int oldh)
{
Super.onsizechanged (W, H, OLDW, OLDH);
Mtrianglewidth = (int) (W/mtabvisiblecount * Radio_triangel);//1/6 of
//width
mtrianglewidth = math.min (dimens Ion_triangel_width, mtrianglewidth);
Initialize triangle
inittriangle ();
Initial offset
Minittranslationx = getwidth ()/MTABVISIBLECOUNT/2-mtrianglewidth/2;
}
/**
* Initialization triangle indicator
/private void Inittriangle ()
{
MPath = new Path ();
Mtriangleheight = (int) (MTRIANGLEWIDTH/2/MATH.SQRT (2));
Mpath.moveto (0, 0);
Mpath.lineto (mtrianglewidth, 0);
Mpath.lineto (MTRIANGLEWIDTH/2,-mtriangleheight);
Mpath.close ();
}
onSizeChanged
, we mainly determine the width of the triangle and Path
go to construct the triangle.
The default width of the bottom of our triangle is 1/6 of each Tab
width; Of course there's an upper limit. (int) (getScreenWidth() / 3 * RADIO_TRIANGEL);【RADIO_TRIANGEL = 1.0f / 6】
This really does not matter, mainly for screen adaptation, you can extract for custom attributes to allow users to set;
initTriangle()
Use Path
to construct a triangle, this is very simple ~ ~
This is also initialized mInitTranslationX
because the first tab is displayed in the middle position.
Triangle initialization is done, should not go to see where it is drawn ~ ~
4, Dispatchdraw
/**
* Draw indicator */
@Override
protected void Dispatchdraw (Canvas Canvas)
{
canvas.save ();
The brush translates to the correct position
canvas.translate (Minittranslationx + Mtranslationx, getheight () + 1);
Canvas.drawpath (MPath, mpaint);
Canvas.restore ();
Super.dispatchdraw (canvas);
}
Before we draw the child view, we first draw our triangle indicator ~ ~
We can see that by canvas.translate
moving the canvas, we draw the indicator to the specified position ~ ~ Of course, remember save
and restore
.
See, we also have a mTranslationX
, this is dynamic change, the following will introduce ~ ~
The triangle draws completes, should arrive, follows the Viewpager to move the ~ ~ Of course, here must first bind ViewPager
, otherwise how to follow
5, Setviewpager
Sets the associated Viewpager public void Setviewpager (Viewpager mviewpager, int pos) {this.mviewpager = Mviewpager; Mviewpager.setonpagechangelistener (New Onpagechangelistener () {@Override public void onpageselected (int
Position) {//Set font color highlighting Resettextviewcolor ();
Highlighttextview (position);
Callback if (Onpagechangelistener!= null) {onpagechangelistener.onpageselected (position); @Override public void onpagescrolled (int position, float positionoffset, int positio
Noffsetpixels) {//rolling scroll (position, positionoffset);
Callback if (Onpagechangelistener!= null) {onpagechangelistener.onpagescrolled (position,
Positionoffset, Positionoffsetpixels);
@Override public void onpagescrollstatechanged (int state) {//callback if (Onpagechangelistener!= null) {onpagechangelistener.onpagescrollstatechanged (state);
}
}
});
Set the current page Mviewpager.setcurrentitem (POS);
Highlight Highlighttextview (POS); }
Very simple code, we relate ViewPager
to later, register immediately setOnPageChangeListener
, about the following movement of indicators, the core code is: onPageScrolled
Rolling
Scroll (position, positionoffset); This line is introduced later.
Notice here, we are not the setOnPageChangeListener
use of it, but the user may also need to listen to this interface, to do something, then we need to solve the user, so we define a similar interface to the user:
/**
* External Viewpager Callback interface * *
@author zhy * */public
interface Pagechangelistener
{ Public
void onpagescrolled (int position, float positionoffset, int positionoffsetpixels);
public void onpageselected (int position);
public void onpagescrollstatechanged (int state);
}
External Viewpager Callback Interface
private Pagechangelistener onpagechangelistener;
The setting of the external Viewpager callback interface is public
void Setonpagechangelistener (Pagechangelistener pagechangelistener)
{
this.onpagechangelistener = Pagechangelistener;
}
If the user needs a callback, please use our mIndicator.setOnPageChangeListener
, callback method is exactly the same as the original listener
~ ~
PS: do not ask me, here used mViewPager.setOnPageChangeListener
I also want to monitor what to do, and I set the mViewPager.setOnPageChangeListener
indicator how not to move, please look carefully above
Of course, there is also a highlight text and reset text color code, in fact, is simply to change the current selection of the tab text color.
/**
* Highlight Text
*
* @param position
/protected void Highlighttextview (int position)
{
View view = Getchildat (position);
if (view instanceof TextView)
{(
TextView) view). SetTextColor (Color_text_highlightcolor);
}
/**
* Reset Text color *
/
private void Resettextviewcolor ()
{for
(int i = 0; i < Getchildcount (); i++)
{
View view = Getchildat (i);
if (view instanceof TextView)
{(
TextView) view). SetTextColor (Color_text_normal);}
}}
Next to scroll debut ~
6, scroll
/**
* Indicator follows finger scrolling, and container scrolling
*
* @param position
* @param offset
/public void scroll (int position, Float offset)
{
/**
* <pre>
* 0-1:position=0; 1-0:postion=0;
* </pre>
/
/changing offsets, invalidate
Mtranslationx = getwidth ()/Mtabvisiblecount * (position + offset);
int tabwidth = Getscreenwidth ()/mtabvisiblecount;
Container scrolling, when moving to the last countdown, start scrolling
if (offset > 0 && position >= (mTabVisibleCount-2) && Getchildcoun T () > Mtabvisiblecount)
{
if (mtabvisiblecount!= 1)
{
This.scrollto (position-( mTabVisibleCount-2)) * tabwidth + (int) (Tabwidth * offset), 0;
} else
//special handling for Count 1 o'clock
{
This.scrollto (position * tabwidth + (int) (Tabwidth * offset), 0);
}
invalidate ();
}
After reading, there is no one, the horizontal slot, on these lines of code on the implementation of the indicator followed by scrolling and our tab following scrolling ~ ~
Well, in fact the indicator follows the scrolling above said, dependent mTranslationX
, and then through canvas.translate
the implementation of ~ ~ that is, on a line to determine the current offset can be.
For example: from the No. 0 tab sliding to the 1th Tab:position
0, offset
will 0.0~1.0 so change ~ Our offset is actually increasing offset *
Tab
the width of each ~
Well, the container is rolling, in fact, the container scrolling x is offset * each tab width ~; But there is a premise that the current slide is the visible penultimate to the last, so we have a judgment:
position >= (mTabVisibleCount – 2) ;
So, we also have the :(position – (mTabVisibleCount – 2)) * tabWidth ;
second to last, as it happens to be visible at the time of the shift,
So position – (mTabVisibleCount – 2)为0
, the offset, which is(tabWidth * offset)
When visible as 0, we need special treatment, which is our else~
Finally rememberinvalidate
Well, the approach to this core is finished ~ ~ ~ the rest of the assorted ~ ~
7, the remaining methods
/**
* Set the number of visible tabs
*
* @param count
/public void Setvisibletabcount (int count)
{
This.mtabvisiblecount = count;
}
/**
* Set tab title Content optional, you can write their own in the layout file dead
*
* @param datas
/public void Settabitemtitles (list< string> datas)
{
//If the passed-in list has a value, remove the View
if (datas!= null && datas.size () > 0) set in the layout file
{
this.removeallviews ();
This.mtabtitles = datas;
for (String title:mtabtitles)
{
//Add View
AddView (Generatetextview (title));
}
Set item's Click event
setitemclickevent ();
}
In fact, you can in the onCreate
inside to set the content of the tab
display, as well as the number of visible, Tab
we guess, if the layout and onCreate
inside all wrote the quantity, which is effective (oneself to experiment) ~ ~
Remember if it was code control setVisibleTabCount
, setTabItemTitles
before the call.
Summarize
Interested, to change the triangle to our underline indicator play ~ ~ Estimated to change a few lines of code can ~ ~ above is the production of Android cool Viewpagerindicator all the content, I hope to develop Android can help.