/**稀土掘金,时光不老**/
大家好,很早就想写博客了,一是工作忙,二是缺乏原创性,三当然是自己的能力不够啦,写这篇博客是很惶恐。。。。请多多包涵
/****************************
-------- ---------
-------
***************************************/
好了,回到正题上来,最近看到韩海龙的博客 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的,
* 并且是越来越小,它右边的view的position是从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();
}
}
}
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()); ListstrList = 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反射机制,将自定义Scroll和ViewPager结合来调节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反射机制,将自定义Scroll和ViewPager结合来调节ViewPager的滑动效果**/
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. * RecyclingPagerAdapter是Jake WhartonAndroid大神封装的可用于复用的PagerAdapter。 */ public class TubatuAdapter extends RecyclingPagerAdapter { private ListmList; 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(); } }
补充一下,RecyclingPagerAdapter是Jake 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; } /** *再来一个RecycleBin类* 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); }
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; } } }
甩了一把汗,终于写完了,嗯嗯木有遗漏哦,纪念一下的我的第一篇博客,感谢各位道友,让我们一起稀土掘金,扎实成长吧!
来一句 弘洋大神 的名言