Create the most easy-to-use Tab Indicator in history-Indicator and tabindicator
If you do not know what a Tab indicator is, I believe that after you have read Netease news, you will surely be enlightened :'
It is the red bar under the navigation bar. Today we will also implement this effect... Our code is very simple and easy to use. For preliminary statistics, such indicator can be used for a line of code.
Well, I used a LinearLayout before the project added this effect. There are multiple item codes and tabs in it. How can I add Indicator? I chose to override LinearLayout.
Let's talk about our ideas first. In fact, the idea is also very simple, that is, draw a small rectangle under our navigation, and constantly change the distance of this rectangle to the left.
The idea is so simple. With the idea, the next step is implementation. Let's look at the Code:
Public class Indicator extends LinearLayout {private Paint mPaint; // paintprivate int mTop of the Indicator; // topprivate int mLeft of the Indicator; // leftprivate int mWidth of the Indicator; // widthprivate int mHeight = 5; // The height of the indicator, fixed with private int mColor; // the color of the indicator private int mChildCount; // The number of sub-items, used to calculate the width of the Indicator public Indicator (Context context, AttributeSet attrs) {super (context, attrs); setBackgroundColor (Color. TRANSPARENT );/ /The background must be set; otherwise, onDraw will not execute // the color of the custom attribute indicator TypedArray ta = context. obtainStyledAttributes (attrs, R. styleable. indicator, 0, 0); mColor = ta. getColor (R. styleable. indicator_color, 0X0000FF); ta. recycle (); // initialize paintmPaint = new Paint (); mPaint. setColor (mColor); mPaint. setAntiAlias (true) ;}@ Overrideprotected void onFinishInflate () {super. onFinishInflate (); mChildCount = getChildCount (); // obtain the number of sub-items} @ Overridepro Tected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {super. onMeasure (widthMeasureSpec, heightMeasureSpec); mTop = getMeasuredHeight (); // The measured height is the top position of the indicator int width = getMeasuredWidth (); // obtain the total width of the measurement. int height = getMeasuredHeight () + mTop + mHeight; // redefine the measurement height. mWidth = width/mChildCount; // The width of the indicator is the total width/number of items setMeasuredDimension (width, height);}/*** the indicator scrolls * @ param position current position * @ Param offset: 0 ~ 1 */public void scroll (int position, float offset) {mLeft = (int) (position + offset) * mWidth); invalidate ();} @ Overrideprotected void onDraw (Canvas canvas) {// enclose a rectangular Rect rect = new Rect (mLeft, mTop, mLeft + mWidth, mTop + mHeight); canvas. drawRect (rect, mPaint); // draw this rectangle super. onDraw (canvas );}}
Isn't it easy to add more than 60 lines of comments to the code? Next we will analyze the code.
Let's take a look at the constructor method.
Public Indicator (Context context, AttributeSet attrs) {super (context, attrs); setBackgroundColor (Color. TRANSPARENT); // The background must be set; otherwise, onDraw will not execute // the color of the custom attribute indicator TypedArray ta = context. obtainStyledAttributes (attrs, R. styleable. indicator, 0, 0); mColor = ta. getColor (R. styleable. indicator_color, 0X0000FF); ta. recycle (); // initialize paintmPaint = new Paint (); mPaint. setColor (mColor); mPaint. setAntiAlias (true );}
In the third line, You need to note that we set a background in the Custom LinearLayout, and note that the background must be set. Why? Because ViewGroup does not use the onDraw method by default, because ViewGroup does not need to be drawn and the Child item of ViewGroup needs to be drawn. Here we set the background color, viewGroup will use the onDraw method to draw its own background. Do we need onDraw? Of course, we need to draw an indicator in onDraw.
The following three lines of code are used to obtain the custom attributes, that is, the color of the indicator. Then, the painting of the indicator is initialized.
@ Overrideprotected void onFinishInflate () {super. onFinishInflate (); mChildCount = getChildCount (); // obtain the number of sub-items}
In the onFinishLayout method, the work we do is also very simple, that is, to obtain the number of sub-items, because we need to determine the width of the Indicator Based on the width of LinearLayout and the number of sub-items.
@ Overrideprotected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {super. onMeasure (widthMeasureSpec, heightMeasureSpec); mTop = getMeasuredHeight (); // The measured height is the top position of the indicator int width = getMeasuredWidth (); // obtain the total width of the measurement. int height = getMeasuredHeight () + mTop + mHeight; // redefine the measurement height. mWidth = width/mChildCount; // The indicator width is the total width/number of items setMeasuredDimension (width, height );}
Continue. In the onMeasure method, we first call the onMeasure of the parent class to let him call the default code for measurement. Next we get the measurement height through getMeasuredHeight, what is the use of this height for us? The top value of my indicator is the measured height. Go down to get the width of the measurement and redefine the height of the measurement, because we need to add the height of the indicator. Width/mChildCount we mentioned above, is to calculate the width of the indicator, finally we save the adjusted height Value, let it go to layout by default.
Next, let's take a look at the onDraw method.
@ Overrideprotected void onDraw (Canvas canvas) {// enclose a rectangular Rect rect = new Rect (mLeft, mTop, mLeft + mWidth, mTop + mHeight); canvas. drawRect (rect, mPaint); // draw this rectangle super. onDraw (canvas );}
In onDraw, what we need to do is easier. We just need to find a position and draw our indicator. We can see that we use a rectangle, the left value is constantly changing outside.
Finally, let's look at the custom scroll method.
/*** Indicator scroll * @ param position current position * @ param offset 0 ~ 1 */public void scroll (int position, float offset) {mLeft = (int) (position + offset) * mWidth); invalidate ();}
Two parameters are accepted, which correspond to ViewPager. onPageChangeListener. onPageScrolled (int position, float positionOffset, int positionOffsetPixels) the first two parameters. Here we try to hand over all our work to our Indicator, which makes it quite convenient to use it outside.
So what have we done? 1. Calculate the left value of the rectangle. 2. redraw.
Let's see how the left value is calculated. Add position and offset and multiply the width of the indicator. Why? Think about it. The value of position is the page number displayed by the current ViewPager, that is, the tab number currently. offset refers to the offset of a few percent from the current page, that is, the offset is a 0 ~ The value of 1. In this way (position + offset) * The mWidth result is also the left value of the rectangle we need.
Now, we have customized an Indicator. Let's try it:
First, in the layout file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:indicator="http://schemas.android.com/apk/res/org.loader.indicatortest" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity" > <org.loader.indicatortest.Indicator android:id="@+id/indicator" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="10dip" android:paddingTop="10dip" android:weightSum="4" indicator:color="#FFFF0000" > <TextView android:id="@+id/tab_one" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:text="TAB1" /> <TextView android:id="@+id/tab_two" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:text="TAB2" /> <TextView android:id="@+id/tab_three" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:text="TAB3" /> <TextView android:id="@+id/tab_four" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_horizontal" android:text="TAB4" /> </org.loader.indicatortest.Indicator> <android.support.v4.view.ViewPager android:id="@+id/container" android:layout_width="match_parent" android:layout_height="wrap_content"/></LinearLayout>
First, define a series of "tabs", and then a ViewPager. Let's take a look at the Activity.
public class MainActivity extends Activity implements OnClickListener {private Indicator mIndicator;private TextView mTabOne;private TextView mTabTwo;private TextView mTabThree;private TextView mTabFour;private ViewPager mContainer;private ArrayList<TextView> mViews = new ArrayList<TextView>(4);@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mIndicator = (Indicator) findViewById(R.id.indicator);mContainer = (ViewPager) findViewById(R.id.container);mTabOne = (TextView) findViewById(R.id.tab_one);mTabTwo = (TextView) findViewById(R.id.tab_two);mTabThree = (TextView) findViewById(R.id.tab_three);mTabFour = (TextView) findViewById(R.id.tab_four);mTabOne.setOnClickListener(this);mTabTwo.setOnClickListener(this);mTabThree.setOnClickListener(this);mTabFour.setOnClickListener(this);initViews();mContainer.setAdapter(new PagerAdapter() {@Overridepublic boolean isViewFromObject(View arg0, Object arg1) {return arg0 == arg1;}@Overridepublic int getCount() {return mViews.size();}@Overridepublic Object instantiateItem(ViewGroup container, int position) {View view = mViews.get(position);container.addView(view);return view;}@Overridepublic void destroyItem(ViewGroup container, int position,Object object) {container.removeView(mViews.get(position));}});mContainer.setOnPageChangeListener(new OnPageChangeListener() {@Overridepublic void onPageSelected(int position) {}@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {mIndicator.scroll(position, positionOffset);}@Overridepublic void onPageScrollStateChanged(int position) {}});}private void initViews() {for(int i=0;i<4;i++) {TextView tv = new TextView(this);tv.setText("hello android" + i);mViews.add(tv);}}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.tab_one:mContainer.setCurrentItem(0);break;case R.id.tab_two:mContainer.setCurrentItem(1);break;case R.id.tab_three:mContainer.setCurrentItem(2);break;case R.id.tab_four:mContainer.setCurrentItem(3);break;}}}
Well, the writing is messy. Let's look at it. In fact, the focus is on a line of code. At the beginning, I also said that we can use a line of code.
Check the line of code in onPageScrolled:
mIndicator.scroll(position, positionOffset);
OK, this line of code controls the slide of our indicator.
Finally, let's take a look:
Is it convenient and easy? In the future, we only need to copy the Indicator to the project, and a line of code can solve this cool tab effect.