自己动手写了一个指示器,感觉还可以,暂时没有写多栏滚动的。
一共做了三种样式
第一种样式:指示器的大小和标题等宽
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>
后面有时间的话,再写一个滚动标题栏的实例,现在只是有思路。
网友评论