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代码

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<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];

    /**
     * Unsorted views that can be used by the adapter as a convert view.
     */
    private SparseArray<View>[] scrapViews;

    private int viewTypeCount;

    private SparseArray<View> currentScrapViews;

    public void setViewTypeCount(int viewTypeCount) {
        if (viewTypeCount < 1) {
            throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
        }
        //noinspection unchecked
        SparseArray<View>[] scrapViews = new SparseArray[viewTypeCount];
        for (int i = 0; i < viewTypeCount; i++) {
            scrapViews[i] = new SparseArray<View>();
        }
        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<View> 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<View>[] scrapViews = this.scrapViews;
        for (int i = 0; i < viewTypeCount; ++i) {
            final SparseArray<View> 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<View> 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;
        }
    }
}

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

来一句 弘洋大神 的名言  

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


评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值