android自定義View之從入門到放棄(三)實現viewPager詳解 記錄學習

本次博客主要介紹的是如何使用自定義ViewGroup實現viewPager 的詳細介紹 :
從中你還可以學習到scrollTo() 和scrollBy()的詳細使用的方法 ,手勢識別器的使用
好了,廢話不多比比 開始咯
首先:實現ViewPager的思路:
在這裏插入圖片描述
1.首先需要幾張圖片,這你可以隨便來幾張圖片,最好寬高都一樣 相差別太大
從上圖我們就可以拿到圖片的left,top,right,bottom的值 ,然後根據ViewGroup特有的Layout()方法將圖片的位置進行確定
上代碼:
佈局文件中:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    tools:context=".widget.LunBoActivity">
<com.example.zidingyidemo.widget.LunboView
    android:id="@+id/lunboView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />
</LinearLayout>

要實例化的activity:

public class LunBoActivity extends AppCompatActivity {
    int arr[] = {R.drawable.first,R.drawable.second,R.drawable.third,R.drawable.four,R.drawable.five};
    private LunboView lunboView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lun_bo);
        lunboView = findViewById(R.id.lunboView);
        for(int i=0;i<arr.length;i++){
            ImageView imageView = new ImageView(this);
            imageView.setBackgroundResource(arr[i]);
            lunboView.addView(imageView);
        }
    }
}

從上我們可以看到 我們在activity中將我們準備好的圖片資源放入了一個數組中,然後再代碼中進行imageView的實例化 將準備好的圖片資源放入到我們的imageView中了
自定義的View -->精華所在

public class LunboView extends ViewGroup {
 
    public LunboView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }
    
    //View的具體位置
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
       //拿到孩子  確定孩子的具體位置
        for(int i=0;i<getChildCount();i++){//循環遍歷孩子個數
            View childView = getChildAt(i);//拿到孩子們-->imageView
            childView.layout(i*getWidth(),0,(i+1)*getWidth(),getHeight());//確定孩子的具體位置
         }
    }

}

當完成以上步驟 你就可以將圖片進行顯示出來了 ,但是 由於我們定義的孩子位置的座標 當前只能看到第一張圖片(佔滿屏幕)
想要實現左右滑動顯示其他的圖片 這時我們就要接觸到手勢識別器(GestureDetector )
手勢識別器的詳細使用步驟:
* 1.定義出來
* 2.實例化 將想要的方法進行實現(其中有跟多的方法 如滾動事件監聽,雙擊事件監聽 按需重寫方法就可以)
* 3.在onTouchEvent中註冊(傳遞給手勢識別器)
一起來看代碼:

public class LunboView extends ViewGroup {
    /**
     * 手勢識別器
     * 1.定義出來
     * 2.實例化 將想要的方法進行實現
     * 3.在onTouchEvent中註冊(傳遞給手勢識別器)
     *
     */
    private GestureDetector detector;//1.定義出來
    public LunboView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        scroller = new Scroller(context);
        detector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){ //2.實例化 將想要的方法進行實現
            /**
             *
             * @param e1  手指按下
             * @param e2   手指擡起
             * @param distanceX  距離x
             * @param distanceY   距離y
             * @return
             */
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
               // return super.onScroll(e1, e2, diseX, distanceY);
                scrollBy((int) distanceX,getScrollY());   //getScrollY初始值   0
                return true;
            }
        });
    }


    //View的具體位置
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
       //拿到孩子  確定孩子的具體位置
        for(int i=0;i<getChildCount();i++){
            View childView = getChildAt(i);
            childView.layout(i*getWidth(),0,(i+1)*getWidth(),getHeight());
         }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);//這一步可以省略 因爲下面返回了true
       detector.onTouchEvent(event);//3.在onTouchEvent中註冊(傳遞給手勢識別器)
        return true;
    }

}

從上面就可以對圖片進行滑動 但是會有一個問題 就是當我們滑動未滑動到下一張圖片時 會停留再當前你滑動的位置 ,下面我們就解決這個bug 但是之前 還是來介紹一下scrollTo()和scrollBy()
首先通過字面意思我們就可以得知 scrollTo(x,y)是到達指定的位置
源碼:

 public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

是不是有點懵 ,不急 我們記住他的字面意思 然後我們來看一下scrollBy()
scrollBy(x,y) 他的意思在初始值的狀態下移動到指定位置的間隔距離(x,y)
源碼:

  public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

從中我們可以看出 scrollBy源碼裏面又執行了scrollTo()
其中我們可以看到mScrollX,和mScrollY 他們其實就是我們頁面的座標原點 就是0,0
由此就可以清晰的區分出scrollTo和ScrollBy()
scrollTo(x,y)->到達我們指定的x,y座標位置
scrollBy(x,y)->達到我們指定的x,y座標的距離
舉個簡單的例子:
假設當前我們的座標值x,y爲(20,0) 當我們想要移動到(40,0)這個座標的時候
scrollTo(40,0); scrollBy(20,0)
當然還有scrollTo()和scrollBy()都是在內部移動的 不會改變當前視圖的位置 (很重要)
在這裏插入圖片描述
從上圖我們可以清晰的看到 :當我們想要顯示下一張圖片時我們當前的left,top值 要從之前的(getWidth,0)移動到屏幕(0,0)的位置纔可以顯示,由此可得 上一張圖片要顯示在當前屏幕上時 我們的left,top值就從(-getWidth,0)變成了(0,0) 由此我們可以得出結論,在以當前屏幕爲參照物的時候,當我們想要顯示下一張圖片 我們的座標是減少了的,當我們想要顯示上一張圖片時 我們的座標反而是加加了 但是當我們想要顯示下一張圖片時我們的index是要加一的

然後就可以根據MotionEvent的擡起和按下的時候,獲取當前的x座標,然後計算出偏移的距離,根據偏移的距離進行判斷,如果偏移的距離大於整個屏幕寬度的一半就顯示下一張圖片,反之還顯示當前圖片

@Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
       detector.onTouchEvent(event);
        switch (event.getAction()){
            //手指按下
            case MotionEvent.ACTION_DOWN:
                 startX= event.getX();//獲取按下的時候的X值
                break;
            case MotionEvent.ACTION_UP:
                 float endX = event.getX();//獲取擡起的時候的X值
                 float pian = endX-startX;//x偏移量
                int currentindex =  current;
                Log.i("startend","start"+startX+"endx"+endX);
                 if((startX-endX)>getWidth()/2){
                     //顯示下一個頁面
                     currentindex++;  //注意這裏時下標位置
                 }else if((endX-startX)>getWidth()/2){
                     currentindex--;
                 }
                 //根據下標移動到具體頁面
                 scrollto(currentindex);
                break;
        }
        return true;
    }

    private void scrollto(int currentindex) {
        Log.i("nidaodiyou","@"+getChildCount());
        if(currentindex<0){
            currentindex = 0;
        }
        if(currentindex>getChildCount()-1){
            currentindex = getChildCount()-1;

        }
        current = currentindex;

        //移動到指定位置(瞬間)
        scrollTo(current*getWidth(),getScrollY());
    }

在這裏插入圖片描述
其中下標的確認由圖清楚可得 我就不再做更多的解釋了

這時我們就可以解決這個bug了 但是新的問題又出現了 當我們滑動超過中間的一半 是瞬時滑動到了上一張或者下一張 沒有緩慢划過去的那種動畫 感覺不美觀
這時我們可以調用系統提供的Scroller這個類來解決 這裏不做過多的介紹 我就將完整的代碼給貼出來

public class LunboView extends ViewGroup {
    private  float startX;
    private int current;  //當前頁面的下標位置
    //private Scroller scroller;
    private android.widget.Scroller scroller;
    /**
     * 手勢識別器
     * 1.定義出來
     * 2.實例化 將想要的方法進行實現
     * 3.在onTouchEvent中註冊(傳遞給手勢識別器)
     *
     */
    private GestureDetector detector;
    public LunboView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        scroller = new Scroller(context);
        detector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
            /**
             *
             * @param e1  手指按下
             * @param e2   手指擡起
             * @param distanceX  距離x
             * @param distanceY   距離y
             * @return
             */
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
               // return super.onScroll(e1, e2, diseX, distanceY);
                scrollBy((int) distanceX,getScrollY());   //getScrollY初始值   0
                return true;
            }
        });
    }


    //View的具體位置
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
       //拿到孩子  確定孩子的具體位置
        for(int i=0;i<getChildCount();i++){
            View childView = getChildAt(i);
            childView.layout(i*getWidth(),0,(i+1)*getWidth(),getHeight());
         }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
       detector.onTouchEvent(event);
        switch (event.getAction()){
            //手指按下
            case MotionEvent.ACTION_DOWN:
                 startX= event.getX();
                break;
            case MotionEvent.ACTION_UP:
                 float endX = event.getX();
                 float pian = endX-startX;
                int currentindex =  current;
                Log.i("startend","start"+startX+"endx"+endX);
                 if((startX-endX)>getWidth()/2){
                     //顯示下一個頁面
                     currentindex++;  //注意這裏時下標位置
                 }else if((endX-startX)>getWidth()/2){
                     currentindex--;
                 }
                 //根據下標移動到具體頁面
                 scrollto(currentindex);
                break;
        }
        return true;
    }

    private void scrollto(int currentindex) {
        Log.i("nidaodiyou","@"+getChildCount());
        if(currentindex<0){
            currentindex = 0;
        }
        if(currentindex>getChildCount()-1){
            currentindex = getChildCount()-1;

        }
        current = currentindex;

        int  distance = current*getWidth()-getScrollX();
        scroller.startScroll(getScrollX(),getScrollY(),distance,0);

        invalidate();  //導致ondraw()重新執行  還有computeScroll


        //移動到指定位置(瞬間)
        //scrollTo(current*getWidth(),getScrollY());
    }

    //
    @Override
    public void computeScroll() {
     //   super.computeScroll();
        if(scroller.computeScrollOffset()){
            float currx = scroller.getCurrX();
            scrollTo((int) currx,0);
            invalidate();  //再去執行
        }
    }
}

ok,這樣就完成了效果 喜歡的話請點一個小贊👍 你的支持是我最大的動力!!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章