自定义可滑动的tab选项卡,可切换选项卡样式(下划线,仿小米三角形,方形背景)

下载demo链接:http://download.csdn.net/detail/qq_20489601/9704356

由于在项目开发过程中经常使用到viewPager+fragment实现选项卡的切换功能,每次用的时候都要在activity中重新写一遍相同的代码,所以在空闲时间,我自己自定义了一个各种样式的可滑动的选项卡控件,在实际项目中使用非常方便,下面是此控件的主要代码:
1.要实现顶部选项卡的可滑动,我们可定义一个带反弹效果且可滑动的BounceScrollView,可以继承HorizontalScrollView来实现这个效果,下面是主要代码:
public class BounceScrollView extends HorizontalScrollView {
private View inner;
// 点击时x坐标
private float x;
// 矩形(这里只是个形式,只是用于判断是否需要动画
private Rect normal = new Rect();
// 是否开始计算
private boolean isCount = false;

public BounceScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

/***
 * 根据 XML 生成视图工作完成.该函数在生成视图的最后调用,在所有子视图添加完之后. 即使子类覆盖了 onFinishInflate
 * 方法,也应该调用父类的方法,使该方法得以执行.
 */
@Override
protected void onFinishInflate() {
    if (getChildCount() > 0) {
        inner = getChildAt(0);
    }
}   
//手动需要设置滚动位置
public void setScrolledTo(int position, float positionOffset) {
    this.scrollTo(position,(int) positionOffset);
}

 //监听touch
@Override
public boolean onTouchEvent(MotionEvent ev) {
    if (inner != null) {
        commOnTouchEvent(ev);
    }

    return super.onTouchEvent(ev);
}

//触摸事件
public void commOnTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    switch (action) {
    case MotionEvent.ACTION_DOWN:
        break;
    case MotionEvent.ACTION_UP:
        // 手指松开.
        if (isNeedAnimation()) {
            animation();
            isCount = false;
        }
        break;
    /***
     * 排除出第一次移动计算,因为第一次无法得知y坐标, 在MotionEvent.ACTION_DOWN中获取不到,
     * 因为此时是MyScrollView的touch事件传递到到了LIstView的孩子item上面.所以从第二次计算开始.
     * 然而我们也要进行初始化,就是第一次移动的时候让滑动距离归0. 之后记录准确了就正常执行.
     */
    case MotionEvent.ACTION_MOVE:
        final float preX = x;// 按下时的y坐标
        float nowX = ev.getX();// 时时y坐标
        int deltaX = (int) (preX - nowX);// 滑动距离
        if (!isCount) {
            deltaX = 0; // 在这里要归0.
        }

        x = nowX;
        // 当滚动到最上或者最下时就不会再滚动,这时移动布局
        if (isNeedMove()) {
            // 初始化头部矩形
            if (normal.isEmpty()) {
                // 保存正常的布局位置
                normal.set(inner.getLeft(), inner.getTop(),
                        inner.getRight(), inner.getBottom());
            }
            // 移动布局
            inner.layout(inner.getLeft() - deltaX / 4, inner.getTop(),
                    inner.getRight()  - deltaX / 4, inner.getBottom());

        }
        isCount = true;
        break;

    default:
        break;
    }
}

//回缩动画
public void animation() {
    // 开启移动动画
    TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(),
            normal.top);
    ta.setDuration(200);
    inner.startAnimation(ta);
    // 设置回到正常的布局位置
    inner.layout(normal.left, normal.top, normal.right, normal.bottom);
    normal.setEmpty();

}

// 是否需要开启动画
public boolean isNeedAnimation() {
    return !normal.isEmpty();
}

/***
 * 是否需要移动布局 inner.getMeasuredHeight():获取的是控件的总高度
 * getHeight():获取的是屏幕的高度
 */
public boolean isNeedMove() {
    int offset = inner.getMeasuredWidth() - getWidth();
    int scrollX = getScrollX();

    // 0是顶部反弹
    //是底部反弹加上    
    if (scrollX == 0  || scrollX == offset) {
        return true;
    }
    return false;
}

}

2.实现了顶部选项卡可滑动的功能之后,就可以开始自定义ViewPagerIndicator,为了方便开发过程中使用不同的样式,我定义了三种样式(1.下划线 2三角形 3.方形背景),且在实际开发过程中有可能会使用小红点来标记哪一项有新的内容,所以在此控件中也加入了小红点(可根据开发实际情况,自己设置是否显示),下面请看控件主要代码:

/**
*构造方法中初始化自定义属性
*/
public ViewPagerIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
/**
* 获得自定义属性
*/
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewPagerIndicator);
//tab的数量
mTabVisibleCount = a.getInt(R.styleable.ViewPagerIndicator_item_count,
2);

    //tab的风格
    mIndicatorStyle = a.getInt(
            R.styleable.ViewPagerIndicator_indicator_style, STYLE_TRIANGLE);

    //指示器的颜色
    mIndicatorColor = a.getColor(
            R.styleable.ViewPagerIndicator_indicator_color,
            D_INDICATOR_COLOR);

    //选中时标题的颜色
    mSelectTitleColor = a.getColor(R.styleable.ViewPagerIndicator_select_title_color,
            COLOR_TEXT_HIGHLIGHTCOLOR);

    if (mTabVisibleCount < 0)
        mTabVisibleCount = 2;
    a.recycle();

    // 初始化画笔
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setColor(mIndicatorColor);
    mPaint.setStyle(Style.FILL);
    mPaint.setPathEffect(new CornerPathEffect(3));
}

/**
* 绘制指示器
*/
@Override
protected void dispatchDraw(Canvas canvas) {
// 保存画布
canvas.save();
switch (mIndicatorStyle) {
case STYLE_LINE:
//下划线
canvas.translate(mTranslationX, getHeight() - mTriangleHeight);// 画笔平移到正确的位置
canvas.drawRect(mRectF, mPaint);
break;

    case STYLE_SQUARE:
        //方形背景
        canvas.translate(mTranslationX, 0);// 画笔平移到正确的位置
        canvas.drawRect(mRectF, mPaint);
        break;

    case STYLE_TRIANGLE:
        //三角形
        canvas.translate(mInitTranslationX + mTranslationX, getHeight() + 1);// 画笔平移到正确的位置
        canvas.drawPath(mPath, mPaint);
        break;
    }
    // 恢复画布
    canvas.restore();
    super.dispatchDraw(canvas);

}

/**
 * 初始化指示器的宽度和高度
 */
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    switch (mIndicatorStyle) {
    case STYLE_LINE:
        mTriangleWidth = w / mTabVisibleCount;
        mTriangleHeight = h / 10;
        mTranslationX = 0;
        mRectF = new Rect(0, 0, mTriangleWidth, mTriangleHeight);
        break;
    case STYLE_SQUARE:
        mTriangleWidth = w / mTabVisibleCount;
        mTriangleHeight = h;
        mTranslationX = 0;
        mRectF = new Rect(0, 0, mTriangleWidth, mTriangleHeight);
        break;
    case STYLE_TRIANGLE:
        mTriangleWidth = (int) (w / mTabVisibleCount * RADIO_TRIANGEL);// 1/6
        // width
        mTriangleWidth = Math.min(DIMENSION_TRIANGEL_WIDTH, mTriangleWidth);
        // 初始化三角形
        initTriangle();
        break;
    }
    // 初始时的偏移量
    mInitTranslationX = ScreenUtils.getInstance().getScreenWidth()
            / mTabVisibleCount / 2 - mTriangleWidth / 2;
}

/**
 * 设置可见的tab的数量
 * @param count
 */
public void setVisibleTabCount(int count) {
    this.mTabVisibleCount = count;
}

/**
 * 设置tab的标题内容 可选,生成textview加入布局,灵活处理
 * @param datas
 */
public void setTabItemTitles(List<String> datas) {
    // 如果传入的list有值,则移除布局文件中设置的view
    if (datas != null && datas.size() > 0) {
        this.removeAllViews();
        this.mTabTitles = datas;

        for (String title : mTabTitles) {
            // 添加view
            addView(generaLinearLayoutView(title, showItem));
        }
        // 设置item的click事件
        setItemClickEvent();
    }
}

/**
 * 设置要显示小红点的item 要在setTabItemTitles()方法之前调用 如果不需要标记小红点,此方法可以不调用
 * 
 * @param showItem
 */
public void setShowItemList(List<String> showItem) {
    this.showItem = showItem;
}

/**
 * 根据标题生成我们的TextView
 * 
 * @param text
 */
private TextView generateTextView(String text) {
    TextView tv = new TextView(getContext());
    LinearLayout.LayoutParams lp = new LayoutParams(
            LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    lp.gravity = Gravity.CENTER;
    tv.setTextColor(COLOR_TEXT_NORMAL);
    tv.setText(text);
    tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
    tv.setLayoutParams(lp);
    return tv;
}

/**
 * 构造小红点
 * 
 * @param title
 *            当前构造的标题
 * @param showItem
 *            要显示小红点的标题
 * @return
 */
private TextView generateCicleView(String title, List<String> showItem) {
    TextView tv_dot = new TextView(getContext());
    int width = DensityUtils.dip2px(getContext(), 10);
    LinearLayout.LayoutParams lp = new LayoutParams(width, width);
    lp.gravity = Gravity.CENTER_VERTICAL;
    tv_dot.setGravity(Gravity.CENTER_VERTICAL);
    tv_dot.setBackgroundResource(R.drawable.dot_orange);
    lp.bottomMargin = DensityUtils.dip2px(getContext(), 5);
    tv_dot.setLayoutParams(lp);
    tv_dot.setVisibility(View.GONE);
    if (showItem != null && showItem.size() > 0) {
        for (String string : showItem) {
            if (string.equals(title)) {
                tv_dot.setVisibility(View.VISIBLE);
                break;
            }
        }
    }
    return tv_dot;
}

/**
 * 构造选项卡item
 * 
 * @param title
 *            当前item的标题
 * @param showItem
 *            要显示小红点的标题
 * @return
 */
private View generaLinearLayoutView(String title, List<String> showItem) {
    LinearLayout ll_parent = new LinearLayout(getContext());
    LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
            LayoutParams.MATCH_PARENT);
    params.width = ScreenUtils.getInstance().getScreenWidth()
            / mTabVisibleCount;
    ll_parent.setGravity(Gravity.CENTER);

    ll_parent.setLayoutParams(params);
    ll_parent.addView(generateTextView(title));
    ll_parent.addView(generateCicleView(title, showItem));
    return ll_parent;
}

/**
 * 对外的ViewPager的回调接口
 */
public interface PageChangeListener {
    public void onPageScrolled(int position, float positionOffset,
            int positionOffsetPixels);

    public void onPageSelected(int position);

    public void onPageScrollStateChanged(int state);
}

// 对外的ViewPager的回调接口
private PageChangeListener onPageChangeListener;

/**
 * 对外的ViewPager的回调接口的设置
 * @param pageChangeListener
 */
public void setOnPageChangeListener(PageChangeListener pageChangeListener) {
    this.onPageChangeListener = pageChangeListener;
}

/**
 * 设置关联的ViewPager,以及传入 BounceScrollView,进行设置滚动
 * @param mViewPager
 * @param scrollView
 * @param pos
 */
public void setViewPager(ViewPager mViewPager,
        final BounceScrollView scrollView, int pos) {
    this.mViewPager = mViewPager;

    mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            // 重置字体颜色
            resetTextViewColor();
            highLightTextView(position);
            // 回调
            if (onPageChangeListener != null) {
                onPageChangeListener.onPageSelected(position);
            }
        }

        @Override
        public void onPageScrolled(int position, float positionOffset,
                int positionOffsetPixels) {
            // 滚动
            scroll(scrollView, position, positionOffset);
            // 回调
            if (onPageChangeListener != null) {
                onPageChangeListener.onPageScrolled(position,
                        positionOffset, positionOffsetPixels);
            }
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            // 回调
            if (onPageChangeListener != null) {
                onPageChangeListener.onPageScrollStateChanged(state);
            }

        }
    });
    // 设置当前页
    mViewPager.setCurrentItem(pos);
    // 高亮
    highLightTextView(pos);
}

/**
 * 高亮文本
 * @param position
 */
protected void highLightTextView(int position) {
    View view = getChildAt(position);
    if (view instanceof LinearLayout) {
        for (int j = 0; j < ((LinearLayout) view).getChildCount(); j++) {
            View child = ((LinearLayout) view).getChildAt(j);
            if (child instanceof TextView) {
                ((TextView) child).setTextColor(mSelectTitleColor);
                break;
            }
        }
    }
}

/**
 * 重置文本颜色
 */
private void resetTextViewColor() {
    for (int i = 0; i < getChildCount(); i++) {
        View view = getChildAt(i);
        if (view instanceof LinearLayout) {
            for (int j = 0; j < ((LinearLayout) view).getChildCount(); j++) {
                View child = ((LinearLayout) view).getChildAt(j);
                if (child instanceof TextView) {
                    ((TextView) child).setTextColor(COLOR_TEXT_NORMAL);
                    break;
                }
            }
        }
    }
}

/**
 * 设置点击事件
 */
public void setItemClickEvent() {
    int cCount = getChildCount();
    for (int i = 0; i < cCount; i++) {
        final int j = i;
        View view = getChildAt(i);
        view.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mViewPager.setCurrentItem(j);
            }
        });
    }
}

/**
 * 初始化三角形指示器
 */
private void initTriangle() {
    mPath = new Path();

    mTriangleHeight = (int) (mTriangleWidth / 2 / Math.sqrt(2));
    mPath.moveTo(0, 0);
    mPath.lineTo(mTriangleWidth, 0);
    mPath.lineTo(mTriangleWidth / 2, -mTriangleHeight);
    mPath.close();
}

/**
 * 指示器跟随手指滚动,以及容器滚动
 * @param scrollView
 * @param position
 * @param offset
 */
public void scroll(BounceScrollView scrollView, int position, float offset) {
    // 不断改变偏移量,invalidate
    if (position == 0) {
        mTranslationX = ScreenUtils.getInstance().getScreenWidth()
                / mTabVisibleCount * (position + offset);
    } else {
        mTranslationX = ScreenUtils.getInstance().getScreenWidth()
                / mTabVisibleCount * (position + offset);
    }

    int tabWidth = ScreenUtils.getInstance().getScreenWidth()
            / mTabVisibleCount;

    // 容器滚动,当移动到倒数最后一个的时候,开始滚动
    if (offset > 0 && position >= (mTabVisibleCount - 1)
            && getChildCount() > mTabVisibleCount) {

        if (mTabVisibleCount != 1) {
            // 下面注释掉,是滚动ViewPagerIndicator这个LinearLayout
            // this.scrollTo((position - (mTabVisibleCount - 1)) * tabWidth
            // + (int) (tabWidth * offset), 0);
            // 只滚动滚动条,禁止滚动lineayout
            scrollView.setScrolledTo((position - (mTabVisibleCount - 1))
                    * tabWidth + (int) (tabWidth * offset), 0);
        } else
        // 为count为1时 的特殊处理
        {
            this.scrollTo(position * tabWidth + (int) (tabWidth * offset),
                    0);
            // scrollView.setScrolledTo(position * tabWidth + (int)
            // (tabWidth * offset), 0);
        }
    }
    // 处理特殊情况
    else if (offset > 0 && position <= mTabVisibleCount - 1) {
        scrollView.setScrolledTo(0, 0);
    }

    invalidate();
}

// 设置布局中view的一些必要属性;如果设置了setTabTitles,布局中view则无效
@Override
protected void onFinishInflate() {
    super.onFinishInflate();

    int cCount = getChildCount();

    if (cCount == 0)
        return;

    for (int i = 0; i < cCount; i++) {
        View view = getChildAt(i);
        LinearLayout.LayoutParams lp = (LayoutParams) view
                .getLayoutParams();
        lp.weight = 0;
        lp.width = ScreenUtils.getInstance().getScreenWidth()
                / mTabVisibleCount;
        view.setLayoutParams(lp);
    }
    // 设置点击事件
    setItemClickEvent();
}

}
好了,到这里我们自定义可滑动的tab选项卡就完成了,这里贴出了实现的主页代码,详细情况请点击下载:http://download.csdn.net/detail/qq_20489601/9704356

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值