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"?>
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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

                    android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay">

        

    

    layout="@layout/content_main"/>



 
  

     |

       |

      |

看这里,定义布局模块--VeiwPager布局-content_main

xml version="1.0" encoding="utf-8"?>
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">

    

            android:id="@+id/viewpager"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerInParent="true"
        android:clipChildren="false"
        android:overScrollMode="never" />


上面的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 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 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代码

import android.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 mList;
    private Context mContext;
    private List strList;

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

    }

    public void addAll(List 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;
    }

    /**
     * 

* 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. *

*

* This method will only be called when when the adapter is set on the * the {@link AdapterView}. *

* * @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.
 * 

* 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]; /** * Unsorted views that can be used by the adapter as a convert view. */ private SparseArray[] scrapViews; private int viewTypeCount; private SparseArray currentScrapViews; public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) { throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); } //noinspection unchecked SparseArray[] scrapViews = new SparseArray[viewTypeCount]; for (int i = 0; i < viewTypeCount; i++) { scrapViews[i] = new SparseArray(); } this.viewTypeCount = viewTypeCount; currentScrapViews = scrapViews[0]; this.scrapViews = scrapViews; } protected boolean shouldRecycleViewType(int viewType) { return viewType >= 0; } /** * @return A view from the ScrapViews collection. These are unordered. */ View getScrapView(int position, int viewType) { if (viewTypeCount == 1) { return retrieveFromScrap(currentScrapViews, position); } else if (viewType >= 0 && viewType < scrapViews.length) { return retrieveFromScrap(scrapViews[viewType], position); } return null; } /** * Put a view into the ScrapViews list. These views are unordered. * * @param scrap The view to add */ void addScrapView(View scrap, int position, int viewType) { if (viewTypeCount == 1) { currentScrapViews.put(position, scrap); } else { scrapViews[viewType].put(position, scrap); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { scrap.setAccessibilityDelegate(null); } } /** * Move all views remaining in activeViews to scrapViews. */ void scrapActiveViews() { final View[] activeViews = this.activeViews; final int[] activeViewTypes = this.activeViewTypes; final boolean multipleScraps = viewTypeCount > 1; SparseArray scrapViews = currentScrapViews; final int count = activeViews.length; for (int i = count - 1; i >= 0; i--) { final View victim = activeViews[i]; if (victim != null) { int whichScrap = activeViewTypes[i]; activeViews[i] = null; activeViewTypes[i] = -1; if (!shouldRecycleViewType(whichScrap)) { continue; } if (multipleScraps) { scrapViews = this.scrapViews[whichScrap]; } scrapViews.put(i, victim); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { victim.setAccessibilityDelegate(null); } } } pruneScrapViews(); } /** * Makes sure that the size of scrapViews does not exceed the size of activeViews. * (This can happen if an adapter does not recycle its views). */ private void pruneScrapViews() { final int maxViews = activeViews.length; final int viewTypeCount = this.viewTypeCount; final SparseArray[] scrapViews = this.scrapViews; for (int i = 0; i < viewTypeCount; ++i) { final SparseArray scrapPile = scrapViews[i]; int size = scrapPile.size(); final int extras = size - maxViews; size--; for (int j = 0; j < extras; j++) { scrapPile.remove(scrapPile.keyAt(size--)); } } } static View retrieveFromScrap(SparseArray scrapViews, int position) { int size = scrapViews.size(); if (size > 0) { // See if we still have a view for this position. for (int i = 0; i < size; i++) { int fromPosition = scrapViews.keyAt(i); View view = scrapViews.get(fromPosition); if (fromPosition == position) { scrapViews.remove(fromPosition); return view; } } int index = size - 1; View r = scrapViews.valueAt(index); scrapViews.remove(scrapViews.keyAt(index)); return r; } else { return null; } } }


甩了一把汗,终于写完了,嗯嗯木有遗漏哦,纪念一下的我的第一篇博客,感谢各位道友,让我们一起稀土掘金,扎实成长吧!

来一句 弘洋大神 的名言  

生命不息,奋斗不止,万事起于忽微,量变引起质变 


你可能感兴趣的:(自定义控件ViewPager)