本次博客主要介紹的是如何使用自定義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,這樣就完成了效果 喜歡的話請點一個小贊👍 你的支持是我最大的動力!!!!