您的位置:首页 > 其它

自定义View实现顶部Tab指示器

2016-04-11 20:48 375 查看
自定义View实现顶部Tab指示器,代码非常简单和简洁。另外Fragment切换时也能避免不必要重绘。

源代码:GitHub


效果图:




自定义View:

 
我们通过继承LinearLayout来实现指示器的效果。

自定义Indicator代码:

/**
* Created by gaolonglong on 2016/4/7.
*/
public class Indicator extends LinearLayout {

//Indicator的宽和高
private int mWidth;
private int mHeight = 5;
//Indicator的左、上坐标
private int mTop;
private int mLeft;
//自定义属性
private int mColor;
//LinearLayout的子View数
private int mChildCount;
private int pos;
private Paint mPaint;

public Indicator(Context context) {
this(context, null);
}

public Indicator(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public Indicator(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//setBackgroundColor(Color.TRANSPARENT);

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.Indicator, 0, 0);
mColor = array.getColor(R.styleable.Indicator_indicatorColor, Color.RED);
array.recycle();

init();
}

private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mColor);
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
//获取子View数量,也就是TextView数
mChildCount = getChildCount();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mTop = getMeasuredHeight();
//LinearLayout的宽和高
int width = getMeasuredWidth();
int height = mTop + mHeight;
mWidth = width / mChildCount;
//设置测量款和高
setMeasuredDimension(width,height);
}

/**
* 只要ViewPager一直在滑动就会调用onPageScrolled方法进而调用scroll方法
* 参数position是当前ViewPager位置
* 参数offset是当前ViewPager的偏移量,大小是0~1的,
* 而我们指示器的偏移正好是使用这个参数实现的
*/
public void scroll(int position,float offset){
mLeft = (int) ((position + offset) * mWidth);
pos = position;
//ViewPager不断偏移就不断重绘指示器位置
invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制指示器
Rect rect = new Rect(mLeft,mTop,mLeft + mWidth,mTop + mHeight);
canvas.drawRect(rect, mPaint);
//每次重绘都重置文字颜色
resetColor();
//并设置被选中的和当前ViewPager对应的文字颜色
TextView tv = (TextView) getChildAt(pos);
tv.setTextColor(Color.WHITE);
}

private void resetColor() {
for (int i = 0;i < mChildCount;i++){
TextView tv = (TextView) getChildAt(i);
tv.setTextColor(Color.parseColor("#D0D8DC"));
}
}
}


注释很清楚了,代码也很简单,不赘述了。


使用Indicator:

布局:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.gaolonglong.app.tabindicator.MainActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

<com.gaolonglong.app.tabindicator.Indicator
android:id="@+id/indicator"
app:indicatorColor="#FF0000"
android:paddingTop="13dp"
android:paddingBottom="13dp"
android:weightSum="3"
android:background="?attr/colorPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/home_tab"
android:text="首页"
android:textSize="18sp"
android:gravity="center"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content" />

<TextView
android:id="@+id/radio_tab"
android:text="电台"
android:textSize="18sp"
android:gravity="center"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content" />

<TextView
android:id="@+id/ranking_tab"
android:text="排行榜"
android:textSize="18sp"
android:gravity="center"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content" />

</com.gaolonglong.app.tabindicator.Indicator>

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_main" />

</android.support.design.widget.CoordinatorLayout>


把自定义的View放在AppBarLayout中而不放在content_main,是因为让ToolBar和指示器高度一致,不明白的话,试一下你就知道啥意思了。

代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private Indicator indicator;
private ViewPager viewPager;
private FragmentPagerAdapter mAdapter;
private List<Fragment> list;
private TextView homeTab,radioTab,rankingTab;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

initView();

initEvent();
}

private void initEvent() {
//Listener
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Log.e("666",position+"...."+positionOffset);
indicator.scroll(position,positionOffset);
}

@Override
public void onPageSelected(int position) {

}

@Override
public void onPageScrollStateChanged(int state) {

}
});
//Click
homeTab.setOnClickListener(this);
radioTab.setOnClickListener(this);
rankingTab.setOnClickListener(this);
}

private void initView() {
//View
indicator = (Indicator) findViewById(R.id.indicator);
viewPager = (ViewPager) findViewById(R.id.view_pager);
homeTab = (TextView) findViewById(R.id.home_tab);
radioTab = (TextView) findViewById(R.id.radio_tab);
rankingTab = (TextView) findViewById(R.id.ranking_tab);
//List
list = new ArrayList<Fragment>();
list.add(new HomeFragment());
list.add(new RadioFragment());
list.add(new RankingFragment());
//Adapter
mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return list.get(position);
}

@Override
public int getCount() {
return list.size();
}
};

viewPager.setAdapter(mAdapter);
}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.home_tab:
homeTab.setTextColor(Color.WHITE);
viewPager.setCurrentItem(0);
break;
case R.id.radio_tab:
homeTab.setTextColor(Color.WHITE);
viewPager.setCurrentItem(1);
break;
case R.id.ranking_tab:
homeTab.setTextColor(Color.WHITE);
viewPager.setCurrentItem(2);
break;
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}
}


这个比较简单,就是ViewPager结合Fragment,最关键的是这行代码:
indicator.scroll(position,positionOffset);


它的作用是把ViewPager的滑动和指示器的滑动结合在一起了。

值得一提的是,Fragment的这种写法可以避免布局过多的重绘。如果你已经知道的话,请闭嘴...........
public class HomeFragment extends Fragment {

private View rootView;

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (rootView == null){
Log.e("666","HomeFragment");
rootView = inflater.inflate(R.layout.home_fragment, container, false);
}
ViewGroup parent = (ViewGroup) rootView.getParent();
if (parent != null){
parent.removeView(rootView);
}
return rootView;
}
}


还是比较简单的。
当然,你并不需要重复造轮子,官方Design库中已经提供TabLayout+ViewPager实现顶部Tab指示器,绘制什么的都在XML属性定义,而且提供了很多形式的Tab类型,功能极其强大。

最后值得说明的是,这个很赞的小功能,我特么怎么能想的起来...............呀。这个是由一位大神在2014年奉献出来的大神,速度去膜拜。

好了,我该洗洗睡了。

狂戳:GitHub
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  TabLayout 指示器