【我的Android進階之旅】自定義控制元件之使用ViewPager實現可以預覽的畫廊效果,並且自定義畫面切換的動畫效果的切換時間
我們來看下效果
在這裡,我們實現的是,一個ViewPager來顯示圖片列表。這裡一個頁面,ViewPage展示了前後的預覽,我們讓預覽頁進行Y軸的壓縮,並設定透明度為0.5f,所有我們看到gif最後,左右兩邊的圖片有點朦朧感。讓預覽頁和主頁面有主從感。我們用分析程式碼,一一實現上面的效果。
第一步
新建一個Activity
public class GalleryActivity extends Activity { ViewPager viewPager ; GalleryAdapter adapter ; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gallery); viewPager = (ViewPager) findViewById(R.id.vp_gallery); adapter = new GalleryAdapter(this); viewPager.setAdapter(adapter); } }
GalleryAdapter 的程式碼
public class GalleryAdapter extends PagerAdapter { int[] imgs = {R.mipmap.main_0n, R.mipmap.main_1n, R.mipmap.main_2n}; private Context mContext; public GalleryAdapter(Context context) { this.mContext = context; } @Override public Object instantiateItem(ViewGroup container, int position) { ImageView iv = new ImageView(container.getContext()); iv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); iv.setBackground(mContext.getDrawable(imgs[position % 3])); container.addView(iv); return iv; } @Override public int getCount() { return imgs.length; } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View)object); } }
佈局檔案程式碼
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v4.view.ViewPager android:id="@+id/vp_gallery" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
以上程式碼相信各位都有寫過,一個簡單的圖片瀏覽ViewPager,就不贅述了。現在我們將程式碼改成為可以顯示三個頁面,一個主頁面和前後兩個預覽頁,並設定預覽頁進行Y軸壓縮和透明度為0.5f.
public class GalleryActivity extends Activity {
ViewPager viewPager;
GalleryAdapter adapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gallery);
viewPager = (ViewPager) findViewById(R.id.vp_gallery);
adapter = new GalleryAdapter(this);
viewPager.setAdapter(adapter);
//是否對padding進行裁剪
viewPager.setClipToPadding(false);
int itemWidth = (getResources().getDisplayMetrics().widthPixels) / 3;
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) viewPager.getLayoutParams();
layoutParams.leftMargin = itemWidth / 2;
layoutParams.rightMargin = itemWidth / 2;
//設定頁面的左右padding
viewPager.setLayoutParams(layoutParams);
//設定預載入為3
viewPager.setOffscreenPageLimit(3);
//設定頁面之間的margin為0
viewPager.setPageMargin(0);
viewPager.setPageTransformer(true, (view, position) -> {
if (position < 0) {
view.setScaleY(0.2f * position + 1);
view.setAlpha(1f + 0.5f * position);
} else if (position < 1) {
view.setAlpha(1f - 0.5f * position);
view.setScaleY(-0.2f * position + 1);
} else {
view.setAlpha(0.3f);
view.setScaleY(0.8f);
}
});
}
}
佈局檔案的修改也很重要
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false">
<android.support.v4.view.ViewPager
android:id="@+id/vp_gallery"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
分析程式碼:
我們給ViewPager 設定左右 Padding.並通過 viewPager.setClipToPadding(false)讓viewPager不對其子控制元件進行Padding部分的裁剪。這樣每一個ViewPager就有Padding部分來預覽左右的內容,但是這樣還不夠,我們還需要設定FrameLayout 的 android:clipChildren=“false”,這個屬性是代表FrameLayout 允許子控制元件的內容超出其內容,不對超出的部分進行裁剪。這樣我們就可以得到左右兩邊的預覽部分了。接著,我們再來看
viewPager.setPageTransformer(true, (view, position) -> {
if (position < 0) {
view.setScaleY(0.2f * position + 1);
view.setAlpha(1f + 0.5f * position);
} else if (position < 1) {
view.setAlpha(1f - 0.5f * position);
view.setScaleY(-0.2f * position + 1);
} else {
view.setAlpha(0.3f);
view.setScaleY(0.8f);
}
});
setPageTransformer這個方法是用來設定ViewPager的轉場動畫, 可以設定透明度,XY軸縮排等,我們主要了解這個position值,當向後滑動一個頁面,他的變化為0->1,當向前滑動時候,他的變化為-1->0;
現在是不是有點酷炫了。但是如果滑動過快的話,這些變化並不是很明顯,我們想讓ViewPager 的切換動畫時間久一點,再久一點! 我們去看下ViewPager 的原始碼,發現有個smoothScrollTo方法
/**
* Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
*
* @param x the number of pixels to scroll by on the X axis
* @param y the number of pixels to scroll by on the Y axis
* @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
*/
void smoothScrollTo(int x, int y, int velocity) {
if (getChildCount() == 0) {
// Nothing to do.
setScrollingCacheEnabled(false);
return;
}
int sx;
boolean wasScrolling = (mScroller != null) && !mScroller.isFinished();
if (wasScrolling) {
// We're in the middle of a previously initiated scrolling. Check to see
// whether that scrolling has actually started (if we always call getStartX
// we can get a stale value from the scroller if it hadn't yet had its first
// computeScrollOffset call) to decide what is the current scrolling position.
sx = mIsScrollStarted ? mScroller.getCurrX() : mScroller.getStartX();
// And abort the current scrolling.
mScroller.abortAnimation();
setScrollingCacheEnabled(false);
} else {
sx = getScrollX();
}
int sy = getScrollY();
int dx = x - sx;
int dy = y - sy;
if (dx == 0 && dy == 0) {
completeScroll(false);
populate();
setScrollState(SCROLL_STATE_IDLE);
return;
}
setScrollingCacheEnabled(true);
setScrollState(SCROLL_STATE_SETTLING);
final int width = getClientWidth();
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
final float distance = halfWidth + halfWidth
* distanceInfluenceForSnapDuration(distanceRatio);
int duration;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
duration = (int) ((pageDelta + 1) * 100);
}
duration = Math.min(duration, MAX_SETTLE_DURATION);
// Reset the "scroll started" flag. It will be flipped to true in all places
// where we call computeScrollOffset().
mIsScrollStarted = false;
mScroller.startScroll(sx, sy, dx, dy, duration);
ViewCompat.postInvalidateOnAnimation(this);
}
重點注意mScroller.startScroll(sx, sy, dx, dy, duration)這行程式碼,這個duration是控制整個頁面切換時間的關鍵。我們再來看看mScroller
private Scroller mScroller;
說明在ViewPager 中這是受私有保護的,但是我們可以通過反射替換掉系統的這個引數。
try {
// 通過class檔案獲取mScroller屬性
Field mField = ViewPager.class.getDeclaredField("mScroller");
mField.setAccessible(true);
GalleryViewPagerScroller mScroller = new GalleryViewPagerScroller(mViewPager.getContext(),new AccelerateInterpolator());
mField.set(mViewPager, mScroller);
} catch (Exception e) {
e.printStackTrace();
}
以下是GalleryViewPagerScroller的程式碼
public class GalleryViewPagerScroller extends Scroller {
private int mDuration = 800;
public GalleryViewPagerScroller(Context context) {
super(context);
}
public GalleryViewPagerScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
@Override
public void startScroll(int startX, int startY, int dx, int dy) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
public int getmDuration() {
return mDuration;
}
public void setmDuration(int time) {
mDuration = time;
}
}
所以,整個過程就是,通過重寫startScroll方法設定mDuration為我們想要的時間 ,並例項化一個Scroller 物件,並通過反射把ViewPager 的 私有屬性mScroller給替換掉。這樣,我們就可以設定ViewPager 轉場動畫的延遲了.可以實現一些酷炫的ViewPager效果了,趕快用上吧