Android 仿微信底部渐变Tab

先来看一下效果图


Android 仿微信底部渐变Tab
这里写图片描述

除了第三个的发现Tab有所差别外,其他的基本还原了微信的底部Tab渐变效果

每个Tab都是一个自定义View,根据ImageView的tint属性来实现颜色渐变效果,tint属性的使用可以看我的上一篇文章

我将自定义View命名为ShadeView,包含四个自定义属性
意思分别为图标、背景色、底部文本、底部文本大小

    <declare-styleable name="ShadeView">
        <attr name="icon" format="reference" />
        <attr name="color" format="color" />
        <attr name="text" format="string" />
        <attr name="text_size" format="dimension" />
    </declare-styleable>

ShadeView的定义如下,主要是进行绘图操作,并向外提供改变透明度和图标的方法

public class ShadeView extends View {

    /**
     * 图标
     */
    private Bitmap iconBitmap;
    /**
     * 图标背景色
     */
    private int iconBackgroundColor;
    /**
     * 图标默认背景色
     */
    private final int DEFAULT_ICON_BACKGROUND_COLOR = 0x3CAF36;
    /**
     * 图标底部文本
     */
    private String text;
    /**
     * 图标底部文字默认大小(sp)
     */
    private final int DEFAULT_TEXT_SIZE = 12;
    /**
     * 图标底部文字默认颜色
     */
    private final int DEFAULT_TEXT_COLOR = 0x2B2B2B;
    /**
     * 图标绘制范围
     */
    private Rect iconRect;
    /**
     * 文字笔画
     */
    private Paint textPaint;
    /**
     * 文字范围
     */
    private Rect textBound;
    /**
     * 透明度(0.0-1.0)
     */
    private float mAlpha;

    private Bitmap mBitmap;

    public ShadeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //获取自定义属性值
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ShadeView);
        BitmapDrawable drawable = (BitmapDrawable) typedArray.getDrawable(R.styleable.ShadeView_icon);
        if (drawable != null) {
            iconBitmap = drawable.getBitmap();
        }
        iconBackgroundColor = typedArray.getColor(R.styleable.ShadeView_color, DEFAULT_ICON_BACKGROUND_COLOR);
        text = typedArray.getString(R.styleable.ShadeView_text);
        int textSize = (int) typedArray.getDimension(R.styleable.ShadeView_text_size,
                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE, getResources().getDisplayMetrics()));
        //资源回收
        typedArray.recycle();
        //初始化
        textBound = new Rect();
        textPaint = new Paint();
        textPaint.setTextSize(textSize);
        textPaint.setColor(DEFAULT_TEXT_COLOR);
        textPaint.setAntiAlias(true);
        textPaint.setDither(true);
        textPaint.getTextBounds(text, 0, text.length(), textBound);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //因为图标是正方形且需要居中显示的,所以View的大小去掉padding和文字所占空间后,
        //剩余的空间的宽和高的最小值才是图标的边长
        int bitmapSide = Math.min(getMeasuredWidth() - getPaddingLeft()
                - getPaddingRight(), getMeasuredHeight() - getPaddingTop()
                - getPaddingBottom() - textBound.height());
        int left = getMeasuredWidth() / 2 - bitmapSide / 2;
        int top = (getMeasuredHeight() - textBound.height()) / 2 - bitmapSide / 2;
        //获取图标的绘制范围
        iconRect = new Rect(left, top, left + bitmapSide, top + bitmapSide);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //进一取整
        int alpha = (int) Math.ceil((255 * mAlpha));
        //绘制原图标
        canvas.drawBitmap(iconBitmap, null, iconRect, null);
        setupTargetBitmap(alpha);
        drawSourceText(canvas, alpha);
        drawTargetText(canvas, alpha);
        canvas.drawBitmap(mBitmap, 0, 0, null);
    }

    /**
     * 在mBitmap上绘制以iconBackgroundColor颜色为Dst,DST_IN模式下的图标
     *
     * @param alpha Src颜色的透明度
     */
    private void setupTargetBitmap(int alpha) {
        mBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(mBitmap);
        Paint paint = new Paint();
        paint.setColor(iconBackgroundColor);
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setAlpha(alpha);
        //在图标背后先绘制一层iconBackgroundColor颜色的背景
        canvas.drawRect(iconRect, paint);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint.setAlpha(255);
        //在mBitmap上绘制以iconBackgroundColor颜色为Dst,DST_IN模式下的图标
        canvas.drawBitmap(iconBitmap, null, iconRect, paint);
    }

    /**
     * 绘制默认状态下的字体
     *
     * @param canvas Canvas
     * @param alpha  字体颜色透明度
     */
    private void drawSourceText(Canvas canvas, int alpha) {
        textPaint.setColor(DEFAULT_TEXT_COLOR);
        textPaint.setAlpha(255 - alpha);
        canvas.drawText(text, iconRect.left + iconRect.width() / 2 - textBound.width() / 2,
                iconRect.bottom + textBound.height(), textPaint);
    }

    /**
     * 绘制滑动到该标签时的字体
     *
     * @param canvas Canvas
     * @param alpha  字体颜色透明度
     */
    private void drawTargetText(Canvas canvas, int alpha) {
        textPaint.setColor(iconBackgroundColor);
        textPaint.setAlpha(alpha);
        canvas.drawText(text, iconRect.left + iconRect.width() / 2 - textBound.width() / 2,
                iconRect.bottom + textBound.height(), textPaint);
    }

    /**
     * 设置图标透明度并重绘
     *
     * @param alpha 透明度
     */
    public void setIconAlpha(float alpha) {
        if (mAlpha != alpha) {
            this.mAlpha = alpha;
            invalidateView();
        }
    }

    public void setIconBitmap(Context context, int resourceID) {
        BitmapDrawable bitmapDrawable = (BitmapDrawable) ContextCompat.getDrawable(context, resourceID);
        if (!bitmapDrawable.getBitmap().equals(iconBitmap)) {
            iconBitmap = bitmapDrawable.getBitmap();
            invalidateView();
        }
    }

    /**
     * 判断当前是否为UI线程,是则直接重绘,否则调用postInvalidate()利用Handler来重绘
     */
    private void invalidateView() {
        if (Looper.getMainLooper() == Looper.myLooper()) {
            invalidate();
        } else {
            postInvalidate();
        }
    }

    private static final String STATE_INSTANCE = "STATE_INSTANCE";

    private static final String STATE_ALPHA = "STATE_ALPHA";

    /**
     * 保存状态
     *
     * @return Parcelable
     */
    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable(STATE_INSTANCE, super.onSaveInstanceState());
        bundle.putFloat(STATE_ALPHA, mAlpha);
        return bundle;
    }

    /**
     * 恢复状态
     *
     * @param parcelable Parcelable
     */
    @Override
    protected void onRestoreInstanceState(Parcelable parcelable) {
        if (parcelable instanceof Bundle) {
            Bundle bundle = (Bundle) parcelable;
            mAlpha = bundle.getFloat(STATE_ALPHA);
            super.onRestoreInstanceState(bundle.getParcelable(STATE_INSTANCE));
        } else {
            super.onRestoreInstanceState(parcelable);
        }
    }

}

然后在布局文件中声明使用,这里不需要每个自定义属性都使用到,因为我也提供了默认值

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v4.view.ViewPager
        android:id="@+id/id_viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@drawable/tab_background"
        android:orientation="horizontal"
        android:paddingBottom="3dp"
        android:paddingTop="1dp">

        <com.example.zy.myapplication.ShadeView
            android:id="@+id/id_indicator_one"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="5dp"
            app:icon="@drawable/weixin"
            app:text="微信" />

        <com.example.zy.myapplication.ShadeView
            android:id="@+id/id_indicator_two"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="5dp"
            app:icon="@drawable/address_book"
            app:text="通讯录" />

        <com.example.zy.myapplication.ShadeView
            android:id="@+id/id_indicator_three"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="5dp"
            app:icon="@drawable/discover"
            app:text="发现" />

        <com.example.zy.myapplication.ShadeView
            android:id="@+id/id_indicator_four"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:padding="5dp"
            app:icon="@drawable/me"
            app:text="我" />

    </LinearLayout>

</LinearLayout>

因为主界面是ViewPager,这里就需要一个Fragment子类

public class TabFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        String mTitle = "微信";
        if (getArguments() != null) {
            mTitle = getArguments().getString("Title", "微信");
        }
        TextView textView = new TextView(getActivity());
        textView.setTextSize(25);
        textView.setGravity(Gravity.CENTER);
        textView.setText(mTitle);
        return textView;
    }

}

MainActivity代码如下,重点是对viewPager进行滑动监听,根据滑动偏移量来动态改变透明度alpha,从而实现颜色渐变效果

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener, View.OnClickListener {

    private List<TabFragment> tabFragments;

    private List<ShadeView> tabIndicators;

    private ViewPager viewPager;

    private FragmentPagerAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        viewPager = (ViewPager) findViewById(R.id.id_viewpager);
        viewPager.setAdapter(adapter);
        viewPager.addOnPageChangeListener(this);
    }

    private void initData() {
        tabFragments = new ArrayList<>();
        tabIndicators = new ArrayList<>();
        String[] titles = new String[]{"微信", "通讯录", "发现", "我"};
        for (String title : titles) {
            TabFragment tabFragment = new TabFragment();
            Bundle bundle = new Bundle();
            bundle.putString("Title", title);
            tabFragment.setArguments(bundle);
            tabFragments.add(tabFragment);
        }
        adapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public int getCount() {
                return tabFragments.size();
            }

            @Override
            public Fragment getItem(int arg0) {
                return tabFragments.get(arg0);
            }
        };
        initTabIndicator();
    }

    private void initTabIndicator() {
        ShadeView one = (ShadeView) findViewById(R.id.id_indicator_one);
        ShadeView two = (ShadeView) findViewById(R.id.id_indicator_two);
        ShadeView three = (ShadeView) findViewById(R.id.id_indicator_three);
        ShadeView four = (ShadeView) findViewById(R.id.id_indicator_four);
        tabIndicators.add(one);
        tabIndicators.add(two);
        tabIndicators.add(three);
        tabIndicators.add(four);
        one.setOnClickListener(this);
        two.setOnClickListener(this);
        three.setOnClickListener(this);
        four.setOnClickListener(this);
        one.setIconAlpha(1.0f);
    }

    @Override
    public void onClick(View v) {
        resetTabsStatus();
        switch (v.getId()) {
            case R.id.id_indicator_one:
                tabIndicators.get(0).setIconAlpha(1.0f);
                viewPager.setCurrentItem(0, false);
                break;
            case R.id.id_indicator_two:
                tabIndicators.get(1).setIconAlpha(1.0f);
                viewPager.setCurrentItem(1, false);
                break;
            case R.id.id_indicator_three:
                tabIndicators.get(2).setIconAlpha(1.0f);
                viewPager.setCurrentItem(2, false);
                break;
            case R.id.id_indicator_four:
                tabIndicators.get(3).setIconAlpha(1.0f);
                viewPager.setCurrentItem(3, false);
                break;
        }
    }

    /**
     * 重置Tab状态
     */
    private void resetTabsStatus() {
        for (int i = 0; i < tabIndicators.size(); i++) {
            tabIndicators.get(i).setIconAlpha(0);
        }
    }

    /**
     * 如果是直接点击图标来跳转页面的话,position值为0到3,positionOffset一直为0.0
     * 如果是通过滑动来跳转页面的话
     * 假如是从第一页滑动到第二页
     * 在这个过程中,positionOffset从接近0逐渐增大到接近1.0,滑动完成后又恢复到0.0,而position只有在滑动完成后才从0变为1
     * 假如是从第二页滑动到第一页
     * 在这个过程中,positionOffset从接近1.0逐渐减小到0.0,而position一直是0
     *
     * @param position             当前页面索引
     * @param positionOffset       偏移量
     * @param positionOffsetPixels 偏移量
     */
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        Log.e("TAG", "position==" + position);
        Log.e("TAG", "positionOffset==" + positionOffset);
        Log.e("TAG", "positionOffsetPixels==" + positionOffsetPixels);
        if (positionOffset > 0) {
            ShadeView leftTab = tabIndicators.get(position);
            ShadeView rightTab = tabIndicators.get(position + 1);
            leftTab.setIconAlpha(1 - positionOffset);
            rightTab.setIconAlpha(positionOffset);
        }
    }

    @Override
    public void onPageSelected(int position) {
        if (position == 2) {
            tabIndicators.get(position).setIconBitmap(this, R.drawable.discover_green);
        } else {
            tabIndicators.get(2).setIconBitmap(this, R.drawable.discover);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }

}

这里也提供源代码下载:
****:仿微信底部渐变Tab
GitHub:仿微信底部渐变Tab