Indicator (指示器) 可能你們都見的比較多了,在一個APP中,有不少場景都會用到Indicator,好比第一次安裝APP 時的引導頁,首頁上面的廣告Banner ,又或者是Tab下面的Indicator。Indicator 通常配合ViewPager 使用,它能很直觀的反應出ViewPager 中一共有多少頁,當前選中的是哪一頁,用戶可以很容易的看出是否還能夠滑動,用戶體驗較好。那麼本篇文章就講一下如何經過自定義View來實現一個漂亮簡潔的IndicatorView。java
咱們使用Indicator時,常見的有三種形式,第一種是根據ViewPager 的總頁數展現一排小圓點,選中和未選中分別標爲不一樣的顏色。這也是廣泛的使用Indicator的效果,效果以下:android
第二種形式是根據ViewPager 的總頁數展現一排圓點,而且用數字標出其順序(1,2,3,4 ...),大概效果以下:git
第三種形式和第二種形式差很少,一樣根據ViewPager總頁數展現一片圓點,可是用字母標出順序(A,B,C,D ....), 好比 :魅族手機屏幕的切換的Indicator。效果以下:github
本篇文章就經過自定義View來實現這三種IndicatorView。canvas
首先整理一下思路,CircleIndicatorView ,它是由一組圓 組成的,只不過圓有多種狀態,選中、未選中、顯示字母、顯示數字等等。看着有這麼多中狀態,感受挺複雜,可是其實不復雜,只是用不一樣的變量來控制不一樣的狀態便可。其餘的先不考慮,咱們只要先把這一排圓畫出來,剩下的就比較容易了。要肯定一個圓的,須要知道圓的半徑和圓心,半徑是咱們能夠配置的,那麼,其實核心就是計算這一排的圓點的圓心位置。圓心 y 的位置是固定的(View 高的通常),所以咱們只須要計算每一個圓的圓心x的位置,看下圖,其實很容易就能找到規律,規律以下: 第一個圓的x 就等於圓的半徑,從第二個圓開始,當前圓的圓心x 座標爲 上一個圓的x 座標 + (radius * 2 + mSpace)。 其中mSpace 是圓之間的間距。app
有了上面的規律,咱們只須要一個循環就能找出全部圓的圓心位置。代碼以下:maven
/** * 測量每一個圓點的位置 */
private void measureIndicator(){
mIndicators.clear();
float cx = 0;
for(int i=0;i<mCount;i++){
Indicator indicator = new Indicator();
if( i== 0){
cx = mRadius + mStrokeWidth;
}else{
cx += (mRadius + mStrokeWidth) * 2 +mSpace;
}
indicator.cx = cx;
indicator.cy = getMeasuredHeight() / 2;
mIndicators.add(indicator);
}
}複製代碼
咱們用Indicator類記錄了每一個圓的圓心位置,而且保存在一個列表裏面,如今有了全部圓的數據,咱們就能夠繪製 了。ide
@Override
protected void onDraw(Canvas canvas) {
for(int i=0;i<mIndicators.size();i++){
Indicator indicator = mIndicators.get(i);
float x = indicator.cx;
float y = indicator.cy;
if(mSelectPosition == i){
mCirclePaint.setStyle(Paint.Style.FILL);
mCirclePaint.setColor(mSelectColor);
}else{
mCirclePaint.setColor(mDotNormalColor);
if(mFillMode != FillMode.NONE){
mCirclePaint.setStyle(Paint.Style.STROKE);
}else{
mCirclePaint.setStyle(Paint.Style.FILL);
}
}
canvas.drawCircle(x,y, mRadius, mCirclePaint);
// 繪製小圓點中的內容
if(mFillMode != FillMode.NONE){
String text = "";
if(mFillMode == FillMode.LETTER){
if(i >= 0 && i<LETTER.length){
text = LETTER[i];
}
}else{
text = String.valueOf(i+1);
}
Rect bound = new Rect();
mTextPaint.getTextBounds(text,0,text.length(),bound);
int textWidth = bound.width();
int textHeight = bound.height();
float textStartX = x - textWidth / 2;
float textStartY = y + textHeight / 2;
canvas.drawText(text,textStartX,textStartY, mTextPaint);
}
}
}複製代碼
繪製的代碼很簡單,無非就是循環咱們保存的列表,拿出每個Indicator,而後繪製圓就好了,除此以外就是一些狀態的判斷,好比,是否繪製數字、字母和選中狀態等等 。gradle
到此,咱們的CircleIndicatorView 的繪製工做算是完成了,而且也可以顯示在界面上了,可是它如今仍是一個單獨的View。咱們前面說過,IndicatorView 通常是配合ViewPager使用的,它是隨着Viewpager的切換而改變的,所以咱們須要將IndicatorView 與ViewPager 進行關聯。ui
要讓CircleIndicatorView 與ViewPager 關聯,首先須要 CircleIndicatorView 實現 ViewPager.OnPageChangeListener
接口。而後在onPageSelected
方法中記錄當前頁的位置。代碼以下:
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mSelectPosition = position;
invalidate();
}
@Override
public void onPageScrollStateChanged(int state) {
}複製代碼
第二步,向外提供一個API ,設置CircleIndicatorView 與 ViewPager 關聯。
/** * 與ViewPager 關聯 * @param viewPager */
public void setUpWithViewPager(ViewPager viewPager){
releaseViewPager();
if(viewPager == null){
return;
}
mViewPager = viewPager;
mViewPager.addOnPageChangeListener(this);
int count = mViewPager.getAdapter().getCount();
setCount(count);
}複製代碼
經過如上兩步,就創建了CircleIndicatorView 與ViewPager的關聯,只須要調用方法setUpWithViewPager
就OK。
到此就完了嗎?固然尚未完,其實還有一個小細節,那就是IndicatorView 因該是能夠點擊的,點擊Indicator小圓點就能切換ViewPager 到對應的頁面(如 Iphone 和 魅族手機的 屏幕切換,點擊indicator小圓點就能夠切換 ,之前沒有注意的如今能夠去試一下 ),那麼咱們也來實現這樣一個功能,其實很簡單,重寫onTouchEvent方法 ,監聽ACTION_DOWN 事件,而後獲取點擊屏幕的座標,與所繪製的圓位置比較,若是點擊區域在圓的範圍內,就點擊了該Indicator。點擊以後,切換Viewpager到對應頁面。代碼以下:
@Override
public boolean onTouchEvent(MotionEvent event) {
float xPoint = 0;
float yPoint = 0;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
xPoint = event.getX();
yPoint = event.getY();
handleActionDown(xPoint,yPoint);
break;
}
return super.onTouchEvent(event);
}
private void handleActionDown(float xDis,float yDis){
for(int i=0;i<mIndicators.size();i++){
Indicator indicator = mIndicators.get(i);
if(xDis < (indicator.cx + mRadius+mStrokeWidth)
&& xDis >=(indicator.cx - (mRadius + mStrokeWidth))
&& yDis >= (yDis - (indicator.cy+mStrokeWidth))
&& yDis <(indicator.cy+mRadius+mStrokeWidth)){
// 找到了點擊的Indicator
// 切換ViewPager
mViewPager.setCurrentItem(i,false);
// 回調
if(mOnIndicatorClickListener!=null){
mOnIndicatorClickListener.onSelected(i);
}
break;
}
}
}複製代碼
到此,咱們自定義IndicatorView 的工做就差很少完成了,可是如今的IndicatorView 還不是很靈活,咱們要讓它的可配置性更強,就應該提供更多的API 來讓IndicatorView 使用更加靈活方便,所以,最後一步,加上一些自定義屬性來提升它的靈活性。自定義了以下一些屬性:
屬性名 | 屬性意義 | 取值 |
---|---|---|
indicatorRadius | 設置指示器圓點的半徑 | 單位爲 dp 的值 |
indicatorBorderWidth | 設置指示器的border | 單位爲 dp 的值 |
indicatorSpace | 設置指示器之間的距離 | 單位爲 dp 的值 |
indicatorTextColor | 設置指示器中間的文字顏色 | 顏色值,如:#FFFFFF |
indicatorColor | 設置指示器圓點的顏色 | 顏色值 |
indicatorSelectColor | 設置指示器選中的顏色 | 顏色值 |
fill_mode | 設置指示器的模式 | 枚舉值:有三種,分別是letter,number和none |
自定義屬性文件以下:
<declare-styleable name="CircleIndicatorView">
<attr name="indicatorRadius" format="dimension"/>
<attr name="indicatorBorderWidth" format="dimension"/>
<attr name="indicatorSpace" format="dimension"/>
<attr name="indicatorTextColor" format="color"/>
<attr name="indicatorColor" format="color"/>
<attr name="indicatorSelectColor" format="color"/>
<attr name="fill_mode">
<enum name="letter" value="0"/>
<enum name="number" value="1"/>
<enum name="none" value="2"/>
</attr>
</declare-styleable>複製代碼
經過上面這些屬性,咱們就能夠很好的定製IndicaotorView 了,好比,自定義圓的大小,顏色,border,文字的顏色,選中的顏色和展現的模式等等。
最終的效果以下:
上面講了自定義IndicatorView 的思路、過程 和一些關鍵代碼,爲了使用方便,已經將CircleIndicatorView封裝成了一個庫,如下介紹一下CircleIndicatorView API的使用。Github 地址:github.com/pinguo-zhou…
Dependency
1, 最外層build.gradle添加以下代碼:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}複製代碼
2, app 層buid.gradle dependencies 中 添加以下代碼:
compile 'com.github.pinguo-zhouwei:CircleIndicatorView:v1.0.0'複製代碼
使用方法以下:
1, xml 添加CircleIndicatorView,配置相關屬性
<com.zhouwei.indicatorview.CircleIndicatorView
android:id="@+id/indicator_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="50dp"
android:layout_centerHorizontal="true"
app:indicatorSelectColor="#00A882"
app:fill_mode="letter"
app:indicatorBorderWidth="2dp"
app:indicatorRadius="8dp"
app:indicatorColor="@color/colorAccent"
app:indicatorTextColor="@android:color/white"
/>複製代碼
2, 在代碼中與ViewPager 關聯
// 初始化ViewPager 相關
mViewPager = (ViewPager) findViewById(R.id.viewpager);
mPagerAdapter = new ViewPagerAdapter();
mViewPager.setAdapter(mPagerAdapter);
mIndicatorView = (CircleIndicatorView) findViewById(R.id.indicator_view);
// 關聯ViewPager
mIndicatorView.setUpWithViewPager(mViewPager);複製代碼
上面全部列舉的屬性,出了在xml 中配置外,也能夠在代碼中設置,以下:
// 在代碼中設置相關屬性
CircleIndicatorView indicatorView = (CircleIndicatorView) findViewById(R.id.indicator_view3);
// 設置半徑
indicatorView.setRadius(DisplayUtils.dpToPx(15));
// 設置Border
indicatorView.setBorderWidth(DisplayUtils.dpToPx(2));
// 設置文字顏色
indicatorView.setTextColor(Color.WHITE);
// 設置選中顏色
indicatorView.setSelectColor(Color.parseColor("#FEBB50"));
//
indicatorView.setDotNormalColor(Color.parseColor("#E38A7C"));
// 設置指示器間距
indicatorView.setSpace(DisplayUtils.dpToPx(10));
// 設置模式
indicatorView.setFillMode(CircleIndicatorView.FillMode.LETTER);
// 最重要的一步:關聯ViewPager
indicatorView.setUpWithViewPager(mViewPager);複製代碼
以上就是經過自定義View的方式實現一個多功能的IndicatorView的所有內容,內容很簡單,經過自定義的View方式實現很方便,省得每次還得找設計師要切圖啊(能本身動手的,就不找設計師要了。😄)。若有問題歡迎交流,最後,若是你以爲不錯的話,歡迎github 賞個start啊!!輪子傳送門 github.com/pinguo-zhou…