自定义可滑动的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;
}
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);
/**
* 绘制指示器
*/
@Override
protected void dispatchDraw(Canvas canvas) {
// 保存画布
canvas.save();
switch (mIndicatorStyle) {
case STYLE_LINE:
//下划线
canvas.translate(mTranslationX, getHeight() - mTriangleHeight);// 画笔平移到正确的位置
canvas.drawRect(mRectF, mPaint);
break;
}
好了,到这里我们自定义可滑动的tab选项卡就完成了,这里贴出了实现的主页代码,详细情况请点击下载: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
相关文章推荐
- 自定义TabHost实现背景图片随选项卡切换滑动效果
- 自定义TabHost实现背景图片随选项卡切换滑动效果
- TabTopAutoLayout【自定义顶部选项卡区域(带下划线)(动态选项卡数据且可滑动)】
- 自定义TabHost 一个avtiviy 多个标签 ,实现背景图片随选项卡切换滑动效果
- jQuery实现的Tab滑动选项卡及图片切换(多种效果)小结
- TabLayout的自定义实现选项卡背景的滑动动画
- 自定义RadioButton样式,切换按钮时变换背景颜色
- Tabhost漂亮的自定义实现(背景随着选项卡滑动改变)
- 转载【微信小程序】:微信小程序滚动Tab选项卡:左右可滑动切换(仿某宝)
- Android自定义控件2:自定义带下划线的文本或按钮、组合使用可切换tab
- 仿 QQ 底部 Tab 切换带数字提示的 RadioButton,支持自定义提示数字背景颜色、字体大小、字体颜色。后续将持续更新......
- Tabhost漂亮的自定义实现(背景随着选项卡滑动改变)
- Tabhost漂亮的自定义实现(背景随着选项卡滑动改变)
- javascript+div样式的自定义tab选项卡
- TabTopUnderLineLayout【自定义顶部选项卡(带下划线)】
- TabLayout的自定义实现选项卡背景的滑动动画
- android--解决方案--自定义tabhost(动态添加选项+带自动水平滑动选项卡+手势切换选项卡及内容功能)
- Tabhost漂亮的自定义实现(背景随着选项卡滑动改变)
- Android实现连续并排的若干个TextView单击改变背景颜色达到选项卡Tab栏切换效果
- ios 自定义带下划线的tab切换按钮