學(xué)習自定義View,這個對于初學(xué)者來說確實有點難度。因為這需要你熟悉View繪制的基本流程,不僅如此,你還需要熟悉手勢識別、解決事件沖突等知識。這是一系列綜合性的學(xué)習,如果想在Android方面進階,你必須攻克這個首要技能。今天,我們的目的是學(xué)習自定義ViewGroup實現(xiàn)ViewPager類似的滑動效果。
先來看一下我們要實現(xiàn)的效果,其實這個和原生ViewPager效果一樣的。
此篇是為了學(xué)習自定義View為目的文章,因為處于初學(xué)階段,我們先從簡單的實現(xiàn)效果一步步進階。自定義ViewGroup必須重寫父類的一個方法onLayout(),我們來看一下這個方法的介紹。
大意:這個方法用于對子視圖進行布局,分配子視圖大小和位置。ViewGroup必須重寫這個方法,根據(jù)子視圖的左上、右下角的坐標來確定其大小和位置。
代碼及實現(xiàn)
那么,讓我們繼承ViewGroup來實現(xiàn)這樣效果。先看一下我們的MyViewPager類,它簡單的對子視圖進行了布局。
/**
* @Created by xww.
* @Creation time 2018/8/13.
*/
public class MyViewPager extends ViewGroup {
private GestureDetector mGestureDetector;
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
scrollBy((int) distanceX, getScrollY());
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
//對于每個子View進行布局
View childView = getChildAt(i);
childView.layout(i * getWidth(), t, (i + 1) * getWidth(), b);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
return true;
}
}
對于每個子視圖(圖片)進行了全屏幕的占用,我們看onLayout()方法里的代碼,先獲取了每個子視圖,對子視圖進行大小和位子的設(shè)置。這里我們依照ViewPager,將子視圖橫向排成一排,每一張圖片占一整個屏幕。下面是我們添加圖片的代碼,不予解釋了。
private int[] imgs = {R.drawable.bg_01, R.drawable.bg_02, R.drawable.bg_03, R.drawable.bg_04, R.drawable.bg_05, R.drawable.bg_06};
private void initViewPager() {
for (int img : imgs) {
AppCompatImageView imageView = new AppCompatImageView(getContext());
imageView.setBackgroundResource(img);
myViewpager.addView(imageView);
}
}
這里我用到了GestureDetector類,這個類指明是手勢識別器,它內(nèi)部封裝了一些常用的手勢操作的接口,比如單機、雙擊、長按、滾動等。因為我們用到滾動手勢,所以我們在這里中斷(處理)它的事件。對于scrollTo、scrollBy、Scroller有疑問的可以看我之前的一篇文章:理解scrollTo、scrollBy、Scroller的一些區(qū)別及用法,掌握視圖滾動方式以及屏幕坐標系
根據(jù)手指滑動產(chǎn)生的distanceX,我們進行子視圖的滾動,并且在onTouchEvent中處理我們的手勢識別器的事件。到了這一步,我們變可以看到它的滑動效果。
是不是和我們預(yù)想的不太一樣?動是可以動了,但它不會自己動,還會卡在中間,是不是感覺很不爽。那這樣子的話,我們就得借助另一個滾動視圖的類Scroller類。同理,這個類的用法在上面一個快捷鏈接中也有說明,這里我就不講它是如何使用了。我們看看最終修改后的MyViewPager類。
/**
* @Created by xww.
* @Creation time 2018/8/13.
*/
public class MyViewPager extends ViewGroup {
private GestureDetector mGestureDetector;
private int currentIndex;
private int startX;
private int endX;
private Scroller mScroller;
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
mGestureDetector = new GestureDetector(context, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
scrollBy((int) distanceX, getScrollY());
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
//對于每個子View進行布局
View childView = getChildAt(i);
childView.layout(i * getWidth(), t, (i + 1) * getWidth(), b);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
endX = (int) event.getX();
int tempIndex = currentIndex;
if (startX - endX > getWidth() / 2) { //從右往左滑動
tempIndex++;
} else if (endX - startX > getWidth() / 2) { //從左往右滑動
tempIndex--;
}
scrollIndex(tempIndex);
break;
}
return true;
}
/**
* 移動到指定頁面
*/
private void scrollIndex(int tempIndex) {
//第一頁,無法繼續(xù)向左滑動
if (tempIndex < 0) {
tempIndex = 0;
}
//同理,最后一頁無法向右滑動
if (tempIndex > getChildCount() - 1) {
tempIndex = getChildCount() - 1;
}
currentIndex = tempIndex;
mScroller.startScroll(getScrollX(), 0, currentIndex * getWidth() - getScrollX(), 0);
postInvalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), 0);
postInvalidate();
}
}
}
它實現(xiàn)的思路:我們給予每張圖片一個下標,當我們將當前頁面滾動的距離大于屏幕寬度的一半時,子視圖將自動滾動到下一張圖;同理,當前頁面滾動的距離小于屏幕寬度的一半,那將自動恢復(fù)原始狀態(tài),不會滾動到下一張圖。主要是在onTouchEvent()方法中,標記起始點和滑動后的最終點的坐標距離,用滑動距離比對屏幕的一半寬度,然后在處理是否要切換圖片。代碼實現(xiàn)不是很難,多于理解它實現(xiàn)的原理,相信你很快就會掌握自定義View。