Android知识Android开发Android技术知识

仿iOS的TabLayout

2017-04-26  本文已影响687人  三十二蝉

前言

因为项目需要,需要开发一个如下界面:

效果图.gif

android原生的TabLayout不能完成上图的需求。

实现思路

在TabLayout之上绘制一个View,达到背景的效果。这个View的位置通过ViewPager的OnPageChangeListener确定。

layout布局文件如下:

  <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="50dp">

            <com.test.view.ShapeIndicatorView
                android:id="@+id/custom_indicator"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingTop="4dp"
                app:mode="img_mode"
                app:strokecolor="@color/app_brand_color"
                app:roundraduis="40"
                app:img="@drawable/bg_indicator"
                />
            <android.support.design.widget.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:tabIndicatorColor="@color/app_brand_color"
                app:tabIndicatorHeight="1dp"
                app:tabSelectedTextColor="@color/app_brand_color"
                app:tabTextColor="@color/app_content_normal_color"
                />

        </FrameLayout>
        <View
            style="@style/basic_settings_item_divider"/>

        <android.support.v4.view.ViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>

ShapeIndicatorView是一个自定义的覆盖在TabLayout之上的背景View。源码如下:

public class ShapeIndicatorView extends View implements TabLayout.OnTabSelectedListener,ViewPager.OnPageChangeListener{
    ...
    private TabLayout mTabLayout;
    private ViewPager mViewPager;
    ...
      public void setupWithTabLayout(final TabLayout tableLayout) {
        mTabLayout = tableLayout;


        tableLayout.setSelectedTabIndicatorColor(Color.TRANSPARENT);
        tableLayout.setOnTabSelectedListener(this);


        tableLayout.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                if (mTabLayout.getScrollX() != getScrollX())
                    scrollTo(mTabLayout.getScrollX(), mTabLayout.getScrollY());
            }
        });

        ViewCompat.setElevation(this, ViewCompat.getElevation(mTabLayout));
        tableLayout.post(new Runnable() {
            @Override
            public void run() {
                if (mTabLayout.getTabCount() > 0)
                    onTabSelected(mTabLayout.getTabAt(0));

            }
        });

        //清除Tab background
        for (int tab = 0; tab < tableLayout.getTabCount(); tab++) {
            View tabView = getTabViewByPosition(tab);
            tabView.setBackgroundResource(0);
        }
    }

    public void setupWithViewPager(ViewPager viewPager) {
        mViewPager = viewPager;
        viewPager.addOnPageChangeListener(this);
    }
    ...
}

ShapeIndicatorView内部持有TabLayout和ViewPager的对象实例,在Activity中调用时需要调用setupWithTabLayout和setupWithViewPager函数。
实现TabLayout.OnTabSelectedListener和ViewPager.OnPageChangeListener接口,一个目的是为了保证TabLayout和ViewPager的状态保持一致,另一目的是为了在正确的时机绘制ShapeIndicatorView,具体代码如下:

 @Override
    public void onTabSelected(TabLayout.Tab tab) {
        if (mViewPager != null) {
            if (tab.getPosition() != mViewPager.getCurrentItem())
                mViewPager.setCurrentItem(tab.getPosition());
        } else {
            generateShape(tab.getPosition(), 0);
            invalidate();
        }
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {

    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {

    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        generateShape(position, positionOffset);
        invalidate();
    }

    @Override
    public void onPageSelected(int position) {
        if (mTabLayout.getSelectedTabPosition() != position)
            mTabLayout.getTabAt(position).select();
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }

generateShape函数是确定ShapeIndicatorView需要绘制的样式以及位置。代码如下:

   private void generateShape(int position, float positionOffset) {

        RectF range = new RectF();
        View tabView = getTabViewByPosition(position);
        if (tabView == null)
            return;

        int left, top, right, bottom;
        left = top = right = bottom = 0;
        if (positionOffset > 0.f && position < mTabLayout.getTabCount() - 1) {
            View nextTabView = getTabViewByPosition(position + 1);
            left += (int) (nextTabView.getLeft() * positionOffset + tabView.getLeft() * (1.f - positionOffset));
            right += (int) (nextTabView.getRight() * positionOffset + tabView.getRight() * (1.f - positionOffset));
            left += mShapeHorizontalSpace;
            right -= mShapeHorizontalSpace;
            top = tabView.getTop() + getPaddingTop();
            bottom = tabView.getBottom() - getPaddingBottom();
            range.set(left, top, right, bottom);
        } else {
            left = tabView.getLeft() + mShapeHorizontalSpace;
            right = tabView.getRight() - mShapeHorizontalSpace;
            top = tabView.getTop() + getPaddingTop();
            bottom = tabView.getBottom() - getPaddingBottom();
            range.set(left, top, right, bottom);
            if (range.isEmpty()) {
                return;
            }
        }
        switch (mode){
            case ImgMode:
                mImgRect = new Rect(left,top,right,bottom);
                break;
            case DrawMode:
                if(null ==mDrawPath)
                    mDrawPath = new Path();

                Rect tabsRect = getTabArea();
                tabsRect.right += range.width();
                tabsRect.left -= range.width();

                mDrawPath.reset();
                mDrawPath.moveTo(range.left,range.bottom);
                mDrawPath.lineTo(range.left,range.top);
                mDrawPath.lineTo(range.right,range.top);
                mDrawPath.lineTo(range.right,range.bottom);
                mDrawPath.lineTo(range.left,range.bottom);
                mDrawPath.close();
                break;
        }
    }

最后在ShapeIndicatorView的onDraw方法中drawShape方法,真正绘制:

    private void drawShape(Canvas canvas) {
        int savePos = canvas.save();
        switch (mode){
            case ImgMode:
                if(mImgRect == null || mImgRect.isEmpty())
                    return;
                canvas.drawBitmap(bitmap,null,mImgRect,mImgPaint);
                break;
            case DrawMode:
                if(mDrawPath == null || mDrawPath.isEmpty())
                    return;
                canvas.drawPath(mDrawPath,mDrawPaint);
                break;
        }

        canvas.restoreToCount(savePos);
    }

项目源代码地址:[https://github.com/PaulZJ/ShapeIndicatorViewDemo/tree/master]

上一篇 下一篇

猜你喜欢

热点阅读