1. 程式人生 > >【我的Android進階之旅】自定義控制元件之使用ViewPager實現可以預覽的畫廊效果,並且自定義畫面切換的動畫效果的切換時間

【我的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效果了,趕快用上吧

寫 在最後

在這裡插入圖片描述