美文网首页高级UI具体自定义控件自定义控件
ViewPager的滚动指示器(模仿微博,长宽颜色圆角可设置)

ViewPager的滚动指示器(模仿微博,长宽颜色圆角可设置)

作者: 龍and胡歌 | 来源:发表于2020-02-06 19:10 被阅读0次

    自己动手写了一个指示器,感觉还可以,暂时没有写多栏滚动的。
    一共做了三种样式
    第一种样式:指示器的大小和标题等宽


    1580986653348.gif

    第二种样式:类似于游动,简单模仿微博的实现方案,颜色渐变我没加。


    1581006168305.gif

    第三种样式:最常见的样式。


    1580987316781.gif

    主要的思路是对viewPager进行监听,addOnPageChangeListener。根据onPageScrolled方法的positionOffset和position进行判断。
    至于标题和指示器的位置关系,我是假定它们是位于同一个父布局之中的,直接getLeft()等方法就可以完成位置的计算,如果不是同一个父布局内的话,还得用其他的方法。
    把我的代码贴在下面。
    布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingTop="50dp"
        tools:context=".MainActivity">
        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="抗击肺炎"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:textSize="24sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="武汉"
            android:textSize="24sp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            app:layout_constraintLeft_toRightOf="@id/tv1"
            app:layout_constraintTop_toTopOf="parent" />
    
        <TextView
            android:id="@+id/tv3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="我"
            android:textSize="24sp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            app:layout_constraintLeft_toRightOf="@id/tv2"
            app:layout_constraintTop_toTopOf="parent" />
        <TextView
            android:id="@+id/tv4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="世界和平"
            android:textSize="24sp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            app:layout_constraintLeft_toRightOf="@id/tv3"
            app:layout_constraintTop_toTopOf="parent" />
        <com.example.javatest.IndicactorView
            android:id="@+id/indicator"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            app:indicator_length="15dp"
            app:indicator_height="5dp"
            app:indicator_radius="3dp"
            app:indicator_color="#a451B5"
            app:indicator_mode="MODE_FIX_TITLE"
            app:layout_constraintLeft_toLeftOf="@id/tv1"
            app:layout_constraintTop_toBottomOf="@id/tv1"
            app:layout_constraintRight_toRightOf="@id/tv4"/>
        <androidx.viewpager.widget.ViewPager
            android:id="@+id/viewPager"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/indicator"
            app:layout_constraintBottom_toBottomOf="parent"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    MainActivity:

    public class MainActivity extends AppCompatActivity {
        Handler mHandler;
        IndicactorView indicactorView;
        TextView tv1,tv2,tv3,tv4;
        ViewPager viewPager;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            tv1=findViewById(R.id.tv1);
            tv2=findViewById(R.id.tv2);
            tv3=findViewById(R.id.tv3);
            tv4=findViewById(R.id.tv4);
            indicactorView=findViewById(R.id.indicator);
            viewPager=findViewById(R.id.viewPager);
            indicactorView.setViewPager(viewPager)
                    .setTitleViews(tv1,tv2,tv3,tv4);
            tv1.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    viewPager.setCurrentItem(0);
                }
            });
            tv2.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    viewPager.setCurrentItem(1);
                }
            });
            tv3.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    viewPager.setCurrentItem(2);
                }
            });
            tv4.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    viewPager.setCurrentItem(3);
                }
            });
            viewPager.setAdapter(new PagerAdapter() {
                @Override
                public int getCount() {
                    return 4;
                }
    
                @Override
                public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
                    return view==object;
                }
    
                @NonNull
                @Override
                public Object instantiateItem(@NonNull ViewGroup container, int position) {
                    TextView tv=new TextView(MainActivity.this);
                    tv.setText(""+position);
                    tv.setTextSize(30);
                    tv.setGravity(CENTER);
                    container.addView(tv,-1,-1);
                    return tv;
                }
    
                @Override
                public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
                    container.removeView((View) object);
                }
            });
        }
    }
    

    指示器的代码:

    
    /**
     * viewPager的指示器
     * 需要注意:传进来的标题的View要与指示器位于同一个父布局之中,而且标题的起始位置要与指示器对齐,。
     */
    public class IndicactorView extends View {
        private Paint mPaint;
        private Context mContext;
        //标题的view,也就是TextView
        private View[] titleViews;
        //指示标的长度
        private int indicatorLength;
        //指示标的高度
        private int indicatorHeight;
        //指示标的圆弧度
        private int indicatorRadius;
        //指示标的左边位置
        private int indicatorLeft;
        //指示标的右边位置
        private int indicatorRight;
        //标题最左边的位置,用于计算标题和图标的位置关系
        private int titleLeft;
        //viewPager选中页
        private int selectPosition;
        //viewPager左右移动了多少
        private float offset;
        //viewPager移动过程中,始发的那一夜
        private int fromPositon;
        //样式
        private int mode = 2;
        //指示标的颜色
        private int indicatorColor;
        //viewPager的滚动状态
        private int scrollState = SCROLL_STATE_IDLE;
        //是否进行点击跳转超过2页
        private boolean isJumpToNext;
        //跳转时的动画
        private ValueAnimator animator;
        //跳转前的页数
        private int lastPosition;
        //跳转动画的进度
        private float jumpNextOffset;
        //样式,三种
        public final static int MODE_FLOW = 1;//流动
        public final static int MODE_SCROLL = 2;//滚动
        public final static int MODE_FIX_TITLE = 3;//与标题等宽自适应
    
        public IndicactorView(Context context) {
            this(context, null);
        }
    
        public IndicactorView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public IndicactorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndicactorView);
            indicatorLength = typedArray.getDimensionPixelSize(R.styleable.IndicactorView_indicator_length, 25);
            indicatorRadius = typedArray.getDimensionPixelSize(R.styleable.IndicactorView_indicator_radius, 0);
            indicatorHeight = typedArray.getDimensionPixelSize(R.styleable.IndicactorView_indicator_height, 8);
            mode = typedArray.getInt(R.styleable.IndicactorView_indicator_mode, 1);
            indicatorColor = typedArray.getColor(R.styleable.IndicactorView_indicator_color, Color.parseColor("#3F51B5"));
            init(context);
        }
    
        private void init(Context context) {
            mPaint = new Paint();
            mContext = context;
            animator = new ValueAnimator();
        }
    
        public void setIndicatorLength(int indicatorLength) {
            this.indicatorLength = indicatorLength;
        }
    
        public void setIndicatorHeight(int indicatorHeight) {
            this.indicatorHeight = indicatorHeight;
        }
    
        public void setIndicatorRadius(int indicatorRadius) {
            this.indicatorRadius = indicatorRadius;
        }
    
        public void setIndicatorColor(int indicatorColor) {
            this.indicatorColor = indicatorColor;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    measureHeight(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    
        private int measureHeight(int size, int measureSpec) {
            int result = size;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            switch (specMode) {
                case MeasureSpec.UNSPECIFIED:
                    result = size;
                    break;
                case MeasureSpec.AT_MOST:
                    result = indicatorHeight;
                    break;
                case MeasureSpec.EXACTLY:
                    result = specSize;
                    break;
            }
            return result;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            mPaint.reset();
            mPaint.setAntiAlias(true);
            mPaint.setColor(indicatorColor);
            //点击跳转超过两个距离
            if (isJumpToNext) {
                if (titleViews.length > 0 && selectPosition < titleViews.length) {
                    if (mode == MODE_FIX_TITLE) {
                        indicatorLength = titleViews[selectPosition].getWidth() - (int) ((titleViews[selectPosition].getWidth() - titleViews[lastPosition].getWidth()) * (1 - jumpNextOffset));
                    }
                    titleLeft = titleViews[0].getLeft();
                    int startLeft = 0;
                    int endLeft = 0;
                    if (lastPosition < titleViews.length) {
                        startLeft = titleViews[lastPosition].getLeft() - titleLeft + titleViews[lastPosition].getWidth() / 2 - indicatorLength / 2;
                    }
                    if (selectPosition < titleViews.length) {
                        endLeft = titleViews[selectPosition].getLeft() - titleLeft + titleViews[selectPosition].getWidth() / 2 - indicatorLength / 2;
                    }
                    indicatorLeft = (int) (startLeft + (endLeft - startLeft) * jumpNextOffset);
                    indicatorRight = indicatorLeft + indicatorLength;
                }
            }
            //非点击长距离跳转
            else {
                int anchorPosition=selectPosition;
                if (fromPositon == selectPosition) {
                    //向右滑
                    if(mode==MODE_FLOW){
                        if(offset>0.5f){
                            offset=-(1f-offset);
                            anchorPosition=selectPosition+1;
                        }
                    }
                    Log.d("-------", "向右滑");
                } else {
                    //向左滑
                    if(mode==MODE_FLOW) {
                        if(offset>0.5){
                            offset=-(1-offset);
                        }else {
                            anchorPosition=selectPosition-1;
                        }
                    }else {
                        offset = -(1 - offset);
                    }
                    Log.d("-------", "向左滑");
                }
                Log.d("-------", "滑动"+offset);
                if (titleViews.length > 0 && anchorPosition < titleViews.length) {
                    titleLeft = titleViews[0].getLeft();
                    int titleCenter = titleViews[anchorPosition].getLeft() - titleLeft + titleViews[anchorPosition].getWidth() / 2;
                    if (offset >= 0) {
                        //向右滑动
                        if (mode == MODE_FLOW) {
                            Log.d("-------", "滑动"+anchorPosition);
                            indicatorLeft = titleCenter - indicatorLength / 2;
                            int offsetLength = 0;
                            indicatorRight = titleCenter + indicatorLength / 2;
                            if (anchorPosition + 1 < titleViews.length) {
                                offsetLength = (int) ((titleViews[anchorPosition + 1].getLeft() - titleLeft + titleViews[anchorPosition + 1].getWidth() / 2 + indicatorLength / 2 - indicatorRight) * offset*2);
                            }
                            indicatorRight += offsetLength;
                        } else if (mode == MODE_SCROLL || mode == MODE_FIX_TITLE) {
                            if (mode == MODE_FIX_TITLE) {
                                if (anchorPosition + 1 < titleViews.length) {
                                    indicatorLength = titleViews[anchorPosition].getWidth() + (int) ((titleViews[anchorPosition + 1].getWidth() - titleViews[anchorPosition].getWidth()) * offset);
                                }
                            }
                            int leftCurrent = titleCenter - indicatorLength / 2;
                            int offsetLength = 0;
                            if (anchorPosition + 1 < titleViews.length) {
                                int leftNext = titleViews[anchorPosition + 1].getLeft() - titleLeft + titleViews[anchorPosition + 1].getWidth() / 2 - indicatorLength / 2;
                                offsetLength = (int) ((leftNext - leftCurrent) * offset);
                            }
                            indicatorLeft = leftCurrent + offsetLength;
                            indicatorRight = indicatorLeft + indicatorLength;
                        }
                    } else {
                        //向左滑动
                        if (mode == MODE_FLOW) {
                            Log.d("-------", "滑动"+anchorPosition);
                            indicatorRight = titleCenter + indicatorLength / 2;
                            int offsetLength = 0;
                            indicatorLeft = titleCenter - indicatorLength / 2;
                            if (anchorPosition - 1 >= 0) {
                                offsetLength = (int) ((indicatorLeft - (titleViews[anchorPosition - 1].getLeft() - titleLeft + titleViews[anchorPosition - 1].getWidth() / 2 - indicatorLength / 2)) * offset*2);
                            }
                            indicatorLeft += offsetLength;
                        } else if (mode == MODE_SCROLL || mode == MODE_FIX_TITLE) {
                            if (mode == MODE_FIX_TITLE) {
                                if (mode == MODE_FIX_TITLE) {
                                    if (anchorPosition - 1 >= 0) {
                                        indicatorLength = titleViews[anchorPosition].getWidth() + (int) ((titleViews[anchorPosition - 1].getWidth() - titleViews[anchorPosition].getWidth()) * (-offset));
                                    }
                                }
                            }
                            int leftCurrent = titleCenter - indicatorLength / 2;
                            int offsetLength = 0;
                            if (anchorPosition - 1 >= 0) {
                                int leftNext = titleViews[anchorPosition - 1].getLeft() - titleLeft + titleViews[anchorPosition - 1].getWidth() / 2 - indicatorLength / 2;
                                offsetLength = (int) ((leftCurrent - leftNext) * offset);
                            }
                            indicatorLeft = leftCurrent + offsetLength;
                            indicatorRight = indicatorLeft + indicatorLength;
                        }
                    }
                }
            }
            RectF r = new RectF();
            r.left = indicatorLeft;
            r.top = 0;
            r.right = indicatorRight;
            r.bottom = indicatorHeight;
            canvas.drawRoundRect(r, indicatorRadius, indicatorRadius, mPaint);
        }
    
        public void setMode(int mode) {
            this.mode = mode;
        }
    
        public IndicactorView setTitleViews(View... views) {
            this.titleViews = views;
            return this;
        }
    
        public IndicactorView setViewPager(ViewPager viewPager) {
            viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    Log.d("-------", "position:" + position + ", positionOffset:" + positionOffset + ", positionOffsetPixels:" + positionOffsetPixels);
                    fromPositon = position;
                    offset = positionOffset;
                    if (!isJumpToNext) {
                        invalidate();
                    }
                }
    
                @Override
                public void onPageSelected(int position) {
                    Log.d("-------", "position:" + position);
                    lastPosition = selectPosition;
                    selectPosition = position;
                    if (Math.abs(lastPosition - position) > 1) {
                        isJumpToNext = true;
                        animator = ValueAnimator.ofFloat(0, 1);
                        animator.setDuration(300);
                        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                            @Override
                            public void onAnimationUpdate(ValueAnimator animation) {
                                jumpNextOffset = (float) animation.getAnimatedValue();
                                postInvalidate();
                            }
                        });
                        animator.start();
                    }
                }
    
                @Override
                public void onPageScrollStateChanged(int state) {
                    scrollState = state;
                    if (scrollState == SCROLL_STATE_IDLE) {
                        isJumpToNext = false;
                        offset = 0;
                        invalidate();
                    }
                }
            });
            return this;
        }
    }
    

    自定义属性:

      <declare-styleable name="IndicactorView">
            <attr name="indicator_length" format="dimension"/>
            <attr name="indicator_radius" format="dimension"/>
            <attr name="indicator_height" format="dimension"/>
            <attr name="indicator_color" format="reference|color"/>
            <attr name="indicator_mode">
                <enum name="MODE_FLOW" value="1"/>
                <enum name="MODE_SCROLL" value="2"/>
                <enum name="MODE_FIX_TITLE" value="3"/>
            </attr>
        </declare-styleable>
    

    后面有时间的话,再写一个滚动标题栏的实例,现在只是有思路。

    相关文章

      网友评论

        本文标题:ViewPager的滚动指示器(模仿微博,长宽颜色圆角可设置)

        本文链接:https://www.haomeiwen.com/subject/fzvuxhtx.html