您的位置:首页 > Web前端 > CSS

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

2016-12-07 14:25 671 查看
下载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;
mTransl
4000
ationX = 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐