实现360手机助手TabHost的波纹效果

来源:互联网 发布:淘宝介入后对卖家影响 编辑:程序博客网 时间:2024/04/30 20:46

现在新版360手机助手的界面都做得挺漂亮的,在切换底部导航时的波纹效果也很好看,刚好最近看了个开源项目才了解到原来Drawable做动画效果也怎么好用,所以就仿照360实现了下带波纹的TabHost。源代码地址:https://github.com/Rukey7/XFragmentTabHost

先来看一下实现后的效果:


说明一下实现要点:

1. 因为我们项目之前用的是FragmentTabHost,所以我直接继承FragmentTabHost来实现动画效果更方便;

2. 波纹动画的实现其实是自定义带动画效果的Drawable,然后将Drawable设置为Tab菜单的背景;

3. 其它的就是一些Tab菜单切换的处理了。

一. 自定义波纹Drawable

自定义Drawable只要继承Drawable并实现以下4个方法,同时实现Animatable接口

public class RippleDrawable extends Drawable implements Animatable {    @Override    public void draw(Canvas canvas) {        // 绘图    }    @Override    public void setAlpha(int alpha) {        // 设置透明度    }    @Override    public void setColorFilter(ColorFilter colorFilter) {        // 设置颜色过滤    }    @Override    public int getOpacity() {        // 设置颜色格式        return PixelFormat.RGBA_8888;    }    @Override    public void start() {        // 启动动画    }    @Override    public void stop() {        // 停止动画    }    @Override    public boolean isRunning() {        // 判断动画是否运行        return false;    }}
这几个方法中最重要的就是draw()方法了,相信自定义过View的都知道我们图形就是在这里绘制,这里也一样,其它方法在这里影响不大,最后一个方法用来设置Drawable的颜色格式。要实现动画Drawable需要实现Animatable接口,并实现3个方法如下,其实不实现这个接口也能做动画效果,但还是实现比较好。

下面是整个波纹Drawable的实现代码:

/** * Created by long on 2016/6/27. * 波纹Drawable */public class RippleDrawable extends Drawable implements Animatable {    /**     * 3种模式:左边、中间和右边波纹     */    public static final int MODE_LEFT = 1;    public static final int MODE_MIDDLE = 2;    public static final int MODE_RIGHT = 3;    private int mMode = MODE_MIDDLE;    // 前景色和后景色画笔    private Paint mPaintFront;    private Paint mPaintBehind;    // 用来绘制扇形的矩形框    private RectF mRect;    // 目标View的宽高的一半    private int mHalfWidth;    private int mHalfHeight;    // 扩散半径    private int mRadius;    // 前景色和背景色的分割距离    private int mDivideSpace;    // 扩散满视图需要的距离,中点到斜角的距离    private int mFullSpace;    // 动画控制    private ValueAnimator mValueAnimator;    public RippleDrawable(int frontColor, int behindColor, int mode) {        mPaintFront = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaintFront.setColor(frontColor);        mPaintBehind = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaintBehind.setColor(behindColor);        mRect = new RectF();        mMode = mode;    }    @Override    public void draw(Canvas canvas) {        if (mRadius > mHalfWidth) {            int count = canvas.save();            canvas.drawCircle(mHalfWidth, mHalfHeight, mHalfWidth, mPaintBehind);            canvas.restoreToCount(count);            count = canvas.save();            canvas.drawCircle(mHalfWidth, mHalfHeight, mDivideSpace, mPaintFront);            canvas.restoreToCount(count);        } else if (mRadius > mDivideSpace) {            int count = canvas.save();            canvas.drawCircle(mHalfWidth, mHalfHeight, mRadius, mPaintBehind);            canvas.restoreToCount(count);            count = canvas.save();            canvas.drawCircle(mHalfWidth, mHalfHeight, mDivideSpace, mPaintFront);            canvas.restoreToCount(count);        } else {            canvas.drawCircle(mHalfWidth, mHalfHeight, mRadius, mPaintFront);        }        // 左右两边才进行扇形绘制        if (mMode != MODE_MIDDLE) {            mRect.left = mHalfWidth - mRadius;            mRect.right = mHalfWidth + mRadius;            mRect.top = mHalfHeight - mRadius;            mRect.bottom = mHalfHeight + mRadius;        }        if (mMode == MODE_LEFT) {            canvas.drawArc(mRect, 90, 180, true, mPaintFront);        } else if (mMode == MODE_RIGHT) {            canvas.drawArc(mRect, -90, 180, true, mPaintFront);        }    }    @Override    public void setAlpha(int alpha) {    }    @Override    public void setColorFilter(ColorFilter colorFilter) {    }    @Override    public int getOpacity() {        return PixelFormat.RGBA_8888;    }    @Override    protected void onBoundsChange(Rect bounds) {        super.onBoundsChange(bounds);        mHalfHeight = (bounds.bottom - bounds.top) / 2;        mHalfWidth = (bounds.right - bounds.left) / 2;        mDivideSpace = Math.max(mHalfHeight, mHalfWidth) * 3 / 4;        mFullSpace = (int) Math.sqrt(mHalfWidth * mHalfWidth + mHalfHeight * mHalfHeight);        // 属性动画        mValueAnimator = ValueAnimator.ofInt(0, mFullSpace);        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                mRadius = (int) animation.getAnimatedValue();                invalidateSelf();            }        });        mValueAnimator.setDuration(200);        start();    }    @Override    public void start() {        mValueAnimator.start();    }    @Override    public void stop() {        mValueAnimator.end();    }    @Override    public boolean isRunning() {        return mValueAnimator != null && mValueAnimator.isRunning();    }}
整体还是比较简单的,主要就是绘图那里需要绘制3个图形,一个前景色的圆形、一个后景色的圆形和左右两边的扇形。在绘制前需要计算前景色和后景色绘制的半径,中点都为Tab视图的中心。这里需要实现onBoundsChange(Rect bounds)方法,在这里可以获取到Tab菜单项的尺寸信息,这里的mDivideSpace是前景色圆形的半径,也就是前景和后景的分割距离,而后景色圆形半径为Tab项宽度的一半。最后就剩下左右两边需要填充Tab边角的扇形半径mFullSpace了,距离就是中心到Tab边角点的距离了。

当然了,要实现动画效果肯定不止这些,还有一个重要的ValueAnimator,通过它来控制波纹的扩散半径,用法还是很简单的,用过属性动画的应该都不陌生。这里面需要注意的是里面调用了一个方法invalidateSelf() ,Drawable是通过这个方法来进行重绘的,它会重新调用draw()方法来实现波纹效果。
二. 实现扩展的FragmentTabHost

要实现扩展的FragmentTabHost需要继承它并实现一个重要的方法setCurrentTab(int index),当FragmentTabHost在选择Tab菜单时会调用该方法,在这方法里我们可以得到当前选中的项和之前选中的项,并做动画处理。

在实现FragmentTabHost之前,我们的Tab菜单布局生成也通过这里实现,并提供方法让外面调用,首先是菜单布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:id="@+id/tab_layout"    android:layout_width="match_parent"    android:layout_height="@dimen/tab_height"    android:minWidth="@dimen/tab_min_width"    android:paddingTop="@dimen/tab_padding_top_inactive"    android:paddingBottom="@dimen/tab_padding_bottom"    android:background="?selectableItemBackgroundBorderless">    <ImageView        android:id="@+id/tab_icon"        android:layout_width="@dimen/tab_icon"        android:layout_height="@dimen/tab_icon"        android:layout_gravity="center_horizontal"/>    <TextView        android:id="@+id/tab_title"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center_horizontal"        android:lines="1"        android:text="首页"        android:textColor="@color/colorInactive"        android:textSize="@dimen/tab_text_size_inactive"/></LinearLayout>
这个很简单,就是图标和标题,和正常使用没区别。然后是Tab菜单类:

/** * Created by long on 2016/4/15. * Tab项 */public class TabItem {    private String title;    private int imageRes;    public TabItem(String title, int imageRes) {        this.title = title;        this.imageRes = imageRes;    }    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }    public int getImageRes() {        return imageRes;    }    public void setImageRes(int imageRes) {        this.imageRes = imageRes;    }}
同样很简单,和布局文件对应一个图标和一个标题。

最后看下扩展FragmentTabHost的实现:

/** * Created by long on 2016/4/15. * 扩展TabHost */public class XFragmentTabHost extends FragmentTabHost {    private Context mContext;    private List<View> mTabViews;    private List<TabItem> mTabItems;    // 字体激活颜色    private int mTextActiveColor;    private int mTextInactiveColor;    // 字体激活大小    private float mTextActiveSize;    private float mTextInactiveSize;    // 视图激活对顶部的偏移    private int mViewActivePaddingTop;    private int mViewInactivePaddingTop;    // 波纹模式的前景颜色和后景颜色    private int mFrontColor;    private int mBehindColor;    // TabHost模式    private TabMode mTabMode;    public XFragmentTabHost(Context context) {        super(context);        _init(context);    }    public XFragmentTabHost(Context context, AttributeSet attrs) {        super(context, attrs);        _init(context);    }    private void _init(Context context) {        mTabViews = new ArrayList<>();        mTabItems = new ArrayList<>();        mContext = context;        mTextActiveColor = ContextCompat.getColor(mContext, R.color.colorActive);        mTextInactiveColor = ContextCompat.getColor(mContext, R.color.colorInactive);        mFrontColor = ContextCompat.getColor(mContext, R.color.colorFront);        mBehindColor = ContextCompat.getColor(mContext, R.color.colorBehind);        mTextActiveSize = getResources().getDimension(R.dimen.tab_text_size_active);        mTextInactiveSize = getResources().getDimension(R.dimen.tab_text_size_inactive);        mViewActivePaddingTop = (int) getResources().getDimension(R.dimen.tab_padding_top_active);        mViewInactivePaddingTop = (int) getResources().getDimension(R.dimen.tab_padding_top_inactive);        mTabMode = TabMode.MoveToTop;    }    /**     * 覆写父类接口,并在这里做些动画特效     * @param index 当前选中的Tab项     */    @Override    public void setCurrentTab(int index) {        // 获取之前选中的index        int lastIndex = getCurrentTab();        super.setCurrentTab(index);        // 选中不同的Tab项才做切换处理        if (lastIndex != index) {            _switchTab(lastIndex, index);        }    }    /**     * 添加TabItem     * @param item  TabItem     * @param fragClass fragment类名     * @param bundle 传给fragment的参数     */    public void addTabItem(TabItem item, Class<?> fragClass, Bundle bundle) {        mTabItems.add(item);        View view = _getIndicator(item);        mTabViews.add(view);        this.addTab(newTabSpec(item.getTitle()).setIndicator(view), fragClass, bundle);    }    /**     * 获取TabItem视图     * @param item TabItem     * @return     */    private View _getIndicator(TabItem item) {        View view = LayoutInflater.from(mContext).inflate(R.layout.tab_indicator, null);        ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon);        TextView title = (TextView) view.findViewById(R.id.tab_title);        imageView.setImageResource(item.getImageRes());        title.setText(item.getTitle());        title.setTextColor(mTextInactiveColor);        return view;    }    /**     * 切换Tab     * @param lastIndex 上一个选中索引     * @param nextIndex 下一个选中索引     */    private void _switchTab(int lastIndex, int nextIndex) {        for (int i = 0; i < mTabViews.size(); i++) {            if (i == lastIndex) {                _doRipple(i, false);            } else if (i == nextIndex) {                _doRipple(i, true);            }        }    }    /**     * 波纹处理     * @param index 索引     * @param isActivated 是否激活     */    private void _doRipple(int index, boolean isActivated) {        View view = mTabViews.get(index);        View tabView = view.findViewById(R.id.tab_layout);        TextView title = (TextView) view.findViewById(R.id.tab_title);        if (index == 0) {            _rippleDrawable(tabView, mFrontColor, mBehindColor, RippleDrawable.MODE_LEFT, isActivated);        } else if (index == (mTabViews.size() - 1)){            _rippleDrawable(tabView, mFrontColor, mBehindColor, RippleDrawable.MODE_RIGHT, isActivated);        } else {            _rippleDrawable(tabView, mFrontColor, mBehindColor, RippleDrawable.MODE_MIDDLE, isActivated);        }        if (isActivated) {            title.setTextColor(mTextActiveColor);        } else {            title.setTextColor(mTextInactiveColor);        }    }    /**     * 波纹动画     * @param view     * @param frontColor     * @param behindColor     * @param mode     * @param isActivated     */    @SuppressWarnings("deprecation")    private void _rippleDrawable(final View view, int frontColor, int behindColor, int mode, boolean isActivated) {        if (isActivated) {            RippleDrawable rippleDrawable = new RippleDrawable(frontColor, behindColor, mode);            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {                view.setBackground(rippleDrawable);            } else {                view.setBackgroundDrawable(rippleDrawable);            }        } else {            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {                view.setBackground(null);            } else {                view.setBackgroundDrawable(null);            }        }    }    /**     * 属性设置     * @return     */    public int getTextActiveColor() {        return mTextActiveColor;    }    public void setTextActiveColor(int textActiveColor) {        mTextActiveColor = textActiveColor;    }    public int getTextInactiveColor() {        return mTextInactiveColor;    }    public void setTextInactiveColor(int textInactiveColor) {        mTextInactiveColor = textInactiveColor;    }    public int getFrontColor() {        return mFrontColor;    }    public void setFrontColor(int frontColor) {        mFrontColor = frontColor;    }    public int getBehindColor() {        return mBehindColor;    }    public void setBehindColor(int behindColor) {        mBehindColor = behindColor;    }}
其实也不会复杂,就是在切换Tab菜单时,对选中菜单设置背景为RippleDrawable,对之前的菜单背景设置为空,就这么简单^ ^,使用的话大体和FragmentHost是基本一样的,就添加Tab菜单使用上面实现的方法addTabItem(TabItem item, Class<?> fragClass, Bundle bundle) 就行了,具体下载源代码查看。

这个TabHost实现还是不复杂,处理波纹效果外,源代码里还有一些其它动画效果,实现思路都一样,有兴趣也可以自己定制些更好看的动画效果~

0 0