美文网首页高级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