1. 程式人生 > >Android-通過自定義ViewPager(中間放大效果)

Android-通過自定義ViewPager(中間放大效果)

/**稀土掘金,時光不老**/

大家好,很早就想寫部落格了,一是工作忙,二是缺乏原創性,三當然是自己的能力不夠啦,寫這篇部落格是很惶恐。。。。請多多包涵

/****************************

--------   ---------

                                -------

***************************************/

好了,回到正題上來,最近看到韓海龍的部落格 http://hanhailong.com/

看了裡面的效果不錯,就試著模仿了一下,加了一些自己的東西,好啦,廢話不說了,先看效果圖如下:

 

一. 首先說說核心點吧:

1. 在包裹ViewPager的父佈局 和 ViewPager中  的android:clipChildren設定為false,意味著不限制子View在    其範圍內,也就是說子view可以超  出父view的範圍

2. 左右滑動的縮放通過ViewPager的PageTransformer來實現縮放動畫

        3. 螢幕的點選攔截事件分發給ViewPager

        4. 自定義Scroll類 ,用於調節ViewPager左右滑動速度

OK ,核心講完了,進入我們程式碼,佈局模組,非常簡單----------------------|

      |

    |

看這裡,定義佈局模組--主佈局-activity_gallery_view_pager

<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows=
"true" android:orientation="vertical" tools:context=".GalleryViewPagerActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay"> </android.support.v7.widget.Toolbar> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_main"/> </LinearLayout>

    |

      |

     |

看這裡,定義佈局模組--VeiwPager佈局-content_main

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/page_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:clipChildren="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!--RelativeLayout和自定義的ClipViewPager都各自添加了一個屬性android:clipChildren=”false”    clipChildren的意思是是否限制子View在其範圍內,這個預設是true    也就是預設是限制子view在其範圍的-->
<view.ClipViewPager
android:id="@+id/viewpager"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
android:clipChildren="false"
android:overScrollMode="never" />
</RelativeLayout>

上面的RelativeLayout和自定義的ClipViewPager都各自添加了一個屬性android:clipChildren=”false”,clipChildren的意思是是否限制子View在其範圍內,這個預設是true,也就是預設是限制子view在其範圍的.

給ViewPager設定滑動速度通過自定義Scroller實現,關於Scroller有興趣的童鞋可以去看看

mViewPager = (ClipViewPager) findViewById(R.id.viewpager);
/**調節ViewPager的滑動速度**/
mViewPager.setSpeedScroller(300);
給ViewPager設定縮放動畫,這裡通過PageTransformer來實現
/**ViewPager設定縮放動畫,這裡通過PageTransformer來實現**/
mViewPager.setPageTransformer(true, new ScalePageTransformer());
再來看ScalePageTransformer的實現,核心就是實現transformPage(View page, float position)這個方法

這段程式碼檢視韓海龍大神的(站在前輩的基礎上學習)

/**
 * Created by HanHailong on 15/9/27.
 *
 * description:  其實核心程式碼就是這個動畫實現部分,這裡設定了一個最大縮放和最小縮放比例,
* 當處於最中間的view往左邊滑動時,它的position值是小於0的,
* 並且是越來越小,它右邊的viewposition是從1逐漸減小到0*/
public class ScalePageTransformer implements ViewPager.PageTransformer {

    public static final float MAX_SCALE = 1.2f;
    public static final float MIN_SCALE = 0.6f;
/**核心就是實現transformPage(View page, float position)這個方法**/
@Override
public void transformPage(View page, float position) {

        if (position < -1) {
            position = -1;
} else if (position > 1) {
            position = 1;
}

        float tempScale = position < 0 ? 1 + position : 1 - position;
        float slope = (MAX_SCALE - MIN_SCALE) / 1;
//一個公式
float scaleValue = MIN_SCALE + tempScale * slope;
page.setScaleX(scaleValue);
page.setScaleY(scaleValue);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            page.getParent().requestLayout();
}
    }
}

呼 呼 ...看看自己沒啥遺漏了。美女帥哥們別急-------下面我們看下在Activity的完整實現程式碼(必要的註釋已經新增 )
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import adapter.TubatuAdapter;
import utils.ScalePageTransformer;
import view.ClipViewPager;
/**
 * Created by wujian on 2016/3/23.
 * description: 畫廊式中間放大效果
*/
public class GalleryViewPagerActivity extends AppCompatActivity {
    private final static float TARGET_HEAP_UTILIZATION = 0.75f;
    private TubatuAdapter mPagerAdapter;
    private ClipViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gallery_view_pager);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
initFindView();
initData();
}

    private void initFindView() {

        mViewPager = (ClipViewPager) findViewById(R.id.viewpager);
/**調節ViewPager的滑動速度**/
mViewPager.setSpeedScroller(300);
/**ViewPager設定縮放動畫,這裡通過PageTransformer來實現**/
mViewPager.setPageTransformer(true, new ScalePageTransformer());
List<String> strList = Arrays.asList("現代", "簡約", "歐式", "中式", "美式", "地中海", "東南亞", "日式");
/**
         * 需要將整個頁面的事件分發給ViewPager,不然的話只有ViewPager中間的view能滑動,其他的都不能滑動,
* 這是肯定的,因為ViewPager總體佈局就是中間那一塊大小,其他的子佈局都跑到ViewPager外面來了
*/
findViewById(R.id.page_container).setOnTouchListener(new View.OnTouchListener() {
            @Override
public boolean onTouch(View v, MotionEvent event) {
                return mViewPager.dispatchTouchEvent(event);
}
        });
mPagerAdapter = new TubatuAdapter(this,strList);
mViewPager.setAdapter(mPagerAdapter);
}

    private void initData() {
        List<Integer> list = new ArrayList<>();
list.add(R.mipmap.style_xiandai);
list.add(R.mipmap.style_jianyue);
list.add(R.mipmap.style_oushi);
list.add(R.mipmap.style_zhongshi);
list.add(R.mipmap.style_meishi);
list.add(R.mipmap.style_dzh);
list.add(R.mipmap.style_dny);
list.add(R.mipmap.style_rishi);
/**這裡需要將setOffscreenPageLimit的值設定成資料來源的總個數,如果不加這句話,會導致左右切換異常;**/
mViewPager.setOffscreenPageLimit(list.size());
mPagerAdapter.addAll(list);
}
}

來來,瞧瞧上面Activity程式碼的解釋

一是setOffscreenPageLimit的值設定成資料來源的總個數,如果不加這句話,會導致左右切換異常;

二是需要將整個頁面的事件分發給ViewPager,不然的話只有ViewPager中間的view能滑動,其他的都不能滑動,這是肯定   的,因為ViewPager總體佈局就是中間那一塊大小,其他的子佈局都跑到ViewPager外面來了;

三是你發現ViewPager加了setOnTouchListener方法後,滑動是可以了,但是點選左右兩邊不能切換,這裡需要重寫        ViewPager的dispatchTouchEvent方法;

四是ViewPager設定了setSpeedScroller(300)設定滑動時間為300毫秒;

下面看ClipViewPager程式碼

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import java.lang.reflect.Field;
import utils.SpeedScroller;
/**
 * Created by wujian 15/9/27.
 */
public class ClipViewPager extends ViewPager {

    public ClipViewPager(Context context) {
        super(context);
}

    public ClipViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
}

    @Override
public boolean dispatchTouchEvent(MotionEvent ev) {

        if (ev.getAction() == MotionEvent.ACTION_UP) {
            View view = viewOfClickOnScreen(ev);
            if (view != null) {
                int index = indexOfChild(view);
                if (getCurrentItem() != index) {
                    setCurrentItem(indexOfChild(view));
}
            }
        }
        return super.dispatchTouchEvent(ev);
}

    /**
     * @param ev
* @return
*/
private View viewOfClickOnScreen(MotionEvent ev) {
        int childCount = getChildCount();
        int[] location = new int[2];
        for (int i = 0; i < childCount; i++) {
            View v = getChildAt(i);
v.getLocationOnScreen(location);
            int minX = location[0];
            int minY = getTop();
            int maxX = location[0] + v.getWidth();
            int maxY = getBottom();
            float x = ev.getX();
            float y = ev.getY();
            if ((x > minX && x < maxX) && (y > minY && y < maxY)) {
                return v;
}
        }
        return null;
}

    /**利用java反射機制,將自定義ScrollViewPager結合來調節ViewPager的滑動效果**/
public void setSpeedScroller(int duration) {
        try {
            Field mScroller = null;
mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
SpeedScroller scroller = new SpeedScroller(this.getContext(),
                    new AccelerateInterpolator());
mScroller.set(this, scroller);
scroller.setmDuration(duration);
}catch(NoSuchFieldException e){

        }catch (IllegalArgumentException e){

        }catch (IllegalAccessException e){

        }
    }
}

 |

 |

       ……...............看這裡  O(∩_∩)O

實現原理就是手指點選螢幕,如果點選的位置恰好落在ViewPager某個子View範圍內,就讓ViewPager切換到哪個子View!viewOfClickOnScreen方法是獲取手指點選ViewPager中的哪個子View,最後呼叫setCurrentItem切換到相應的子View,其中設定ViewPager的滑動速度通過Scroller實現

/**利用java反射機制,將自定義ScrollViewPager結合來調節ViewPager的滑動效果**/
下面看SpeedScroller程式碼
importandroid.content.Context;
import android.view.animation.Interpolator;
import android.widget.Scroller;
/**
 * Created by wujian on 2016/3/23.
 * description: 自定義Scroll類,用於調節滑動速度
*/
public class SpeedScroller extends Scroller {
    private int mDuration = 1500;
    public SpeedScroller(Context context) {
        super(context);
}

    public SpeedScroller(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 void setmDuration(int time) {
        mDuration = time;
}

    public int getmDuration() {
        return mDuration;
}
}

霍霍  差點忘了還有程式碼  TubatuAdapter-------  (*^__^*) 嘻嘻……

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by wujian on 2016/3/23.
 * RecyclingPagerAdapterJake WhartonAndroid大神封裝的可用於複用的PagerAdapter*/
public class TubatuAdapter extends RecyclingPagerAdapter {

    private List<Integer> mList;
    private Context mContext;
    private List<String> strList;
    public TubatuAdapter(Context context,List<String> strList) {
        mList = new ArrayList<>();
mContext = context;
        this.strList = strList;
}

    public void addAll(List<Integer> list) {
        mList.addAll(list);
notifyDataSetChanged();
}

    @Override
public View getView(final int position, View convertView, ViewGroup container) {
        ImageView imageView = null;
        if (convertView == null) {
            imageView = new ImageView(mContext);
} else {
            imageView = (ImageView) convertView;
}
        imageView.setTag(position);
imageView.setImageResource(mList.get(position));
imageView.setOnClickListener(new View.OnClickListener() {
            @Override
public void onClick(View v) {
                Toast.makeText(mContext, strList.get(position), Toast.LENGTH_SHORT).show();
}
        });
        return imageView;
}

    @Override
public int getCount() {
        return mList.size();
}
}

補充一下,RecyclingPagerAdapterJake WhartonAndroid大神封裝的可用於複用的PagerAdapter。如下,請看---------------------------

import android.support.v4.view.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
/**
 * A {@link PagerAdapter} which behaves like an {@link android.widget.Adapter} with view types and
 * view recycling.
 */
public abstract class RecyclingPagerAdapter extends PagerAdapter {
    static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
    private final RecycleBin recycleBin;
    public RecyclingPagerAdapter() {
        this(new RecycleBin());
}

    RecyclingPagerAdapter(RecycleBin recycleBin) {
        this.recycleBin = recycleBin;
recycleBin.setViewTypeCount(getViewTypeCount());
}

    @Override
public void notifyDataSetChanged() {
        recycleBin.scrapActiveViews();
        super.notifyDataSetChanged();
}

    @Override
public final Object instantiateItem(ViewGroup container, int position) {
        int viewType = getItemViewType(position);
View view = null;
        if (viewType != IGNORE_ITEM_VIEW_TYPE) {
            view = recycleBin.getScrapView(position, viewType);
}
        view = getView(position, view, container);
container.addView(view);
        return view;
}

    @Override
public final void destroyItem(ViewGroup container, int position, Object object) {
        View view = (View) object;
container.removeView(view);
        int viewType = getItemViewType(position);
        if (viewType != IGNORE_ITEM_VIEW_TYPE) {
            recycleBin.addScrapView(view, position, viewType);
}
    }

    @Override
public final boolean isViewFromObject(View view, Object object) {
        return view == object;
}

    /**
     * <p>
* Returns the number of types of Views that will be created by
     * {@link #getView}. Each type represents a set of views that can be
     * converted in {@link #getView}. If the adapter always returns the same
     * type of View for all items, this method should return 1.
     * </p>
* <p>
* This method will only be called when when the adapter is set on the
     * the {@link AdapterView}.
     * </p>
*
     * @return The number of types of Views that will be created by this adapter
     */
public int getViewTypeCount() {
        return 1;
}

    /**
     * Get the type of View that will be created by {@link #getView} for the specified item.
     *
     * @param position The position of the item within the adapter's data set whose view type we
     *                 want.
     * @return An integer representing the type of View. Two views should share the same type if one
     * can be converted to the other in {@link #getView}. Note: Integers must be in the
     * range 0 to {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can
     * also be returned.
     * @see #IGNORE_ITEM_VIEW_TYPE
     */
@SuppressWarnings("UnusedParameters") // Argument potentially used by subclasses.
public int getItemViewType(int position) {
        return 0;
}

    /**
     * Get a View that displays the data at the specified position in the data set. You can either
     * create a View manually or inflate it from an XML layout file. When the View is inflated, the
     * parent View (GridView, ListView...) will apply default layout parameters unless you use
     * {@link android.view.LayoutInflater#inflate(int, ViewGroup, boolean)}
     * to specify a root view and to prevent attachment to the root.
     *
     * @param position    The position of the item within the adapter's data set of the item whose view
     *                    we want.
     * @param convertView The old view to reuse, if possible. Note: You should check that this view
     *                    is non-null and of an appropriate type before using. If it is not possible to convert
     *                    this view to display the correct data, this method can create a new view.
     *                    Heterogeneous lists can specify their number of view types, so that this View is
     *                    always of the right type (see {@link #getViewTypeCount()} and
     *                    {@link #getItemViewType(int)}).
     * @param container   The parent that this view will eventually be attached to
     * @return A View corresponding to the data at the specified position.
     */
public abstract View getView(int position, View convertView, ViewGroup container);
}
再來一個RecycleBin類
import android.os.Build;
import android.util.SparseArray;
import android.view.View;
/**
 * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
 * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
 * start of a layout. By construction, they are displaying current information. At the end of
 * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
 * could potentially be used by the adapter to avoid allocating views unnecessarily.
 * <p/>
* This class was taken from Android's implementation of {@link android.widget.AbsListView} which
 * is copyrighted 2006 The Android Open Source Project.
 */
public class RecycleBin {
    /**
     * Views that were on screen at the start of layout. This array is populated at the start of
     * layout, and at the end of layout all view in activeViews are moved to scrapViews.
     * Views in activeViews represent a contiguous range of Views, with position of the first
     * view store in mFirstActivePosition.
     */
private View[] activeViews = new View[0];
    private int[] activeViewTypes = new int[0