ScrollView+指示器
最近干了一段时间的活,产品要求完成混动指示器的功能,也就是说上面有tab标题,当滑动到不同的位置之后,标题切换,且点击标题也可以使得scrollview,滑动到对应的位置,我看了看网上的例子觉得挺少程度,这里就分享一些经验给大家,第一自定义一个类似标题栏的指示器
public class CustomerIndicatior extends LinearLayout { /** * 绘制矩形的画笔 */ private Paint mPaint; /** * path构成一个矩形 */ private Path mPath; /** * 矩形的宽度 */ private int mRectangleWidth; /** * 初始时,矩形指示器的偏移量 */ private int mInitTranslationX; /** * 手指滑动时的偏移量 */ private float mTranslationX; /** * 默认的Tab数量 */ private static final int COUNT_DEFAULT_TAB = 4; /** * tab数量 */ private int mTabVisibleCount = COUNT_DEFAULT_TAB; /** * 与之绑定的ViewPager */ public ViewPager mViewPager; /** * 对外的ViewPager的回调接口 */ private ViewPagerIndicator.PageChangeListener onPageChangeListener; /** * 文本默认颜色 */ private int textColor; /** * 指示器颜色和文字高亮时的颜色 */ private int underlineColor; /** * 控件最底下的那一条线 */ private boolean DrawBottomLine ; /** * 指示器的高度 */ private float underlineHeight; /** * 画最底下的线的画笔 */ private Paint mDrawLinePaint; private ScroViewChangeListener scroViewChangeListener; public CustomerIndicatior(Context context) { this(context, null); } public CustomerIndicatior(Context context, AttributeSet attrs) { super(context, attrs); // 获得自定义属性 TypedArray a = context.obtainStyledAttributes(attrs, com.enjoylink.view.lib.R.styleable.ViewPagerIndicator); mTabVisibleCount = a.getInt(com.enjoylink.view.lib.R.styleable.ViewPagerIndicator_visibilitycount,COUNT_DEFAULT_TAB); textColor = a.getColor(com.enjoylink.view.lib.R.styleable.ViewPagerIndicator_textcolor, Color.parseColor("#ffffff")); underlineColor = a.getColor(com.enjoylink.view.lib.R.styleable.ViewPagerIndicator_underlinecolor, Color.parseColor("#1995F7")); underlineHeight = a.getDimension(com.enjoylink.view.lib.R.styleable.ViewPagerIndicator_underlineheigth, 16); DrawBottomLine = a.getBoolean(com.enjoylink.view.lib.R.styleable.ViewPagerIndicator_draw_bottom_line, false) ; if (mTabVisibleCount < 0) mTabVisibleCount = COUNT_DEFAULT_TAB; a.recycle(); // 初始化画笔 mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(underlineColor); mPaint.setStyle(Paint.Style.FILL); //画group的画笔 mDrawLinePaint = new Paint(); mDrawLinePaint.setAntiAlias(true); mDrawLinePaint.setColor(Color.parseColor("#dad9dc")); mDrawLinePaint.setStyle(Paint.Style.FILL); } /** * 画Group */ @Override protected void onDraw(Canvas canvas) { if(DrawBottomLine){ canvas.save() ; canvas.drawRect(0, getHeight()-underlineHeight/2, getWidth(), getHeight(), mDrawLinePaint); canvas.restore(); } super.onDraw(canvas); } /** * 绘制指示器 */ @Override protected void dispatchDraw(Canvas canvas) { canvas.save(); // 画笔平移到正确的位置 canvas.translate(mInitTranslationX + mTranslationX, getHeight() + 1); canvas.drawPath(mPath, mPaint); canvas.restore(); super.dispatchDraw(canvas); } /** * 初始化矩形的宽度 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // 初始化矩形 initRectangle(); // 初始时的偏移量 mInitTranslationX = getWidth() / mTabVisibleCount / 2 - mRectangleWidth/ 2; } /** * 设置可见的tab的数量 * * @param count */ public void setVisibleTabCount(int count) { this.mTabVisibleCount = count; } /** * 设置tab的标题内容 可选,可以自己在布局文件中写死 * * @param datas */ public void setTabItemTitles(List<String> datas) { // 如果传入的list有值,则移除布局文件中设置的view if (datas != null && datas.size() > 0) { this.removeAllViews(); for (String title : datas) { // 添加view addView(generateTextView(title)); } // 设置item的click事件 setItemClickEvent(); } } public void setTabItemTitles(String[] datas) { if (datas != null && datas.length > 0) { this.removeAllViews(); for (int i=0;i<datas.length;i++) { addView(generateTextView(datas[i])); } setItemClickEvent(); } } public void setSeletView(int pos ,float positionOffset) { scroll(pos, positionOffset); resetTextViewColor(); highLightTextView(pos); } /** * 高亮文本 * * @param position */ protected void highLightTextView(int position) { View view = getChildAt(position); if (view instanceof TextView) { ((TextView) view).setTextColor(underlineColor); } } /** * 重置文本颜色 */ private void resetTextViewColor() { for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); if (view instanceof TextView) { ((TextView) view).setTextColor(textColor); } } } /** * 设置点击事件 */ 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) { scroViewChangeListener.onSlectScrolled(j); } }); } } /** * 根据标题生成我们的TextView * * @param text * @return */ private TextView generateTextView(String text) { TextView tv = new TextView(getContext()); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); lp.width = getScreenWidth() / mTabVisibleCount; tv.setGravity(Gravity.CENTER); tv.setTextColor(textColor); tv.setText(text); tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); if(mRectangleWidth == 0){ TextPaint paint = tv.getPaint() ; mRectangleWidth = (int) paint.measureText(text) ; } tv.setLayoutParams(lp); return tv; } /** * 初始化矩形指示器 */ private void initRectangle() { mPath = new Path(); mPath.moveTo(0, 0); mPath.lineTo(mRectangleWidth, 0); mPath.lineTo(mRectangleWidth, -underlineHeight); mPath.lineTo(0, -underlineHeight); mPath.close(); } /** * 设置布局中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 = getScreenWidth() / mTabVisibleCount; view.setLayoutParams(lp); } // 设置点击事件 setItemClickEvent(); } /** * 获得屏幕的宽度 * * @return */ public int getScreenWidth() { WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.widthPixels; } /** * 指示器跟随手指滚动,以及容器滚动 * * @param position * @param offset */ public void scroll(int position, float offset) { /** * 0-1:position=0 ;1-0:postion=0; */ // 不断改变偏移量,invalidate mTranslationX = getWidth() / mTabVisibleCount * (position + offset); int tabWidth = getScreenWidth() / mTabVisibleCount; // 容器滚动,当移动到倒数最后一个的时候,开始滚动 if (offset > 0 && position >= (mTabVisibleCount - 2)&& getChildCount() > mTabVisibleCount) { if (mTabVisibleCount != 1) { this.scrollTo((position - (mTabVisibleCount - 2)) * tabWidth+ (int) (tabWidth * offset), 0); } else // 为count为1时 的特殊处理 { this.scrollTo(position * tabWidth + (int) (tabWidth * offset),0); } } invalidate(); } public interface ScroViewChangeListener { public void onSlectScrolled(int position); } public void setOnScroViewChangeListener(ScroViewChangeListener scroViewChangeListener) { this.scroViewChangeListener = scroViewChangeListener; } }
第二步需要一个自定义的ScrollView,这里主要是监听ScrollView的滑动距离
public class MyScrollView extends ScrollView { private static final long DELAY = 100; private int currentScroll; private Runnable scrollCheckTask; /** * @param context */ public MyScrollView(Context context) { super(context); init(); } /** * @param context * @param attrs */ public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * @param context * @param attrs * @param defStyle */ public MyScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { scrollCheckTask = new Runnable() { @Override public void run() { int newScroll = getScrollY(); if (currentScroll == newScroll) { if (onScrollListener != null) { onScrollListener.onScrollStopped(); } } else { if (onScrollListener != null) { onScrollListener.onScrolling(); } currentScroll = getScrollY(); postDelayed(scrollCheckTask, DELAY); } } }; setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { currentScroll = getScrollY(); postDelayed(scrollCheckTask, DELAY); } return false; } }); } public interface OnScrollListener { public void onScrollChanged(int x, int y, int oldX, int oldY); public void onScrollStopped(); public void onScrolling(); } private OnScrollListener onScrollListener; /** * @param onScrollListener */ public void setOnScrollListener(OnScrollListener onScrollListener) { this.onScrollListener = onScrollListener; } @Override protected void onScrollChanged(int x, int y, int oldX, int oldY) { super.onScrollChanged(x, y, oldX, oldY); if (onScrollListener != null) { onScrollListener.onScrollChanged(x, y, oldX, oldY); } } /** * @param child * @return */ public boolean isChildVisible(View child) { if (child == null) { return false; } Rect scrollBounds = new Rect(); getHitRect(scrollBounds); return child.getLocalVisibleRect(scrollBounds); } /** * @return */ public boolean isAtTop() { return getScrollY() <= 0; } /** * @return */ public boolean isAtBottom() { return getChildAt(getChildCount() - 1).getBottom() + getPaddingBottom() == getHeight() + getScrollY(); } }
第三步在Activity中的使用,以下是调用指示器的接口回调处理,指示器ci_indicatior的点击事件,滑动至相应的地点,
和sv_message_show, ScrollView的监听滑动所做的事情, 这里需要主要的是定位地点的高度准确才能做出判断,
看以下示意图
ci_indicatior.setOnScroViewChangeListener(new CustomerIndicatior.ScroViewChangeListener() { //监听指示器的点击 @Override public void onSlectScrolled(int position) { if(position==0){ sv_message_show.scrollTo(0,0); }else if (position==1){ sv_message_show.scrollTo(0,tv_text_line_tow.getTop()); }else if (position==2){ sv_message_show.scrollTo(0,tv_text_line_there.getTop()); } } }); sv_message_show.setOnScrollListener(new MyScrollView.OnScrollListener() {//监听scroView的滚动 @Override public void onScrollChanged(int x, int t, int oldX, int oldY) { if (t < tv_text_line_tow.getTop()) { ci_indicatior.setSeletView(0, 0); } else if (t < tv_text_line_there.getTop() && t >= tv_text_line_tow.getTop()) { ci_indicatior.setSeletView(1, 0); } else if (t >= tv_text_line_there.getTop()) { ci_indicatior.setSeletView(2, 0); } } @Override public void onScrollStopped() { if (sv_message_show.isAtTop()) { ci_indicatior.setSeletView(0, 0); } else if (sv_message_show.isAtBottom()) { ci_indicatior.setSeletView(2, 0); } } @Override public void onScrolling() { android.util.Log.d("@", "scrolling..."); } });
最后希望对大家有帮助,第一次分享自己的一些心得,写的不好的地方望见谅