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...");
    }
});

最后希望对大家有帮助,第一次分享自己的一些心得,写的不好的地方望见谅

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值