Example sharing of the MIUI tab switching effect in Android app _android

Source: Internet
Author: User

1. Overview
ha, 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 thought how to implement such an indicator on your own, and how complex 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, look at the title, with MIUI related, So we're going to do a feature that's consistent with tabpageindicator, but it looks like the Miui 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 attribute should be customizable by the user, we publish a custom attribute, set by the user, and the width of each textview is screenwidth/mvisibletab.
Triangle Tabindicator Drawing: Whether it is to draw a triangle or underscore indicator, we must be in the tab's outer layout to draw, then we initialize a triangle, the final dispatchdraw, according to the position of the triangle, directly draw.
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 movement, so how do we get the distance to move? There's a pagechangelistener inside there's a onpagescrolled method that recalls Positionoffset and positionoffsetpixels, and 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 based on onpagescrolled provided Positionoffset, let our linearlayout 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:orientati on= "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" Zhy:item_count = "3" > </com.example.demo_zhy_mms_miui. viewpagerindicator> <android.support.v4.view.viewpager android:id= "@+id/id_vp" android:layout_width= " Match_parent "android:layout_height=" 0DP "android:layout_weight=" 1 "> </android.support.v4.view.viewpag 

 Er> </LinearLayout>

First of all we declare in the layout file, one is Viewpagerindicator, one is our 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<frag 
  Ment> (); 
  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 = Vpsimplef 
      Ragment.newinstance (data); 
    Mtabcontents.add (fragment); Madapter = new Fragmentpageradapter (Getsupportfragmentmanager ()) {@Override public int Getcou 
      NT () {return mtabcontents.size (); 
      @Override public Fragment getitem (int position) {return mtabcontents.get (position); 
  } 
    }; 
    private void Initview () {Mviewpager = (Viewpager) Findviewbyid (R.ID.ID_VP); 
  Mindicator = (viewpagerindicator) Findviewbyid (r.id.id_indicator); 

 } 
  
}

About the use of our viewpagerindicator, just two lines:

Set the caption on the tab
Mindicator.settabitemtitles (mdatas);
Sets the associated Viewpager
Mindicator.setviewpager (mviewpager,0);

Other code is initialized Viewpager God horse ~ ~ Visible, our control to write well 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 give the tabtitle of the list<string> size is also 3, is the effect of the effect of Figure 1 ~ ~ ~
When the item_count=4, and the tabtitle of the list<string> size greater than 4, is the effect of Figure 2 effect ~ ~ ~
In fact, we also support the direct writing of our tab in the layout, you can not use Mindicator.settabitemtitles (Mdatas), instead, you can define a few textview in the layout, fixed text, style of what ~ ~ In fact, other controls we also support ~ ~ ~
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 we wrote in the Values/attr.xml:

<?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 the Setpatheffect, is to draw the line of the connection, a bit rounded corner ~ ~

(3) Onfinishinflate and onsizechanged
Some of our initialization work will be done in these two methods ~ ~ Size-related, will be in the onsizechanged callback to set up ~

/** 
   * 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);
You can download the code at the end of the article, the Mindicator.settabitemtitles (Mdatas), this line of code comments to test ~ ~ But note that the definition of the tab and Viewpager the best number of pages consistent.
The simple code is to get the Childview and then display a reset width of getscreenwidth ()/Mtabvisiblecount, and then set the Click event.

/** 
   * 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 more simple ~ ~ is Mviewpager.setcurrentitem (j);
Look below onsizechanged.

/** * Initialize the width of the triangle */@Override protected void onsizechanged (int w, int h, in 
    T oldw, int oldh) {super.onsizechanged (W, H, OLDW, OLDH); 
    Mtrianglewidth = (int) (W/mtabvisiblecount * Radio_triangel);//1/6 of//width 
 
    Mtrianglewidth = Math.min (Dimension_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 to construct the triangle.
The default width of the bottom of our triangle is 1/6 of each tab width; there is 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 () with path to construct a triangle, this is very simple ~ ~
The Minittranslationx is also initialized, as it is initially displayed in the middle of the first tab.
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 ~ ~
As you can see, we move the canvas through canvas.translate to draw the indicator to the specified location ~ ~ Of course, remember save and restore.
See, we also have a mtranslationx here, this is dynamic change, the following will introduce ~ ~

The triangle draws completes, should arrive, follows Viewpager to move the ~ ~ Of course, here must bind Viewpager first, 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 posi 
      tion) {//Set font color highlighting Resettextviewcolor (); 
 
      Highlighttextview (position); 
      Callback if (Onpagechangelistener!= null) {onpagechangelistener.onpageselected (position); @Override public void onpagescrolled (int position, float positionoffset, int positionoffsetp 
 
      Ixels) {//rolling scroll (position, positionoffset); 
            Callback if (Onpagechangelistener!= null) {onpagechangelistener.onpagescrolled (position, 
      Positionoffset, Positionoffsetpixels); @Override public void onpagescrollstatechanged (int state) {//callback if (Onpagechan Gelistener!= null) {ONPAGECHangelistener.onpagescrollstatechanged (state); 
  } 
 
    } 
  }); 
  Set the current page Mviewpager.setcurrentitem (POS); 
Highlight Highlighttextview (POS); 

 }

Very simple code, we associate Viewpager later, immediately register Setonpagechangelistener, about the following movement of indicators, the core code is: onpagescrolled

Rolling
Scroll (position, positionoffset);

This line behind the introduction ~
Notice here, we are not to use the Setonpagechangelistener, 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 and the callback method is exactly the same as the original listener ~ ~
PS: Do not ask me, here used the Mviewpager.setonpagechangelistener I also want to listen to do, and I set the Mviewpager.setonpagechangelistener indicator why not 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 following finger scrolling, and container scrolling * * @param position * @param offset/Pub 
     LIC void Scroll (int position, float offset) {/** * <pre> * 0-1:position=0; 1-0:postion=0; * </pre>//changing offsets, invalidate Mtranslationx = GetWidth ()/Mtabvisiblecount * (position + offs 
 
    ET); 
 
    int tabwidth = Getscreenwidth ()/mtabvisiblecount; Container scrolling, when moving to the last countdown, start scrolling if (offset > 0 && position >= (mTabVisibleCount-2) && get ChildCount () > Mtabvisiblecount) {if (Mtabvisiblecount!= 1) {This.scrollto (Position-( 
      mTabVisibleCount-2)) * tabwidth + (int) (Tabwidth * offset), 0); else//is special handling for Count 1 o'clock {this.scrollto (position * tabwidth + (int) (Tabwidth * off 
      Set), 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 following scrolling above said, rely on Mtranslationx, and then through the canvas.translate 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 for 0,offset will 0.0~1.0 so change ~ Our offset is actually increased offset * each tab width ~
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); As a then, we also have the offset: (Position-(mTabVisibleCount-2)) * tabwidth; as it happens to be visible in the penultimate to the last,
So position-(mTabVisibleCount-2) is 0, the offset is (tabwidth * offset) ~ ~
When visible as 0, we need special treatment, which is our else~
Finally remember invalidate~~
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 OnCreate inside to set tab display content, as well as the Visible tab number, we guess, if in the layout and oncreate inside all wrote the quantity, which effective (oneself to experiment) ~ ~
Remember that if it is code control, Setvisibletabcount is invoked before settabitemtitles.

OK, basically finished ~ ~ ~
Interested, change the triangle to our underline indicator play a game ~ ~ Estimated to change a few lines of code can ~ ~

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.