讀人就是讀自己塞栅。 — 《等一個(gè)人讀書》
寫在前面
最近項(xiàng)目又改了UI,真是一件開(kāi)心的事情(微笑臉)静稻,效果圖見(jiàn)圖一,右上角有一個(gè)水平的白色線條匈辱,還有一個(gè)灰色的背景線條振湾,就是這個(gè)東西,它是一個(gè)指示器亡脸。起初在沒(méi)看到代碼之前押搪,我以為添加應(yīng)用的界面類似ScrollView這種東西做的,如圖二的淘寶首頁(yè)輪播圖下面部分浅碾,指示器和內(nèi)容聯(lián)動(dòng)大州,手指滑動(dòng)內(nèi)容,指示器就會(huì)實(shí)時(shí)隨之變化垂谢,具體效果詳見(jiàn)淘寶首頁(yè)厦画。但是這里面有一個(gè)問(wèn)題,我們項(xiàng)目這個(gè)界面用ViewPager寫的埂陆,所以解決方案是根據(jù)頁(yè)數(shù)去更新指示器位置苛白。
具體實(shí)現(xiàn)
上面講明了需求,現(xiàn)在就讓我們用代碼實(shí)現(xiàn)該指示器焚虱,創(chuàng)建TrackView繼承自View。
public class TrackView extends View {
// 背景色
private int mBackColor;
// 前景色
private int mForeColor;
// 背景寬度
private int mBackWidth;
// 前景寬度
private int mForeWidth;
// 高度
private int mHeight;
// 前景色距View開(kāi)始距離
private float mForeDistance;
// 畫筆
private Paint mPaint;
public TrackView(Context context) {
this(context, null);
}
public TrackView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public TrackView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
if (null != attrs) {
// 獲取自定義屬性懂版,獲取不到則使用默認(rèn)值
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.TrackView);
mBackColor = typedArray.getColor(R.styleable.TrackView_back_color, Color.GRAY);
mForeColor = typedArray.getColor(R.styleable.TrackView_fore_color, Color.WHITE);
mBackWidth = typedArray.getDimensionPixelOffset(R.styleable.TrackView_back_width, 100);
mForeWidth = typedArray.getDimensionPixelOffset(R.styleable.TrackView_fore_width, 50);
mHeight = typedArray.getDimensionPixelOffset(R.styleable.TrackView_height, 10);
// 一定要回收
typedArray.recycle();
} else {
mBackColor = Color.GRAY;
mForeColor = Color.WHITE;
mBackWidth = 100;
mForeWidth = 50;
mHeight = 10;
}
// 創(chuàng)建畫筆鹃栽,設(shè)置抗鋸齒
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 設(shè)置畫筆開(kāi)始和結(jié)束為圓角
mPaint.setStrokeCap(Paint.Cap.ROUND);
// 設(shè)置畫筆寬度
mPaint.setStrokeWidth(mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
drawForeground(canvas);
}
/**
* 繪制背景線條,固定的那條線
* 需要考慮內(nèi)邊距
* @param canvas
*/
private void drawBackground(Canvas canvas) {
mPaint.setColor(mBackColor);
canvas.drawLine(mHeight + getPaddingLeft(),
mHeight / 2 + getPaddingTop(),
mHeight + getPaddingLeft() + mBackWidth,
mHeight / 2 + getPaddingTop(),
mPaint);
}
/**
* 繪制前景線條,會(huì)動(dòng)的那條線民鼓,根據(jù)mForeDistance改變位置
* 需要考慮內(nèi)邊距
* @param canvas
*/
private void drawForeground(Canvas canvas) {
mPaint.setColor(mForeColor);
canvas.drawLine(mHeight + getPaddingLeft() + mForeDistance,
mHeight / 2 + getPaddingTop(),
mHeight + getPaddingLeft() + mForeDistance + mForeWidth,
mHeight / 2 + getPaddingTop(),
mPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 重新計(jì)算寬高
int width = getSize(mHeight * 2 + mBackWidth + getPaddingLeft() + getPaddingRight(), widthMeasureSpec);
int height = getSize(mHeight + getPaddingTop() + getPaddingBottom(), heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int getSize(int size, int measureSpec) {
int result = size;
int mode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (mode) {
// 如果測(cè)量模式為未知或wrap_content薇芝,則返回默認(rèn)值。
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
result = size;
break;
// 如果測(cè)量模式為具體數(shù)值或match_parent丰嘉,則返回具體數(shù)值夯到。
case MeasureSpec.EXACTLY:
result = specSize;
break;
default:
break;
}
return result;
}
/**
* 根據(jù)頁(yè)數(shù)更新指示器位置
* @param position 當(dāng)前頁(yè)數(shù)
* @param position 總頁(yè)數(shù)
*/
public void updateByPage(int position, int count) {
float offset = mBackWidth - mForeWidth;
mForeDistance = offset / (count - 1) * position;
postInvalidate();
}
}
下面是自定義屬性,在/src/main/res/values/attrs.xml中饮亏。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TrackView">
<attr name="back_color" format="color"/>
<attr name="fore_color" format="color"/>
<attr name="back_width" format="dimension"/>
<attr name="fore_width" format="dimension"/>
<attr name="height" format="dimension"/>
</declare-styleable>
</resources>
如何使用
下面通過(guò)一個(gè)Demo演示如何使用該自定義View耍贾。
1.創(chuàng)建布局
使用ConstraintLayout包裹ViewPager和TrackView,指定TrackView的自定義屬性路幸。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.chad.learning.track.view.TrackView
android:id="@+id/view_track"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:back_color="@android:color/darker_gray"
app:back_width="100dp"
app:fore_color="@android:color/background_dark"
app:fore_width="50dp"
app:height="10dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>
2.創(chuàng)建適配器
ViewPager需要適配器才能加載內(nèi)容荐开,所以這里創(chuàng)建一個(gè)適配器,每一頁(yè)的內(nèi)容都是一個(gè)TextView简肴。
public class ViewPagerAdapter extends PagerAdapter {
private Context mContext;
private List<String> mData;
public ViewPagerAdapter(Context context, List<String> data) {
mContext = context;
mData = data;
}
@Override
public int getCount() {
return mData == null ? 0 : mData.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
TextView textView = new TextView(mContext);
textView.setLayoutParams(layoutParams);
textView.setTextColor(Color.BLACK);
textView.setTextSize(50);
textView.setText(mData.get(position));
textView.setGravity(Gravity.CENTER);
container.addView(textView);
return textView;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
}
3.創(chuàng)建Activity
新建Activity晃听,重寫onCreate函數(shù),調(diào)用setContentView指定布局砰识,初始化View并調(diào)用ViewPager的addOnPageChangeListener設(shè)置頁(yè)數(shù)改變監(jiān)聽(tīng)器能扒。
public class TrackActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener {
private ViewPager mViewPager;
private TrackView mTrackView;
private ViewPagerAdapter mViewPagerAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_track);
initView();
}
private void initView() {
mViewPager = findViewById(R.id.view_pager);
mTrackView = findViewById(R.id.view_track);
List<String> data = new ArrayList<>();
for (int i = 0; i < 5; i ++) {
data.add(String.format("當(dāng)前頁(yè)數(shù):%s", i + 1));
}
mViewPagerAdapter = new ViewPagerAdapter(this, data);
mViewPager.setAdapter(mViewPagerAdapter);
mViewPager.addOnPageChangeListener(this);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
// 該函數(shù)為頁(yè)數(shù)改變回調(diào),通過(guò)該回調(diào)更新指示器
mTrackView.updateByPage(position, mViewPagerAdapter.getCount());
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
運(yùn)行效果如下:
最后
如果這個(gè)功能讓我做辫狼,強(qiáng)烈要求使用類似ScrollView那樣的效果實(shí)現(xiàn)初斑,這樣指示器相當(dāng)于ScrollBar,類似淘寶那樣的效果予借,用戶體驗(yàn)很好越平,這篇文章就不演示這種實(shí)現(xiàn)方式了,實(shí)現(xiàn)起來(lái)也不是很難灵迫,留給有興趣的同學(xué)們搞秦叛。