1. 程式人生 > >自定義控制元件由淺到深(二)實現ViewPager滑動時的小圓點變化

自定義控制元件由淺到深(二)實現ViewPager滑動時的小圓點變化

在我的之前兩個部落格有介紹ViewPager的使用和簡述自定義控制元件,但在ViewPager的使用中,我沒有添加當ViewPager滑動狀態改變時,下方小圓點跟隨變化的效果並沒有實現。

   實現ViewPager小圓點滑動效果的方法有多種方法,這次我們主要是為了深入的研究自定義View,那就以此實現該效果。如果開發有這方面的需求,可以在www.github.com搜尋Indicator,有很多這方面非常好的開源控制元件。

     通過這個例子,引申出:

                                        1.如何建立自定義view

                                        2.自定義view的動態改變

                                        3.自定義view怎樣設定屬性

先看一張效果圖:

                          

一、佈局檔案:

其中用到v4包下的ViewPager,Indicator是我們自定義的view(在第二步建立),也就是效果圖中的小圓點。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width=
"match_parent" android:layout_height="match_parent"> <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="200dp"></android.support.v4.view.ViewPager> <com.gaojianbo.indicator.Indicator android:id="@+id/indicator"
android:layout_width="200dp" android:layout_height="60dp" android:layout_alignBottom="@+id/viewPager" android:layout_centerHorizontal="true"/> </RelativeLayout> 二、新建一個類Indicator繼承View,完成自定義view 思考

1.需要兩個畫筆進行繪畫:第一個畫空心圓,第二個畫實心的移動的圓(後面簡稱實心圓)

2.怎樣實現動態的新增空心圓?

程式設計思路:動態新增,一般使用for迴圈,根據一個int變數,來決定程式碼執行次數,可以節省很多程式碼。

3.實現實心圓跟隨ViewPager的滑動而改變位置是怎樣實現的?

關鍵在於偏移量的問題,在Indicator設定好接收偏移量的方法,在MainActivity中的ViewPager監聽事件的回撥方法中在呼叫,

傳遞Position位置和移動百分比給Indicator自定義控制元件(詳細在程式碼中)。

public class Indicator extends View {
    //實心圓的畫筆
private Paint forePaint;
    //空心圓的畫筆
private Paint bgPaint;
    //規定圓的數量,預設是6個,如果有XML指定的數量,使用指定的
private int number = 6;
    //圓的半徑,規定預設值為10,如果有XML指定的值,使用指定的
private int r = 10;
    //定義空心圓的背景顏色,預設紅色,如果有XML指定的顏色,使用指定的
private int bgColor = Color.RED;
    //定義實心圓的背景顏色,預設藍色,如果有XML指定的顏色,使用指定的
private int foreColor = Color.BLUE;

    //該方法在我們java程式碼新增控制元件時回撥
public Indicator(Context context) {
        super(context);
        initPaint();
    }
    //該方法在我們XML檔案裡新增控制元件時回撥
public Indicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();       
    }
    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        initPaint();
    }
    //引數就是畫板,可以直接使用
@Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //畫多個空心圓,為了使圓步擠在一起,所以對x軸座標進行動態修改
for (int x = 0; x < number; x++) {
            canvas.drawCircle(60+x*r*3,60,r,bgPaint);
        }     

//畫實心圓,為使實心圓能夠進行X軸移動, 引數1加上了偏移量

canvas.drawCircle(60+offset, 60, r, forePaint); } //初始化畫筆物件 private void initPaint() { //建立畫筆的物件,用於畫實心圓   forePaint = new Paint(); //設定抗鋸齒 forePaint.setAntiAlias(true); //設定畫筆的樣式,為實心 forePaint.setStyle(Paint.Style.FILL); //設定畫筆的顏色 forePaint.setColor(foreColor); //設定畫筆的寬度 forePaint.setStrokeWidth(2);

//建立畫筆的物件,用於畫空心圓

bgPaint = new Paint();
bgPaint.setAntiAlias(true);
bgPaint.setStyle(Paint.Style.STROKE);
bgPaint.setColor(bgColor);
bgPaint.setStrokeWidth(2);
}
//移動的偏移量private float offset;
public void setoffset(int position, float positionOffset){
        //防止角標越界,取餘數
position %= number;
        //因為從一個圓到另一個圓經過3個半徑,兩個圓各兩個半徑,中間的間距是一個半徑
offset = position*3*r+positionOffset*3*r;
//關鍵:重新繪製自定義view的方法,十分常用invalidate();
}}

思考:偏移量 offset = position*3*r+positionOffset*3*r;這一句的作用,那我們可以試試看。
首先在程式碼中將偏移量改為offset = position*3*r;執行一下看看是什麼效果:

看完了效果圖大家可能覺得沒什麼影響啊,效果還是一樣。那我們把程式碼補全看看
      offset = position*3*r+positionOffset*3*r;
然後執行一下:

這兩個效果圖對比一下就知道了,加上後,小圓點在移動的時候加了從一個空心圓移動到另一個的滑動效果,
而不是直接到了下一個空心圓。
三、MainActivity:實現ViewPager的輪播效果(要是看不懂可以看我的上一篇關於ViewPager使用的部落格)
public class MainActivity extends AppCompatActivity {
    //建立handler物件,主要是為了實現ViewPager的自動滑動
private Handler handler = new Handler();
    //建立一個集合裝ViewPager載入的圖片控制元件
private List<View> views = new ArrayList<View>();
    private Indicator indicator;
    private ViewPager viewPager;
    @Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //呼叫封裝的方法進行自動的滑動
AutoScroll();
        //初始化ViewPageritem資料
initData();
        //初始化控制元件
viewPager = (ViewPager) findViewById(R.id.viewPager);
        indicator = (Indicator) findViewById(R.id.indicator);
        //設定ViewPager介面卡
viewPager.setAdapter(new MyPagerAdapter());
        //設定ViewPager的監聽器
viewPager.setOnPageChangeListener(new MyPagerListner());
    }
    //初始化ViewPageritem資料
private void initData() {
        for (int x = 0; x < 4; x++) {
            View inflate = getLayoutInflater().inflate(R.layout.pager_item, null);
            ImageView imgShow = (ImageView) inflate.findViewById(R.id.imgShow);
            //這裡就呼叫了系統的圖片來作為資源
  imgShow.setImageResource(R.mipmap.ic_launcher);
views.add(inflate);
}
}
private void AutoScroll() {
//使用這個方法,第二個引數可以指定方法多少秒後執行handler.postDelayed(new Runnable() {
@Overridepublic void run() {
//獲取當前的頁面下標int currentItem = viewPager.getCurrentItem();
//在原有的頁數加一就是下一個頁數,然後設定跳到這一頁,即可在次死迴圈裡實現自動輪播的效果viewPager.setCurrentItem(currentItem + 1);
//使用postDelayed(this,2000)可以讓 run()裡面的程式碼成為死迴圈handler.postDelayed(this, 2000);
}
}, 2000);
}
//建立ViewPager介面卡,重寫其四個構造方法class MyPagerAdapter extends PagerAdapter {
//設定ViewPageritem數量 @Overridepublic int getCount() {
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override public void destroyItem(ViewGroup container, int position, Object object) {
position %= views.size();
container.removeView(views.get(position));
}
//類似於listview裡的getView,引數1ViewPager的化身,引數2item的位置@Overridepublic Object instantiateItem(ViewGroup container, int position) {
  position %= views.size();
//從集合裡拿對應位置的圖片View view = views.get(position);
//imageView物件新增到ViewPager container.addView(view);
return view;
}
}
//
建立ViewPager的監聽事件class MyPagerListner implements ViewPager.OnPageChangeListener{
//ViewPager滑動時回撥 //引數1:item的位置 引數2:偏移的百分比,這個百分比用於接近於1 引數3:偏移量 @Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
indicator.setoffset(position,positionOffset);
}
//ViewPager選中時回撥@Overridepublic void onPageSelected(int position) {
}
//ViewPager滑動狀態改變時回撥 @Override public void onPageScrollStateChanged(int state) {

}
}
}
四、pager_item的佈局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
    <ImageView
android:id="@+id/imgShow"
android:layout_width="match_parent"
android:layout_height="200dp" />
</LinearLayout>

擴充套件深入:五、在自定義控制元件裡設定屬性:
熟知Studio的人都知道,在我們設計佈局時,比如要展示一個TextView文字,我們要設定他的一些屬性
是非常方便的,如下兩張圖,會自動彈出提示,這是因為控制元件的屬性Studio已經幫我們封裝好了。

我們自定義控制元件Indicator用到了諸多屬性,如空心圓的數量,半徑r,以及實心圓空心圓的顏色等等

上面的程式碼實現這些屬性,採取的是定義全域性變數來實現的。

怎麼使我們自定義的控制元件可以在佈局xml檔案裡定義呢

我們先來在佈局檔案中嘗試一下: 發現這樣並不可行,連提示都沒有,別說設定屬性了。 那我們現在就來實現它:
需要五個步驟: 0.values下建立一個xml資原始檔。
  1.在xml資原始檔裡定義表頭和屬性。
  2.在構造方法裡進行屬性之間的關聯。
  3.同步Gradle檔案,否則在xml佈局檔案裡依然沒有辦法引用。
  4.在xml佈局檔案裡進行使用。
按照步驟做:
1.在values下建立attrs資原始檔



2.比如改變空心圓和實心圓的顏色,以及空心圓的數量這三個屬性。
程式碼如下:
     <resources>
         <!--指定這些屬性都是誰的,注意這裡新增完屬性要在自定義View類構造方法裡使用-->
         <!--注意:當寫完這個屬性後,佈局XML檔案要想引用的話,必須要同步Gradle檔案-->
<declare-styleable name="Indicator">
         <!--定義控制元件顯示圓的數量 引數format是型別-->
 <attr name="setNumber" format="integer"></attr>
         <!--定義空心圓的顏色  引數format是型別,reference表示什麼都能裝-->
 <attr name="setBgColor" format="reference"></attr>
         <!--定義實心圓的顏色  引數format是型別,reference表示什麼都能裝-->
         <attr name="setForeColor" format="reference"></attr>
</declare-styleable> </resources>
     3.在Indicator類中,繼承了View並重寫了四個方法,其中有一個構造方法是在我們XML檔案裡新增控制元件時回撥
       我們就在這個方法中進行關聯。  
       具體程式碼如下:     
        //該方法在我們XML檔案裡新增控制元件時回撥
       public Indicator(Context context, AttributeSet attrs) {
            super(context, attrs);
            initPaint();
        //引用attrs檔案下,給自定義控制元件設定屬性,得到TypdeArray物件
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Indicator);
        //使用typedArray物件,把在自定義控制元件設定的屬性和XML檔案裡設定的屬性進行關聯,才算是完成
//注意:在XML檔案裡設定的型別屬性獲取時,也要是對應的型別
number = typedArray.getInteger(R.styleable.Indicator_setNumber,number);
       bgColor =typedArray.getInteger(R.styleable.Indicator_setBgColor,bgColor);
       foreColor=typedArray.getInteger(R.styleable.Indicator_setForeColor,foreColor);
4.同步Gradle檔案,點選下圖的按鈕即可。

                         5.在佈局中使用
       看一下效果:
       
     從效果圖上可以看出,空心圓的數量number,顏色bgColor以及實心圓的顏色foreColor都被改變了。
  程式碼完成到現在,還是會有一個bug:看一下當實心圓從最後一個空心圓滑動到第一個空心圓時,會發生什麼?
效果圖:

我們會發現,實心圓從最後一個空心圓滑出去的時候,還會繼續滑動,超出了空心圓的範圍,這樣做肯定是不切合實際的。
就需要在程式碼裡進行修改,在自定義Indicator類中設定偏移量時,加一個if判斷,具體程式碼和原因如下:
      private float offset;
      public void setoffset(int position, float positionOffset){
      //防止角標越界
position %= number;
      //因為從一個圓到另一個圓經過3個半徑,兩個圓各兩個半徑,中間的間距是一個半徑
offset = position*3*r+positionOffset*3*r;
      //position是從0開始遞增,而number則是從1開始遞增,當position==number-1時,實心圓滑動到了最後一個,
//取消掉positionOffset*3*r的滑動效果,將偏移量重新設定為position*3*r即可。
if(position==number-1){
          offset = position*3*r;
       }
       //關鍵:重新繪製自定義view的方法,十分常用
invalidate();
       }
再看一下效果:

這到這一步就大功告成了~

                 通過這個例子,確實證明了通過自定義view可以實現一些酷炫的效果。